JavaScript学习笔记(十四) 继承

这篇文章将会介绍在 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 底层也是基于寄生式组合继承的

【 阅读更多 JavaScript 系列文章,请看 JavaScript学习笔记

posted @ 2020-02-27 22:49  半虹  阅读(100)  评论(0编辑  收藏  举报