原型链继承

1
2
3
4
5
6
7
8
9
function Zoo() {
this.zoo = "动物园";
}
function panda(name) {
this.name = name;
}
panda.prototype = new Zoo();
var animal = new panda("熊猫");
console.log(animal.zoo + "里面有一只" + animal.name);

缺点: 不能向父类构造函数(Zoo)传递参数

构造函数继承

——在子类型构造函数的内部调用父类构造函数,使用 apply()和 call()方法将 this 指向子类

1
2
3
4
5
6
7
8
9
function Zoo(zoo) {
this.zoo = zoo;
}
function panda(zoo, name) {
Zoo.call(this, zoo); // 把 Zoo的this 指向panda
this.name = name;
}
var animal = new panda("动物园", "熊猫");
console.log(animal.zoo + "里面有一只" + animal.name);

缺点: 无法调用父类原型上的方法 例如:

1
2
3
4
Zoo.prototype.sleep = function () {
console.log("要睡觉觉了!");
};
console.log(animal.sleep()); // TypeError: animal.sleep is not a function

组合继承

——将原型链继承和构造函数继承组合到一起。使用原型链继承实现对原型属性和方法的继承,用借用构造函数继承实现对父实例属性的继承。这样既通过在原型上定义方法实现了函数复用,又能保证每个实例都有自己的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Zoo(zoo) {
this.zoo = zoo;
}
Zoo.prototype.sleep = function () {
console.log("要睡觉觉了!");
};
function panda(zoo, name) {
Zoo.call(this, zoo); // 把 Zoo的this 指向panda
this.name = name;
}
panda.prototype = new Zoo();
var animal = new panda("动物园", "熊猫");
console.log(animal.zoo + "里面有一只" + animal.name);
animal.sleep();

缺点: 会调用两次超类型构造函数,一次是在创建子类型原型的时候,一次是在子类型构造函数的内部,占用内存。

寄生组合式继承

——寄生组合式继承是对组合继承的进一步优化。我们先看一下为什么要写这个语句。

1
panda.prototype = new Zoo();

我们无非是想让 panda 继承 Zoo 的原型。但是我们为什么不直接写成这样呢?

1
panda.prototype = Zoo.prototype;

——这样写确实可以实现子类对象对父类对象原型的继承。但是这样写的话:所有继承该父类的子类对象的原型都指向同一个了。也就是说 SubType 不能有自己的原型了。这显然不是我们想要的。
——既然不能直接继承,那可不可以间接继承 Zoo.prototype 呢。这就是最终的解决方案:间接组合式继承。
——我们让一个对象去指向 Zoo.prototype,然后让 panda.prototype 指向这个函数产生的对象不就可以了嘛。
——核心函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function indirect(son, father) {
var target = Object.create(father.prototype); // 也可以直接写var target = father.prototype; father.prototype本身就是一个对象,万物皆对象
target.constructor = son;
son.prototype = target;
}
function Zoo(zoo) {
this.zoo = zoo;
}
Zoo.prototype.sleep = function () {
console.log("要睡觉觉了!");
};
function panda(zoo, name) {
Zoo.call(this, zoo); // 把 Zoo的this 指向panda
this.name = name;
}
indirect(panda, Zoo);
var animal = new panda("动物园", "熊猫");
console.log(animal.zoo + "里面有一只" + animal.name);
animal.sleep();

优点: 只调用了一次 Zoo 构造函数,因此避免在 panda.prototype 上创建不必要的,多余的属性,与此同时,原型链还能保持不变,还能正常使用 instanceof 和 isPrototypeOf(),因此,寄生组合式继承被认为是引用类型最理想的继承范式。

ES6 继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Zoo {
constructor(zoo) {
this.zoo = zoo;
}
sleep() {
return "要睡觉觉了!";
}
}
class animal extends Zoo {
constructor(zoo, age) {
super(zoo);
this.age = age;
}
eat() {
console.log("吃完饭饭" + super.sleep()); // 这里的super指代的是父亲构造函数 Zoo 的原型上的方法 Zoo.prototype
}
}
var panda = new animal("上野动物园", "5");
console.log(panda.zoo); // 上野动物园
panda.eat(); // 吃完饭饭要睡觉觉了!

——Class 可以通过 extends 关键字实现继承
——子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。
——这是因为子类自己的 this 对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用 super 方法,子类就得不到 this 对象。
—— super(zoo)里的参数 zoo 是父亲的属性 要是用父类属性需要加上参数