1. 原型链继承
构造函数、原型和实例之间的关系:
- 每个构造函数都有一个原型对象(
protype
)
- 原型对象都包含一个指向构造函数的指针(
constructor
)
- 实例都包含一个指向构造函数原型对象的指针,也叫隐式对象(
__proto__
)。
继承的本质就是复制,即重写原型对象,代之以一个新类型的实例。
1.1. 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| function SuperType() { this.property = true; }
SuperType.prototype.getSuperValue = function() { return this.property; }
function SubType() { this.subproperty = false; }
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function() { return this.subproperty; }
const instance = new SubType(); console.log(instance.getSuperValue()); console.log(instance.getSubValue()); console.log(instance instanceof SuperType); console.log(instance instanceof SubType); console.log(Object.getPrototypeOf(SubType.prototype) === SuperType.prototype);
|
1.2. 优缺点分析
- 优点:父类的方法可以复用
- 缺点:多个实例对 引用类型 的操作会被篡改 (原始类型不受影响);子类示例不能给父类构造函数传参
1 2 3 4 5 6 7 8 9 10 11 12 13
| function SuperType() { this.colors = ['red', 'blue', 'green'] } function SubType() {}
SubType.prototype = new SuperType()
const instance1 = new SubType() instance1.colors.push('black') console.log(instance1.colors)
const instance2 = new SubType() console.log(instance2.colors)
|
2. 借用构造函数继承
使用父类的构造函数来增强子类实例,等同于复制父类的实例给子类(不使用原型)
2.1. 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function SuperType() { this.color = ['red', 'green', 'blue'] } function SubType() { SuperType.call(this) } const instance1 = new SubType() instance1.color.push('black') console.log(instance1.color)
const instance2 = new SubType() console.log(instance2.color)
|
2.2. 优缺点分析
- 优点:父类引用类型的数据不会被子类共享,不会相互影响
- 缺点:
- 只能继承父类的实例属性和方法,不能继承父类原型属性/方法
- 无法实现复用,每个子类都有父类实例函数的副本,影响性能
3. 组合继承
组合上述两种方法就是组合继承:
- 用原型链实现对原型属性和方法的继承
- 用借用构造函数技术来实现实例属性的继承。
3.1. 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| 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) this.age = age }
SubType.prototype = new SuperType()
SubType.prototype.constructor = SubType SubType.prototype.sayAge = function () { console.log(this.age) }
const instance1 = new SubType('Nicholas', 29) instance1.colors.push('black') console.log(instance1.colors) instance1.sayName() instance1.sayAge()
const instance2 = new SubType('Greg', 27) console.log(instance2.colors) instance2.sayName() instance2.sayAge()
|
3.2. 优缺点分析
4. 原型式继承
利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型。
4.1. 实现
object()
对传入其中的对象执行了一次浅复制,将构造函数F
的原型直接指向传入的对象。
Object.create()
的方法,能够代替代码中的object
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function object(obj) { function F() {} F.prototype = obj return new F() } const person = { name: 'Nicholas', friends: ['Shelby', 'Court', 'Van'], }
const anotherPerson = object(person) anotherPerson.name = 'Greg' anotherPerson.friends.push('Rob')
const yetAnotherPerson = object(person) yetAnotherPerson.name = 'Linda' yetAnotherPerson.friends.push('Barbie')
console.log(person.friends)
|
4.2. 优缺点分析
- 缺点:
- 原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。
- 无法传递参数
5. 寄生式继承
在原型式继承的基础上,增强对象,返回构造函数。
5.1. 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function object(obj) { function F() {} F.prototype = obj return new F() } function createAnother(original) { const clone = object(original) clone.sayHi = function () { console.log('hi') } return clone } const person = { name: 'Nicholas', friends: ['Shelby', 'Court', 'Van'], } const anotherPerson = createAnother(person) anotherPerson.sayHi()
|
5.2. 优缺点分析
- 缺点(同原型式继承):
- 原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。
- 无法传递参数
6. 寄生组合式继承
结合借用构造函数传递参数和寄生模式实现继承。
目前最优的方案,最成熟的方法,也是现在库实现的方法
6.1. 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| function inheritPrototype(subType, superType) { const prototype = Object.create(superType.prototype) prototype.constructor = subType subType.prototype = prototype }
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) this.age = age }
inheritPrototype(SubType, SuperType)
SubType.prototype.sayAge = function () { console.log(this.age) }
const instance1 = new SubType('xyc', 23) const instance2 = new SubType('lxy', 23)
instance1.colors.push('2') instance1.colors.push('3') console.log(instance1.colors) console.log(instance2.colors) instance1.sayName() instance1.sayAge() instance2.sayName() instance2.sayAge()
|
6.2. 优缺点分析
7. 混入方式继承多个对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function MyClass() { SuperClass.call(this) OtherSuperClass.call(this) }
MyClass.prototype = Object.create(SuperClass.prototype)
Object.assign(MyClass.prototype, OtherSuperClass.prototype)
MyClass.prototype.constructor = MyClass
MyClass.prototype.myMethod = function () { }
|
Object.assign
会把 OtherSuperClass
原型上的函数拷贝到 MyClass
原型上,使 MyClass
的所有实例都可用 OtherSuperClass
的方法。
8. ES6类继承extends
extends
关键字主要用于类声明或者类表达式中,以创建一个类,该类是另一个类的子类。其中constructor
表示构造函数,一个类中只能有一个构造函数,有多个会报出SyntaxError错误,如果没有显式指定构造方法,则会添加默认的 constructor
方法。
8.1. 使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| class Rectangle { constructor(height, width) { this.height = height this.width = width }
get area() { return this.calcArea() }
calcArea() { return this.height * this.width } }
const rectangle = new Rectangle(10, 20) console.log(rectangle.area)
class Square extends Rectangle { constructor(length) { super(length, length)
this.name = 'Square' }
get area() { return this.height * this.width } }
const square = new Square(10) console.log(square.area)
|
8.2. 实现原理
extends
继承的实现和寄生组合式继承方式一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function _inherits(subType, superType) { subType.prototype = Object.create(superType && superType.prototype, { constructor: { value: subType, enumerable: false, writable: true, configurable: true, }, })
if (superType) { Object.setPrototypeOf ? Object.setPrototypeOf(subType, superType) : (subType.__proto__ = superType) } }
|
9. 总结
9.1. 函数声明和类声明的区别
函数声明会提升,类声明不会。
9.2. ES5继承和ES6继承的区别
- ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到
this
上( Parent.call(this)
)
- ES6的继承有所不同,实质上是先创建父类的实例对象
this
,然后再用子类的构造函数修改this
。 因为子类没有自己的this
对象,所以必须先调用父类的super()
方法,否则新建实例报错。