乐趣区

关于javascript:js实现继承的几种方式

js 继承

  1. 原型链
  2. 构造函数
  3. 组合继承(原型链 + 构造函数)
  4. 原型式继承
  5. 寄生式继承
  6. 寄生组合继承

1. 原型链继承

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

// 父类
function School(name) {
    // 实例属性
    this.name = name || "父类"
    this.arr = [1]
}
// 父类原型办法
School.prototype = {
    constructor: School,
    showName() {console.log(this.name);
    },
}
// 子类
function Student(gender) {this.gender = gender}
Student.prototype = new School("子类")
Student.prototype.constructor = Student
// 子类的实例
let std1 = new Student()
let std2 = new Student()

要害

Student.prototype = new School()
  • 将父类的实例作为子类的原型
  • new School()失去一个实例对象 o,它的构造函数是 School,o 上没有 constructor 属性,但会继承 School 的 prototype 上的 constructor
  • 给 o 追加一个属性 constructor 指向 Student
  • 相当于通过 o 的继承实现的间接的继承,也就是原型链继承

长处

  • 共享了父类原型上的办法

    std1.showName() // 子类
    std1.showName === std2.showName //true

毛病

  • 不能向父类传参 – 在实例化子类时想让 std1 带上 name 不能实现,只能手动追加
  • 子类实例共享了父类构造函数的援用属性

    std1.arr.push(2)
    console.log(std2.arr);//[1,2]

2. 构造函数

将父类的构造函数在子类的构造函数中执行,扭转 this 指向

function School(name) {
    this.name = name || "父类"
    this.arr = [1]
    this.showName = function () {console.log(this.name);
    }
}

function Student(name,gender) {School.call(this,name)
    this.gender = gender
}

let std1 = new Student("张三")
let std2 = new Student("李四")

要害

School.call(this,name)
  • 将 School 中的 this 改为 Student 中的 this, 将 name 参数传入 School 返回
  • 相当于拷贝了父类构造函数中的属性与办法
  • 在实例化子类时都会这样拷贝一次

长处

  • 能够向父类构造函数传参 – 通过 call
  • 不共享父类的援用属性

    std1.arr.push(2)
    console.log(std2.arr);//[1]

毛病

  • 办法不能复用

    std1.showName === std2.showName //false
  • 不能继承父类原型上的办法 – 只是对父类构造函数内属性与办法的拷贝

3. 组合继承

联合原型链和构造函数

function School(name) {
    this.name = name || "父类"
    this.arr = [1]
    
}
School.prototype = {
    constructor:School,
    showName() {console.log(this.name);
    }

}
function Student(name, gender) {School.call(this, name)
    this.gender = gender
}
Student.prototype = new School()
Student.prototype.constructor = Student

let std1 = new Student("张三")
let std2 = new Student("李四")
  • 蕴含了 1 和 2 的要害语句

长处

  • 能够向父类传参数
  • 共享复用父类原型上的办法
  • 不共享父类构造函数上的援用属性

毛病

  • 两个要害语句调用了两次父类构造函数,生成两个实例正本,影响性能

优化 1

Student.prototype = new School()
替换为
Student.prototype = School.prototype
  • 这种优化解决了两次调用,然而在批改 constructor 时,父类的也会扭转

优化 2(完满)

Student.prototype = new School()
替换为
Student.prototype = Object.create(School.prototype)
  • Object.create(proto), 创立一个以 proto 为原型的对象,未调用 父类构造函数并且失去一个能继承父类原型的 对象

    Student.prototype.__proto__ === School.prototype//true
  • 批改子类的 constructor 不会影响到父类, 相似与 1 中提到了 o 对象,对其追加 constructor 即可
let std1 = new Student("张三")
let sch1 = new School("理工")
std1.constructor === Student //true
sch1.constructor === School  //true

4. 原型式继承

2006 年 Douglas Crockford 介绍的一种不波及严格意义上构造函数的继承办法
不自定义类型也能够通过原型实现对象之间的信息共享

let School = {
    name: "理工",
    arr: [1],
    showName() {console.log(this.name);
    }
}
function creat(o) {function F() { }
    F.prototype = o
    return new F()}
let sch1 = creat(School)
sch1.name = "工业"
sch1.showName() // 工业
sch1.arr.push(2)
let sch2 = creat(School)
console.log(sch2.arr);//[1,2]
sch1.showName === sch2.showName //true
  • 3 中提到的 Object.create()的原理和 create 函数一样
  • 这种形式的优缺点与原型链继承形式相似

5. 寄生式继承

let School = {
    name: "理工",
    arr: [1],
    showName() {console.log(this.name);
    }
}
function create(o) {function F() { }
    F.prototype = o
    return new F()}
function enhance(obj) {let clone = create(obj)
    clone.showAge = function () {console.log(this.age);
    }
    return clone
}
let sch1 = enhance(School)
let sch2 = enhance(School)

sch1.showName === sch2.showName;//true
sch1.showAge === sch2.showAge;//false
  • 调用函数创建对象,再加强新对象,最初返回新对象
  • 然而办法不能复用

6. 寄生组合式继承

加强的形式与寄生式不同

function enhance(Student,School) {let clone = create(School.prtotype)
    clone.costructor = Student
    Student.prototye = clone
}
  • 其实这种形式就是 3 中的优化 2,create 就相当于 Object.create()

ES6 中 class 的继承形式在这里不再介绍,后续在介绍 class 时作为其中的一部分剖析

退出移动版