几种继承方式

  • 原型链
  • 盗用构造函数
  • 组合继承
  • 原型式继承
  • 寄生继承
  • 寄生式组合继承

原型链

原理:子类的__proto__指向父类的一个实例当作原型对象
缺陷
1. 原型对象上的引用值会在所有子实例间共享
2. 子类实例化时无法给父构造函数传参

1
2
3
4
function Father(){}
function Son(){}
Son.prototype = new Father(); //继承
const son = new Son();

盗用构造函数

原理: 在子类中调用父类构造函数
缺陷:无法继承到父类的方法

1
2
3
4
5
6
7
function Person(name) {
this.name = name;
}
function Student(name) {
Person.call(this, name)
}
const instance = new Student('son');

组合继承

原理: 原型链 + 盗用构造函数
要继承的属性用构造函数
要继承的方法写在原型上
缺陷:调用两次父类构造函数,导致原型对象和子实例上都有父类属性
优势:同时解决了继承引用值属性和方法的继承问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Father(name) {
this.name = name;
this.colors = ['a']
}
//父类方法
// Father.prototype.sayName = function() {
// console.log(this.name);
// }
function Son(name, age){
Father.call(this, name) //第二次调用父类构造函数 --继承属性
this.age = age;
}
Son.prototype = new Father() //第一次调用父类构造函数 --继承方法
Son.prototype.constructor = Son

let son = new Son('blank',14)
//可以发现实例和它的原型都有 name 和 colors, 这是调用俩次父类构造函数的结果
console.log(Object.getOwnPropertyNames(son)); //['name', 'colors', 'age']
console.log(Object.getOwnPropertyNames(Son.prototype)); //['name', 'colors', 'constructor']

这是最大缺陷:调用两次父类构造函数,导致子类实例和子类原型都有同名属性—— 寄生式组合继承解决了该问题

原型式继承

原理:利用Object.create 实现继承,无需定义构造函数
缺陷:同原型链继承,原型对象上的引用值会在所有子实例间共享

1
2
3
4
5
function object(o) {
function F() { }
F.prototype = o;
return new F(); //返回对象的原型是o
}

Object.create()

ES5实现的一个方法

Object.create(param1 [, param2])

  • param1: 和object方法一样,返回一个对象,该对象原型是param1
  • param2: 和defineProperties一样, 可选

寄生式继承

原理:和工厂函数类似,创建一个继承的函数,以某种方式增强对象再返回这个对象
缺陷:同原型式继承,原型对象上的引用值会在所有子实例间共享

1
2
3
4
5
6
7
function createAnother(original) {
let clone = Object.create(original) //原型式继承的做法
clone.sayHi = function() { //增强该对象
console.log('hi');
}
return clone
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let person = {
name: 'blank',
friends: ['a'] //和子对象共享地址!
}
let anotherPerson = createAnother(person)
let yetAnotherPerson = createAnother(person)

anotherPerson.name = 'another'
anotherPerson.friends.push('another')

yetAnotherPerson.name = 'yet'
yetAnotherPerson.friends.push('yet')

console.log(person); //{ name: 'blank', friends: [ 'a', 'another', 'yet' ] }

寄生式组合继承(最优)

最大特点是: 用一个函数实现两个类的继承关系

1
2
3
4
5
6
function inheritPrototype(Son, Father){
let prototype = Object.create(Father.prototype) //避免调用父构造函数
//下面两行是建立起构造函数和原型对象之间的双向箭头
prototype.constructor = Son
Son.prototype = prototype;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Father(name) {
//定义实例属性
this.name =name
this.colors = ['a']
}
function Son(name, age) {
Father.call(this, name) //调用一次父类构造函数
this.age = age
}
// 建立继承关系
inheritPrototype(Son, Father)
// 定义父类方法
Father.prototype.sayName = function() {
console.log(this.name);
}
// 定义子类方法 -- 必须在inheritPrototype后面
Son.prototype.sayAge = function() {
console.log(this.age);
}
  1. 只调用一次父类构造函数,属性不重复
  2. 引用值不共享
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 实例化
    let son = new Son('blank',22)
    //1. 只调用了一次父类构造函数, 所以子类原型上没有实例属性
    console.log(Object.getOwnPropertyNames(son)); //[ 'name', 'colors', 'age' ]
    console.log(Object.getOwnPropertyNames(son.__proto__)); //[ 'constructor', 'sayAge' ]
    console.log(Object.getOwnPropertyNames(Father.prototype)); //[ 'constructor', 'sayName' ]

    //2. 引用值不共享
    let son2 = new Son('lzy2', 23)
    son.colors.push('b')
    son2.colors.push('c')
    console.log(son.colors); // [ 'a', 'b' ]

如何理解寄生?
就是把继承这一操作用函数封装起来

ES6 类继承——extends

extends

可以继承任何拥有[[Constructor]]和原型的对象

  • 实例拥有constructor指向构造函数

super

  1. super只能在派生类的构造函数和静态方法中使用
    super()super.superStaticFun()

  2. 调用super() 会调用父类构造函数,并把返回的实例赋值给this

    所以必须使用this前调用super()

  3. 默认构造函数实例化时,会调用super()并传入所有参数

  4. 显式定义构造函数时,要么必须调用super(),要么手动返回一个对象

new.target

得到new 了一个什么东西