「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战」。
一、原型链继承
说到原型链继承,我们自然会想到构造函数、原型和实例的关系:每个构造函数都有一个prototype属性指向原型对象,原型有一个属性constructor指回构造函数,而实例有一个内部指针__proto__指向原型。
基本思想是:我们将原型指向一个类型的实例,这样就在他的原型链上了,就可以实现继承。
function Parent (name) {
this.name = name
this.hobby = '炒股'
this.say = function () {
console.log(`我的名字叫${this.name},我的爱好是${this.hobby},我的存款是${this.deposit}`)
}
}
Parent.prototype.deposit = '2000000'
const parent = new Parent('张三')
function Son (name) {
this.name = name
this.hobby = '打游戏'
}
Son.prototype = new Parent()
const son = new Son('张小三')
Son.prototype.say = function() {
console.log(`我的名字叫${this.name},我的爱好是${this.hobby},我将继承我爸爸的存款${this.deposit}`)
}
parent.say()
son.say()
缺点:
- 在原型中包含引用值的时候,原型中包含的引用值会在所有实例间共享。
- 子类型在实例化时不能给父类型的构造函数传参。
二、盗用构造函数继承
基本思路:在子类构造函数中调用父类构造函数。
function Son (name) {
Parent.call(this, arguments)
this.name = name
}
const son = new Son('张小小三')
parent.say()
son.say()
缺点:
- 必须在构造函数中定义方法,因此函数不能重用。
- 只能继承父类的实例属性和方法,不能继承原型属性/方法
由于存在这些问题,盗用构造函数基本上也不能单独使用。
三、组合继承
综合了原型链和盗用构造函数,基本的思路是:使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。
// 组合继承
function Son (name) {
Parent.call(this, arguments)
this.name = name
this.hobby = '打游戏'
}
Son.prototype = new Parent()
const son = new Son('张哈哈')
Son.prototype.say = function() {
console.log(`我的名字叫${this.name},我的爱好是${this.hobby},我将继承我爸爸的存款${this.deposit}`)
}
Son.prototype.say1 = function() {
console.log(`我的名字叫${this.name},我的爱好是${this.hobby},我将继承我爸爸的存款${this.deposit}`)
}
parent.say()
son.say()
son.say1()
组合继承弥补了原型链和盗用构造函数的不足,是 JavaScript 中使用最多的继承模式。而且组合继承也保留了 instanceof 操作符和 isPrototypeOf()方法识别合成对象的能力。
四、原型式继承
基本思路:
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
object()函数会创建一个临时构造函数,将传入的对象赋值给这个构造函数的原型,然后返回这个临时类型的一个实例。本质上,object()是对传入的对象执行了一次浅复制。
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
let person = {
name: '张三',
hobby: ['炒股', '买基金']
}
let son = object(person);
son.name = '张智成';
son.hobby.push('买黄金');
console.log(111, son.name, son.hobby)
ECMAScript 5 通过增加 Object.create()方法将原型式继承的概念规范化了。
let person = {
name: '张三',
hobby: ['炒股', '买基金']
}
let son = Object.create(person);
son.name = '张智成';
son.hobby.push('买黄金');
console.log(111, son.name, son.hobby)
原型式继承非常适合不需要单独创建构造函数,但仍然需要在对象间共享信息的场合。但要记住,属性中包含的引用值始终会在相关对象间共享,跟使用原型模式是一样的。
五、寄生式继承
思路类似于寄生构造函数和工厂模式:创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象。
function createAnother(original){
let clone = object(original); // 通过调用函数创建一个新对象
clone.say = function() { // 以某种方式增强这个对象
console.log(`我的名字叫${this.name},我的爱好是${this.hobby}`);
};
return clone; // 返回这个对象
}
let person = {
name: '张三',
hobby: ['炒股', '买基金']
};
let son = createAnother(person);
son.say()
通过寄生式继承给对象添加函数会导致函数难以重用,与构造函数模式类似。
六、寄生式组合继承
寄生式组合继承通过盗用构造函数继承属性,但使用混合式原型链继承方法。基本思路是不通过调用父类构造函数给子类原型赋值,而是取得父类原型的一个副本。说到底就是使用寄生式继承来继承父类原型,然后将返回的新对象赋值给子类原型。
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function inheritPrototype(Son, Parent) {
let prototype = object(Parent.prototype); // 创建对象
prototype.constructor = Son; // 增强对象
Son.prototype = prototype; // 赋值对象
}
function Parent (name) {
this.name = name
this.hobby = '炒股'
this.say = function () {
console.log(`我的名字叫${this.name},我的爱好是${this.hobby},我的存款是${this.deposit}`)
}
}
Parent.prototype.deposit = '2000000'
function Son (name) {
Parent.call(this, arguments)
this.name = name
this.hobby = '打游戏'
}
inheritPrototype(Son, Parent)
Son.prototype.sonSay = function() {
console.log(`我的名字叫${this.name},我的爱好是${this.hobby},我将继承我爸爸的存款${this.deposit}`)
}
const son = new Son('张哈哈')
son.sonSay()
寄生式组合继承原型链仍然保持不变,因此 instanceof 操作符和isPrototypeOf()方法正常有效,可以算是引用类型继承的最佳模式。