前言
这篇文章将会介绍在 JavaScript 中经常使用的六种继承方式
正文
1.1 原型继承
方法:将子类的原型指向父类的实例
原理:子类在访问属性或调用方法时,往上查找原型链,能够找到父类的属性和方法
function SuperType(name, info) { // 实例属性(基本类型) this.name = name || 'Super' // 实例属性(引用类型) this.info = info || ['Super'] // 实例方法 this.getName = function() { return this.name } } // 原型方法 SuperType.prototype.getInfo = function() { return this.info } // 原型继承 function ChildType(message) { this.message = message } ChildType.prototype = new SuperType('Child', ['Child']) // 在调用子类构造函数时,无法向父类构造函数传递参数 var child = new ChildType('Hello') // 子类实例可以访问父类的实例方法和原型方法 console.log(child.getName()) // Child console.log(child.getInfo()) // ["Child"] // 所有子类实例共享父类的引用属性 var other = new ChildType('Hi') other.info.push('Temp') console.log(other.info) // ["Child", "Temp"] console.log(child.info) // ["Child", "Temp"]
- 缺点:在调用子类构造函数时,无法向父类构造函数传递参数
- 优点:子类实例可以访问父类的实例方法和原型方法
- 缺点:所有子类实例共享父类的引用属性
1.2 构造继承
方法:在子类的构造函数调用父类的构造函数,并将 this
指向子类实例
原理:在构造子类时,调用父类的构造函数初始化子类的属性和方法
function SuperType(name, info) { // 实例属性(基本类型) this.name = name || 'Super' // 实例属性(引用类型) this.info = info || ['Super'] // 实例方法 this.getName = function() { return this.name } } // 原型方法 SuperType.prototype.getInfo = function() { return this.info } // 构造继承 function ChildType(name, info, message) { SuperType.call(this, name, info) this.message = message } // 在调用子类构造函数时,可以向父类构造函数传递参数 var child = new ChildType('Child', ['Child'], 'Hello') // 子类实例可以访问父类的实例方法,但是不能访问父类的原型方法 console.log(child.getName()) // Child console.log(child.getInfo()) // Uncaught TypeError // 每个子类实例的属性独立存在 var other = new ChildType('Child', ['Child'], 'Hi') other.info.push('Temp') console.log(other.info) // ["Child", "Temp"] console.log(child.info) // ["Child"]
- 优点:在调用子类构造函数时,可以向父类构造函数传递参数
- 缺点:子类实例可以访问父类的实例方法,但是不能访问父类的原型方法,因此无法做到函数复用
- 优点:每个子类实例的属性独立存在
1.3 组合继承
方法:同时使用原型继承和构造继承,综合两者的优势所在
原理:通过原型继承实现原型属性和原型方法的继承,通过构造继承实现实例属性和实例方法的继承
function SuperType(name, info) { // 实例属性(基本类型) this.name = name || 'Super' // 实例属性(引用类型) this.info = info || ['Super'] // 实例方法 this.getName = function() { return this.name } } // 原型方法 SuperType.prototype.getInfo = function() { return this.info } // 组合继承 function ChildType(name, info, message) { SuperType.call(this, name, info) this.message = message } ChildType.prototype = new SuperType() ChildType.prototype.constructor = ChildType // 在调用子类构造函数时,可以向父类构造函数传递参数 var child = new ChildType('Child', ['Child'], 'Hello') // 子类实例可以访问父类的实例方法和原型方法 console.log(child.getName()) // Child console.log(child.getInfo()) // ["Child"] // 每个子类实例的属性独立存在 var other = new ChildType('Child', ['Child'], 'Hi') other.info.push('Temp') console.log(other.info) // ["Child", "Temp"] console.log(child.info) // ["Child"]
- 优点:在调用子类构造函数时,可以向父类构造函数传递参数
- 优点:子类实例可以访问父类的实例方法和原型方法
- 优点:每个子类实例的属性独立存在
- 缺点:在实现组合继承时,需要调用两次父类构造函数
2.1 原型式继承
方法:实现一个函数,传入已有对象,在函数内部将新对象的原型指向原有对象,最后返回新对象
原理:返回的新对象继承原有对象,然后根据需求对得到的对象加以修改即可
var superObject = { name: 'Super', info: ['Super'], getName: function() { return this.name } } // 原型式继承 function object(o) { function F() {} F.prototype = o return new F() } // 创建子类实例必须基于一个已有对象 var childObject = object(superObject) // 根据需求对得到的对象加以修改 childObject.message = 'Hello' // 新创建的实例可以访问已有对象的实例属性和实例方法 console.log(childObject.name) // Super console.log(childObject.getName()) // Super // 所有新创建的实例共享已有对象的引用属性 var otherObject = object(superObject) otherObject.info.push('Temp') console.log(otherObject.info) // ["Child", "Temp"] console.log(childObject.info) // ["Child", "Temp"]
- 要求:创建子类实例必须基于一个已有对象
- 缺点:所有新创建的实例都会重新定义已有对象的实例方法,因此无法做到函数复用
- 缺点:所有新创建的实例共享已有对象的引用属性
2.2 寄生式继承
方法:创建一个用于封装继承过程的函数,在函数内部以某种方式增强对象,且最后返回对象
原理:基于原型式继承,类似于工厂模式,将增强对象的过程封装到一个函数中
var superObject = { name: 'Super', info: ['Super'], getName: function() { return this.name } } // 寄生式继承 function object(o) { function F() {} F.prototype = o return new F() } function objectFactory(o) { var clone = object(o) // 创建对象 clone.message = 'Hello' // 增强对象 return clone // 返回对象 } // 创建子类实例必须基于一个已有对象 var childObject = objectFactory(superObject) // 新创建的实例可以访问已有对象的实例属性和实例方法 console.log(childObject.name) // Super console.log(childObject.getName()) // Super // 所有新创建的实例共享已有对象的引用属性 var otherObject = object(superObject) otherObject.info.push('Temp') console.log(otherObject.info) // ["Child", "Temp"] console.log(childObject.info) // ["Child", "Temp"]
- 要求:创建子类实例必须基于一个已有对象
- 缺点:所有新创建的实例都会重新定义已有对象的实例方法,因此无法做到函数复用
- 缺点:所有新创建的实例共享已有对象的引用属性
3 寄生式组合继承
方法:借用寄生式继承的思路,结合组合继承的方法,解决组合继承中需要调用两次父类构造函数的问题
原理:通过构造继承实现实例属性和实例方法的继承,通过寄生式继承实现原型属性和原型方法的继承
不用为了指定子类的原型而调用父类的构造函数,而是使用寄生式继承来继承父类的原型,然后指定给子类的原型
function SuperType(name, info) { // 实例属性(基本类型) this.name = name || 'Super' // 实例属性(引用类型) this.info = info || ['Super'] // 实例方法 this.getName = function() { return this.name } } // 原型方法 SuperType.prototype.getInfo = function() { return this.info } // 寄生式组合继承 function object(o) { function F() {} F.prototype = o return new F() } function objectFactory(childType, superType) { var prototype = object(superType.prototype) // 创建对象 prototype.constructor = childType // 增强对象 childType.prototype = prototype // 将父类原型指定给子类原型 } function ChildType(name, info, message) { SuperType.call(this, name, info) this.message = message } objectFactory(ChildType, SuperType)
寄生式组合继承是 JavaScript 中最常用的继承方式,ES6 中新增的 extends 底层也是基于寄生式组合继承的