3. 继承
把 JavaScript 中,继承相关的内容复制过来。
下面整理常见的继承方式:
- 经典继承 / 借用构造函数
- 组合继承
- 原型继承
- 寄生继承
- 寄生组合继承
- 最佳实践
简单整理:
// 1 原型链继承:子类原型继承父类属性
Son.prototype = new Father();
// 2 经典继承 / 借用构造函数:在Son内不用new直接调用Father构造函数
Function Son(name) {Father.call(this, arguments);}
// 1和2优缺点是互斥的,由此引出方法3:
// 「原型链继承」解决了父类 **方法复用** 的问题,出现了父类 **属性不独立** 的问题。
// 「借用构造函数」解决父类 **属性独立** 的问题,出现了父类 **方法无法复用** 的问题。
// 3 组合继承:调用2次 new Father() => Son内部调用(继承属性), Son.prototype绑定调用(继承方法)
function Son(name,age){ Father.call(this,name) }
Son.prototype = new Father()
// 4 原型式继承:自定义objet,父类子类所有方法都共用
// ES5-
function object(obj) {
function Son() {}
Son.prototype = obj;
return new Son()
}
// ES5
Student.prototype = Object.create(Person.prototype) // Student原型被重定义,constructor需要重新指向。
// ES6
Object.setPrototypeOf(Son.prototype, Father.prototype)
// 5 寄生式继承:是原型式的升级版,有object()的基础上,利用 createSon()实现Son增添新属性和方法。
function createSon(original) {
// let Son = Object.create(original);
let son = object(original);
son.sayHi = function () {
console.log('hi')
}
return son
}
// 4 和 5 都不会生成构造函数和类的概念,而是直接构造了实例对象:
let son1 = object(father); // 原型式
let son1 = createSon(father); // 寄生式
// 6 寄生组合式,继承父类方法调用inheritePrototype绑定原型链,继承父类方法用new Father()调用构造函数。
function inheritPrototype(Sub, Super) {
let subPrototype = Object.create(Super.prototype)
subPrototype.constructor = Sub
Sub.prototype = subPrototype
}
1 原型链继承
基本思想:继承父类的属性和方法,全部依赖原型链实现。
核心代码:Son.prototype = new Father();
- 得到父类的 实例化对象,
- 把子类原型对象修改为第一步得到的实例化对象。
Son的原型对象 Son.prototype
不再是原配,而变成 Father的实例化对象,所以此时 Son 原型对象的构造函数属性Son.prototype.constructor
丢失,需要重新绑定。
优点:
- 方法复用,父类的方法绑定在原型链上,可以被正确的复用。
缺点:
- 属性不独立,原型链继承,父类的属性无法实例化到对象上,而是和方法一样被复用、共用。
- 不可初始化。子类实例对象无法对继承的父类属性进行初始化,只能初始化子类的属性。
- 内容修改。子类实例对象(instance1 和 instance2)会指向同一个引用属性(如 array),这导致如果其中一个实例对象对引用属性的 内容 进行修改
instance1.color.push('pink')
,instance2 的 color 也会跟着改变。 - 显式打印。继承的父类属性,无法显式通过
console.log(instance1)
打印出来。
- 子类原型对象的构造函数属性丢失,需要重新绑定
.constructor
。
// 父类构造函数、父类 属性
function Father() {
this.name = "Moxy";
this.color = ['red', 'yellow', 'black'];
}
// 父类原型、父类方法
Father.prototype.sayName = function () {
console.log(this.name);
};
// 子类构造函数、子类属性
function Son(age) {
this.age = age;
};
// 子类原型、继承父类属性
Son.prototype = new Father();
// 子类方法
Son.prototype.sonFunc = function() {
console.log("Son Function");
}
// 实例化测试:
let instance1 = new Son("12");
let instance2 = new Son("18");
console.log(instance1.__proto__.color === instance2.__proto__.color)
// true,父类属性被子类实例化对象公用
instance1.sayName(); // Moxy,方法成功继承
// 关于公用属性测试:
instance1.color.push('pink'); // 4
instance2.color; // ['red', 'yellow', 'black', 'pink'] 对引用属性的操作,不会发生赋值屏蔽
instance1.name = "ninjee" // 'ninjee' 对原型链上的属性 name 赋值屏蔽,
instance2.name; // 'Moxy' 而不是直接修改原型链的原属性值
instance1.color = "hello world"; //'hello world' 这里发生了赋值屏蔽。
instance2.color; // ['red', 'yellow', 'black', 'pink']