类与实例
类的声明
// 类的声明(es5)
var Animal = function () {
this.name = "Animal";
};
// 类的声明(es6)
class Animal2 {
constructor() {
this.name = "Animal2";
}
}
生成实例
// 类的声明(es5)
var Animal = function () {
this.name = "Animal";
};
// 类的声明(es6)
class Animal2 {
constructor() {
this.name = "Animal2";
}
}
// 实例化
console.log(new Animal()); // Animal { name: 'Animal' }
console.log(new Animal2()); // Animal2 { name: 'Animal2' }
继承实现
本质就是原型链
概念
说到继承的概念,首先要说一个经典的例子。
先定义一个类(Class)叫汽车,汽车的属性包括颜色、轮胎、品牌、速度、排气量等,由汽车这个类可以派生出“轿车”和“货车”两个类,那么可以在汽车的基础属性上,为轿车添加一个后备厢、给货车添加一个大货箱。这样轿车和货车就是不一样的,但是二者都属于汽车这个类,这样从这个例子中就能详细说明汽车、轿车以及卡车之间的继承关系。
继承可以使得子类别具有父类的各种方法和属性,比如上面的例子中“轿车” 和 “货车” 分别继承了汽车的属性,而不需要再次在“轿车”中定义汽车已经有的属性。在“轿车”继承“汽车”的同时,也可以重新定义汽车的某些属性,并重写或覆盖某些属性和方法,使其获得与“汽车”这个父类不同的属性和方法。
继承的基本概念就初步介绍这些,下面我们就来看看 JavaScript 中都有哪些实现继承的方法。
继承的实现方式
构造函数继承
特点: 子类实例共享父类引用属性的问题 创建子类实例时,可以向父类传递参数 可以实现多继承(call多个父类对象)
缺点: 实例并不是父类的实例,只是子类的实例 只能继承父类的实例属性和方法,不能继承原型属性/方法 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
function Parent1() {
this.name = "parent1";
}
Parent1.prototype.say = function () {};
function Child1() {
Parent1.call(this);
this.type = "child1";
}
console.log(new Child1(), new Child1().say());
原型链继承
特点: 解决子类实例共享父类引用属性的问题 创建子类实例时,可以向父类传递参数 可以实现多继承(call多个父类对象)(不完美,没有父类方法)
缺点: 实例并不是父类的实例,只是子类的实例 只能继承父类的实例属性和方法,不能继承原型属性/方法 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
function Parent2() {
this.name = "parent2";
this.play = [1, 2, 3];
}
function Child2() {
this.type = "child2";
}
Child2.prototype = new Parent2();
var s1 = new Child2();
var s2 = new Child2();
console.log(s1.play, s2.play); // [ 1, 2, 3 ] [ 1, 2, 3 ]
s1.play.push(4);
// TODO: 原型链中的原型对象共用
console.log(s1.play, s2.play); // [ 1, 2, 3, 4 ] [ 1, 2, 3, 4 ]
console.log(s1.__proto__ === s2.__proto__); // true
组合方式继承
特点: 解决原型链实现继承的缺点
缺点: 父级构造函数执行了两次,影响性能
function Parent3() {
this.name = "parent3";
this.play = [1, 2, 3];
}
function Child3() {
Parent3.call(this);
this.type = "child3";
}
Child3.prototype = new Parent3();
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play); // [ 1, 2, 3, 4 ] [ 1, 2, 3 ]
组合继承 - 优化1
特点: 父类执行一次(子类实例化执行),Child4.prototype = Parent4.prototype解释(因为都是对象,都是引用类型,不会执行父级构造函数)
缺点: 如何区分一个对象是由它的子类实例化的,还是父类实例化的(s5是Child4还是Parent4直接实例化的)
function Parent4() {
this.name = "parent4";
this.play = [1, 2, 3];
}
function Child4() {
Parent4.call(this);
this.type = "child4";
}
Child4.prototype = Parent4.prototype;
var s5 = new Child4();
var s6 = new Child4();
console.log(s5, s6); // Parent4 { name: 'parent4', play: [ 1, 2, 3 ], type: 'child4' } Parent4 { name: 'parent4', play: [ 1, 2, 3 ], type: 'child4' }
console.log(s5 instanceof Child4, s5 instanceof Parent4); // true true
console.log(s5.constructor); // [λ: Parent4]
组合继承 - 优化2
Child5.prototype = Object.create(Parent5.prototype)解释:Object.create来创建一个中间对象,把两个原型对象区分开,这个中间对象还具备一个特性,它的原型对象是父类的原型对象,这样就可以连起来,通过给Child5的原型对象的constructor做修改,就可以正常区分开父类和子类的构造函数
function Parent5() {
this.name = "parent5";
this.play = [1, 2, 3];
}
function Child5() {
Parent5.call(this);
this.type = "child5";
}
Child5.prototype = Object.create(Parent5.prototype);
Child5.prototype.constructor = Child5;
var s7 = new Child5();
console.log(s7 instanceof Child5, s7 instanceof Parent5); // true true
console.log(s7.constructor); // [λ: Child5]