原型链继承
之前给大家介绍过,原型链的主要问题是原型中包含的引用值会在实例间共享。
function father() {
this.color = ['pink', 'green', 'red']
}
function son() {}
son.prototype = new father()
let instance = new son()
instance.color.push('yellow')
console.log(instance.color)
let instance2 = new son()
instance2.color.push('gray')
console.log(instance2.color)
log
原型链的第二个问题是子类型在实例化时不能给父类型的构造函数传参。事实上,我们无法在不影响所有对象实例的情况下,把参数传进父类的构造函数。再加上之前提到的原型中包含原型引用值的问题,就导致原型链基本不会被单独使用。
盗用构造函数
红宝书上提到了为了解决原型包含引用值导致的继承问题,出现了盗用构造函数的技术。也叫作“对象伪装”或者“经典继承”“构造继承”。
思路是:在子类构造函数中调用父类构造函数。因为毕竟函数就是在特定上下文中执行代码的简单对象。所以可以使用 apply() 和 call() 方法,以新创建的对象为上下文执行构造函数。
function father(){
this.color=['pink', 'green', 'red']
}
function son(){
father.call(this)
}
son.prototype=new father()
let instance = new son()
instance.color.push('yellow')
console.log(instance.color)
let instance2 = new son()
instance2.color.push('gray')
console.log(instance2.color)
log
- 1、传递参数
相比于使用原型链,盗用构造函数的一个优点就是可以在子类构造函数中向父类构造函数传参
function father(name){
this.name = name
}
function son(){
// 继承father并传参
father.call(this,'杨雨')
//实例属性
this.sex="男"
}
let instance = new son()
console.log(instance.name)
console.log(instance.sex)
- 2、盗用构造函数的问题
盗用构造函数的主要缺点,也是使用构造函数自定义类型的问题:必须在构造函数中定义方法,不能通过prototype了,因此函数不能重用。此外,子类不能访问父类原型上的定义的方法。因此所有类型只能使用构造函数模式。由于存在这些问题,盗用构造函数基本上也不能单独使用。
组合继承
组合继承(有时候也叫伪经典继承)综合了原型链和盗用构造函数,将两种继承方式的优点集中了起来。
基本思路是使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。
function father(name){
this.name = name
this.color=['pink', 'green', 'red']
}
father.prototype.sayName = function(){
console.log(this.name)
}
function son(name,age){
//继承属性
father.call(this,name)
this.age=age
}
//继承方法
son.prototype = new father()
son.prototype.sayAge = function(){
console.log(this.age)
}
let instance = new son('杨雨',22)
instance.color.push('yellow')
console.log(instance.color)
instance.sayAge()
instance.sayName()
let instance2 = new son('杨雪',27)
instance2.color.push('gray')
console.log(instance2.color)
instance2.sayAge()
instance2.sayName()
log
原型式继承
适合不需要单独创建构造函数,但仍需要在对象间共享信息的场合。但要记住,属性中包含的引用值始终会在相关对象间共享,跟使用原型模式是一样的。es5通过增加Object.create()方法将原型式继承的概念规范化了。这个方法接受了两个参数:作为新对象原型的对象,以及给新对象定义额外属性的对象(第二个可选)
let father = {
name:"杨雨",
color:['pink', 'green', 'red']
}
let son = Object.create(father)
son.name="杨雪",
son.color.push('yellow')
console.log(son.name)
console.log(son.color)
let son2 = Object.create(father)
son2.name="杨增卫"
son2.color.push('gray')
console.log(son2.name)
console.log(son2.color)
log
- Object.create()的第二个参数和Object.defineProperties()的第二个参数一样,每个新增属性都通过各自的描述符来描述,以这种方式添加的属性会遮蔽原型对象上的同名属性。
let father = {
name:"杨雨"
}
let son = Object.create(father,{name:{
value:"杨雪"
}})
console.log(son.name)
log
寄生式继承
与原型式继承比较接近的一种继承方式是寄生式继承,寄生式继承背后的思路类似于寄生构造函数和工厂模式:创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象。基本的寄生继承模式如下:
function father(original) {
let clone = Object(original) // 通过调用函数创建一个新对象
clone.sayHi = function () { // 以某种方式增强这个对象
console.log('hi')
}
return clone; // 返回这个对象
}
使用:
let person = {
name:"杨雨",
color:['pink', 'green', 'red']
}
let son = father(person)
son.sayHi()
console.log(son.color)
log
这个例子基于Promise对象,返回了一个新对象。新返回的son对象具有person 对象具有person的所有睡醒和方法。还有一个新方法sayHi.
寄生式继承同样适用于主要关注对象,而不在乎类型和构造函数的场景。
object()函数不是寄生式继承所必须的,任何返回新对象的函数都可以
在这里使用。
TIPS:
通过寄生式继承给对象添加函数会导致函数难以重用,与构造函数模式类似。
寄生式组合继承
目的:
解决组合继承父类构造函数始终会被调用两次的效率问题。
寄生式组合继承通过盗用构造函数继承属性,但使用混合式原型链继承方法。基本思路是不通过调用父类构造函数给子类原型赋值,而是取到父类的一个副本。说到底就是使用寄生式继承来继承父类原型,然后将返回的新对象赋值给子类原型。寄生式组合继承的基本模式如下所示:
function inheritPrototype(father,son){
let prototype = Object(father.prototype)
prototype.constructor = son
son.prototype = prototype
}
这个inheritPrototype()函数实现了寄生式组合继承的核心逻辑。这个函数接收两个参数:子类构造函数和父类构造函数。在这个函数内部,第一步是创建父类原型的一个副本。然后,给返回的prototype对象赋值给子类型的原型。如下所示,调用inheritPrototype()就可以实现前面例子中的子类型原型赋值。
function object(o) {
function F() {}
F.prototype = o
return new F()
}
function inheritPrototype(father, son) {
let prototype = object(father.prototype)
prototype.constructor = son
son.prototype = prototype
}
function father(name) {
this.name = name
this.color = ['pink', 'green', 'red']
}
father.prototype.sayName = function () {
console.log(this.name)
}
function son(name, age) {
father.call(this, name)
this.age = age
}
inheritPrototype(father, son);
son.prototype.sayAge = function () {
console.log(this.age)
}
let son2 = new son("杨雨", 22)
son2.color.push('yellow')
console.log(son2.color)
son2.sayAge()
son2.sayName()
//引用值不共享
let son3 = new son("杨雪",27)
son3.color.push('gray')
console.log(son3.color)
log