ES5-继承

接下来在总结 es6 的 class 之前,回顾总结一下 ES5 实现继承的方法

原型链

如果我们让一个实例的原型等于另一个实例,此实例的原型将包含一个指向另一个原型的指针。
举例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Animal(name = "动物") {
this.name = name;
this.foods = ["食物1", "食物2"];
}
Animal.prototype = {
constructor: Animal,
eat() {
console.log(this.foods);
},
};

function Dog(name = "狗子") {
this.name = name;
}
Dog.prototype = new Animal();

var dog1 = new Dog("哈士奇");
console.log(dog1.name); //哈士奇

这里定义了 Animal 和 Dog 两个类型,其中 Dog 通过把 prototype 赋值给 Animal 的实例从而继承了 Animal,这样 Dog 的新原型不仅有 Dog 的属性和方法,也有 Animal 中的属性和方法,因为 prototype 被改写,dog1 的 constructor 指向 Animal。

使用isPrototypeOf() 或者 instanceof可以确定实例和构造函数之原型链间的关系

1
2
3
console.log(Object.prototype.isPrototypeOf(dog1)); //true
console.log(Animal.prototype.isPrototypeOf(dog1)); //true
console.log(Dog.prototype.isPrototypeOf(dog1)); //true

和上篇创建对象遇到的问题一样,包含引用类型之的原型属性会被所有实例共享

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Animal(name = "动物") {
this.name = name;
}
Animal.prototype = {
constructor: Animal,
foods: ["食物1", "食物2"],
eat() {
console.log(this.foods);
},
};

function Dog(name = "狗子") {
this.name = name;
}
Dog.prototype = new Animal();
var dog = new Dog("哈士奇");
dog.foods.push("哈士奇的狗粮");

var dog2 = new Dog("柯基");
dog2.eat();

很明显,我们不会想让柯基去吃哈士奇的狗粮,导致上述问题的原因是 dog dog1 实例的原型是同一个 Animal 的实例,自然是共用属性。

原型链还有第二个问题,就是创建 Dog 的时候没有办法向 Animal 传递参数。

构造函数

解决上面的问题可以用借用构造函数的技术

1
2
3
4
5
6
7
8
9
10
11
function Animal(name = "动物") {
this.name = name;
this.foods = ["食物1", "食物2"];
this.eat = function () {
console.log(this.foods);
};
}

function SubType() {
Animal.call(this);
}

看看就行,跟上一篇创建对象的构造函数方法一样,都是执行构造函数的代码。缺点也类似,没有解决函数重用的问题。最常使用的是组合继承。

组合继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Animal(name = "动物") {
this.name = name;
this.foods = [];
}

Animal.prototype.eat = function () {
console.log(this.foods);
};
function Dog(name) {
//继承属性
Animal.call(this, name);
this.color = "白色";
}
//继承方法
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;
var dog1 = new Dog("柯基");
dog1.foods.push("柯基的狗粮");
dog1.eat(); //["柯基的狗粮"]
var dog2 = new Dog("哈士奇");
dog2.foods.push("哈士奇的狗粮");
dog2.eat(); //["哈士奇的狗粮"]
console.log(dog2.eat == dog2.eat); //true

实际上组合继承的属性是通过创造出子类的 this 后将父类的中的属性添加到 this 上。方法是通过重写子类的 prototype 后拿到父类实例及其原型的方法。

组合继承避免了原型链和借用构造函数的缺陷。是 Js 中最常用的继承。