JavaScript常用继承方案,看完不再emo


前段时间写了一篇关于原型的博客,好似打通了任督二脉,以前斩不断理还乱的各种继承方式突然看得明白了,赶紧记下来✍~~~

—— 以下是正文 ——

原型链继承

核心

将父类的实例作为子类的原型

// 创建SuperType的实例,并将该实例赋值给SubType.prototype
SubType.prototype = new SuperType() 
// 所有涉及到原型链继承的继承方式都要修改子类构造函数的指向,否则子类实例的构造函数会指向SuperType。
SubType.prototype.constructor = SubType

举个例子

// 父类
function Person (name, age) {
    this.name = name || 'Person'
    this.age = age || 0
    this.hobbies = ['music', 'reading']
}

// 为父类新增一个方法
Person.prototype.say = function () {
    console.log('I am a person')
}

// 子类
function Student (name) {
    this.name = name
    this.score = 90
}

// 继承(注意,继承必须要写在子类方法定义的前面)
Student.prototype = new Person()
Student.prototype.constructor = Student

// 为子类新增一个方法(在继承之后,否则会被覆盖)
Student.prototype.study = function () {
    console.log('I am studing')
}

var student = new Student('April')
console.log(student.name)           // April            --子类覆盖父类的属性
console.log(student.age)            // 0                --父类的属性
console.log(student.score)          // 90               --子类自己的属性
student.say()                       // I am a person    --继承自父类的方法
student.study()                     // I am studing     --子类自己的方法

存在的缺点

多个实例对引用类型的操作会被篡改

var stu1 = new Student()
var stu2 = new Student()
stu1.hobbies.push('basketball')
console.log(stu1.hobbies)   // music,reading,basketball
console.log(stu2.hobbies)   // music,reading,basketball

构造函数继承

核心

将父类构造函数的内容复制给了子类的构造函数

SuperType.call(SubType)

举个例子

// 父类
function Person (name, age) {
    this.hobbies = ['music', 'reading']
}
Person.prototype.say = function() {console.log('I am a person')}
// 子类
function Student(){
    Person.call(this)
}
var stu1 = new Student()
var stu2 = new Student()
stu1.hobbies.push('basketball')
console.log(stu1.hobbies)   // music,reading,basketball
console.log(stu2.hobbies)   // music,reading
stu1.say()                  // 报错

存在的缺点

  • 只能继承父类的实例属性和方法,不能继承原型属性/方法
  • 无法实现复用,每个子类都有父类实例函数的副本,影响性能

组合继承

核心

用原型链实现对原型属性和方法的继承,用借用构造函数技术来实现实例属性的继承

function SubType(name, age){
  SuperType.call(this)
}
SubType.prototype = new SuperType()
SubType.prototype.constructor = SubType

举个例子

// 父类
function Person() {
  this.hobbies = ['music','reading']
}

// 父类函数
Person.prototype.say = function() {console.log('I am a person')}

// 子类
function Student(){
    Person.call(this)               // 构造函数继承(继承属性)
}

// 原型链继承(继承方法)
Student.prototype = new Person()
Student.prototype.constructor = Student

// 实例化
var stu1 = new Student()
var stu2 = new Student()
stu1.hobbies.push('basketball')
console.log(stu1.hobbies)           // music,reading,basketball
console.log(stu2.hobbies)           // music,reading
stu1.say()                          // I am a person

存在的缺点

在使用子类创建实例对象时,其原型中会存在两份相同的属性/方法

原型式继承

核心

利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型

function object(obj){
  function F(){}
  F.prototype = obj
  return new F()
}

或者用 ES 5Object.create() 代替上面的 object 方法

举个例子

function object(obj){
  function F(){}
  F.prototype = obj
  return new F()
}
var person = {
  name: "Nicholas",
  friends: ["Shelby", "Court", "Van"]
}

var anotherPerson = object(person)
anotherPerson.name = "Greg"
anotherPerson.friends.push("Rob")

var yetAnotherPerson = object(person)
yetAnotherPerson.name = "Linda"
yetAnotherPerson.friends.push("Barbie")

console.log(person.friends)   // Shelby,Court,Van,Rob,Barbie

存在的缺点

  • 原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。

  • 无法传递参数

寄生式继承

核心

寄生式继承其实就是在原型式继承的基础上,做了一些增强

function createAnother(original){
  // 通过调用 object() 函数创建一个新对象
  var clone = object(original) 
  // 以某种方式来增强对象
  clone.sayHi = function(){  
    console.log("hi")
  }
  // 返回这个对象
  return clone // 返回这个对象
}

举个例子

function createAnother(original){
  var clone = object(original)
  clone.sayHi = function(){  
    console.log("hi")
  }
  return clone
}
var person = {
  name: "Nicholas",
  friends: ["Shelby", "Court", "Van"]
}
var anotherPerson = createAnother(person)
anotherPerson.sayHi() // hi

存在的缺点

  • 原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。

  • 无法传递参数

寄生组合式继承

核心

结合借用构造函数传递参数和寄生模式实现继承

function inheritPrototype(subType, superType){
  var prototype = Object.create(superType.prototype) // 创建对象,创建父类原型的一个副本
  prototype.constructor = subType                    // 增强对象,弥补因重写原型而失去的默认的constructor 属性
  subType.prototype = prototype                      // 指定对象,将新创建的对象赋值给子类的原型
}

举个例子

function inheritPrototype(subType, superType){
  var prototype = Object.create(superType.prototype)
  prototype.constructor = subType 
  subType.prototype = prototype
}
// 父类初始化实例属性和原型属性
function Person (name, age) {
    this.name = name || 'Person'
    this.age = age || 0
    this.hobbies = ['music', 'reading']
}
Person.prototype.say = function () {
    console.log('I am a person')
}

// 借用构造函数传递增强子类实例属性(支持传参和避免篡改)
function Student(name){
  Person.call(this, name)
}

// 将父类原型指向子类
inheritPrototype(Student, Person)

// 新增子类原型属性
Student.prototype.study = function () {
    console.log('I am studing')
}

var stu1 = new Student("stu1")
var stu2 = new Student("stu2")
stu1.hobbies.push('basketball')
stu2.hobbies.push('swimming')
console.log(stu1.hobbies)   // music,reading,basketball
console.log(stu2.hobbies)   // music,reading,swimming

这个继承方案是目前最成熟的方案

ES6类继承extends

核心

ES6 类的定义

class SubType extends SuperType {
    constructor() {
        super()
    }
}

举个例子

class Person {
    constructor(name, age) {
        this.name = name || 'Person'
        this.age = age || 0
        this.hobbies = ['music', 'reading']
    }
    say() {
        console.log('I am a person')
    }
}
class Student extends Person {
    constructor(name) {
        super(name)
        this.name = name
        this.score = 90
    }
    study() {
        console.log('I am studing')
    }
}
var stu1 = new Student("stu1")
var stu2 = new Student("stu2")
stu1.hobbies.push('basketball')
stu2.hobbies.push('swimming')
console.log(stu1.hobbies)   // music,reading,basketball
console.log(stu2.hobbies)   // music,reading,swimming

参考文章:

JavaScript常用八种继承方案

一篇文章理解JS继承——原型链/构造函数/组合/原型式/寄生式/寄生组合/Class extends

JS中的继承(上)

JS中的继承(下)


文章作者: April-cl
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 April-cl !
  目录