(一)继承中的属性或者方法查找原则:就近原则
- 继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的
- 如果子类没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法(就近原则)
(二)JS 中的常用的继承方式及其优缺点⭐⭐⭐⭐⭐
ES5:最常见的两种继承:原型链继承、构造函数继承
ES6:extend继承(寄生组合继承的语法糖)
1. 原型继承
(1)思路:
- 把父类的实例作为子类的原型
(2)缺点:
- 子类的实例共享了父类构造函数的引用属性
- 不能传参
var person = {
friends: ['a', 'b', 'c', 'd']
};
var p1 = Object.create(person);
p1.friends.push('aaa'); //缺点:子类的实例共享了父类构造函数的引用属性
console.log(p1);
console.log(person); //缺点:子类的实例共享了父类构造函数的引用属性
2. (ES5)原型链继承(prototype)
原型链继承:将子对象的 prototype 指向父对象的 实例
(1)缺点:
- 原型中包含的引用值会在所有实例间共享
- 子类型在实例化时不能给父类型的构造函数传参。
(2)基本思路
通过子类的prototype属性和父类进行关联继承
- 将父对象的 实例指向 子对象的 prototype,实现了对SuperType的继承。
SubType通过创建SuperType的实例并将其赋值给自己的原型SubTtype.prototype,SubType.prototype = new SuperType() - 这个赋值重写了SubType最初的原型,将其替换为SuperType的实例(关键)。
这意味着SuperType实例可以访问的所有属性和方法也会存在于SubType.prototype。 - 这样实现继承之后,代码紧接着又给SubType.prototype,也就是这个SuperType的实例添加了一个新方法。
- 最后又创建了SubType的实例并调用了它继承的getSuperValue()方法
① 定义父类
// -------------- 父类 ---------------
function SuperType() {
this.property = true;
this.colors = ['red', 'blue', 'green'];
}
// 父类方法
SuperType.prototype.getSuperValue = function () {
return this.property;
};
② 定义子类
// -------------- 子类 --------------
function SubType() {
this.subproperty = false;
}
// 继承父类SuperType,通过子类的prototype属性和父类进行关联继承
// SubType通过创建SuperType的实例并将其赋值给自己的原型SubTtype.prototype实现了对SuperType的继承
SubType.prototype = new SuperType(); //☆☆☆创建SuperType的实例并将其赋值给自己的原型
SubType.prototype.getSubValue = function () {
return this.subproperty;
};
③ 测试
// ---------- SuperType实例可以访问父类的所有属性和方法 ----------
let instance = new SubType();
// SuperType实例可以访问的所有属性和方法也会存在于SubType.prototyp
console.log(instance.getSuperValue()); // true,
// ------------- 缺点:原型中包含的引用值会在所有实例间共享 -------------
let instance1 = new SubType();
instance1.colors.push('black');
console.log(instance1.colors); // "red,blue,green,black"
let instance2 = new SubType();
console.log(instance2.colors); // "red,blue,green,black
3. (ES5)构造函数继承(组合继承)(prototype + call)
构造函数继承关键在于,使用apply()或call()方法,将父对象的构造函数绑定在子对象上,可以在新创建的对象上获取父类的成员和方法。
(1)优缺点
- 优点
- 可传参,不共享父类引用属性
- 是JavaScript中使用最多的继承模式。而且组合继承也保留了instanceof操作符和isPrototypeOf()方法识别合成对象的能力
- 缺点
- 调用了两次父类的构造函数,造成了不必要的消耗,父类方法可以复用
(2)基本思路
- 使用原型链继承原型上的属性和方法,
SubType.prototype = new SuperType(); - 通过盗用构造函数call继承实例属性
SuperType.call(this, name);
① 父类
// -------------- 父类 ---------------
function SuperType(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
SuperType.prototype.sayName = function () {
console.log(this.name);
};
② 子类继承
☆☆☆在子函数中运行父函数,但是要利用call把this的指向改变一下
// -------------- 子类 ---------------
function SubType(name, age) {
// 通过call继承父类属性
SuperType.call(this, name); // ☆☆☆在子函数中运行父函数,但是要利用call把this改变一下,
this.age = age;
}
// 继承方法
SubType.prototype = new SuperType(); //☆☆☆
SubType.prototype.sayAge = function () {
console.log(this.age);
};
③ 测试
//测试
let instance1 = new SubType('Nicholas', 29);
instance1.colors.push('black');
console.log(instance1.colors); // "red,blue,green,black"
instance1.sayName(); // "Nicholas";
instance1.sayAge(); // 29
let instance2 = new SubType('Greg', 27);
console.log(instance2.colors); // "red,blue,green"
instance2.sayName(); // "Greg";
instance2.sayAge(); // 27
4. 寄生组合继承(prototype + call + constructor)
- 通过借用构造函数来继承属性, 通过原型链来继承方法
- 不必为了指定子类型的原型而调用父类型的构造函数,我们只需要父类型的一个副本而已(本质上就是使用寄生式继承来继承超类型的原型, 然后再讲结果指定给子类型的原型)
缺点
- 最主要的效率问题就是父类构造函数始终会被调用两次
- 一次在是创建子类原型时调用,
- 另一次是在子类构造函数中调用。
// -------------- 父类 ---------------
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function() {
console.log(this.name);
};
// -------------- 子类 ---------------
function SubType(name, age){
// 通过借用构造函数来继承属性
SuperType.call(this, name); // 第二次调用SuperType()
this.age = age;
}
// 通过原型链来继承方法
SubType.prototype = new SuperType(); // 第一次调用SuperType()
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
console.log(this.age);
};
5. (ES6) extend(寄生组合继承的语法糖)
- ES6中新增了class关键字来定义类,通过保留的关键字extends实现了继承。实际上这些关键字只是一些语法糖,底层实现还是通过原型链之间的委托关联关系实现继承。
- 子类只要继承父类,可以不写constructor,一旦写了,则在constructor中的第一句话必须是super
class SubType extends SuperType {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
[延伸问题]
1. ES5/ES6 的继承除了写法以外还有什么区别?
区别于ES5的继承,ES6的继承实现在于使用super关键字调用父类,而ES5是通过call或者apply回调方法调用父类。
class声明会提升,但不会初始化赋值。Foo进入暂时性死区,类似于let、const声明变量。class声明内部会启用严格模式class内的所有方法(包括静态方法和实例方法)都是不可枚举的。class内的所有方法(包括静态方法和实例方法)都没有原型对象 prototype,所以也没有[[construct]],不能使用new来调用。- 必须使用
new调用class class内部无法重写类名。