js(ES5)中实现继承 | 青训营笔记

78 阅读3分钟

这是我参与「第四届青训营 」笔记创作活动的的第8天

继承:继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行效率的效果。基类 & 派生类

  • 每一个构造函数都有一个原型对象prototype,原型对象又包含指向构造函数的指针constructor
  • 构造函数prototype直接定义的属性,与constructor在一个层级
  • Parent.prototype === parent.__prototype构造函数的原型对象 === 实例对象的原型
  • 实例身上有构造函数内部定义的属性和方法,实例原型链上有构造函数prototype上定义的属性和方法

image.png

定义一个父类

function Parent(name) {
    this.name = name || "Parent";
    this.arr = [1, 2, 3];
    this.study = function() {
        console.log(`${this.name} is studying`);
    }
}
Parent.prototype.learn = function() {
    console.log(`${this.name} is learning javascript`)
}

console.log(new Parent())

  • 实例对象身上的属性和方法未 — 构造函数内部声明的属性和方法为
  • 实例对象原型链__proto__上的属性和方法为构造函数prototype上定义的属性和方法

image.png

一、原型链继承

过程分析

  • 子类(派生类)需要能够访问父类内部的成员变量
  • => 父类构造函数的实例对象身上有构造函数内部的成员变量
  • => 将子类prototype指向父类构造函数的实例
  • => 父类实例中的constructor属性指向父类构造函数
  • => 修改子类的constructor指向

代码实现

function Child(){}
Child.prototype.name = 'Child'

Child.prototype = new Parent();
Child.prototype.constructor = Child;

优点

  1. 方法复用,复用父类定义在原型上的方法,比如study()

缺点

  1. 创建子类实例的时候不能给父类传值【只能在指定子类prototype的时候给父类传值】
const child = new Child("child");
console.log(child.name) // "Parent"
  1. 共享父类引用属性【对于父类中引用类型的值,多个子类实例公用一个,一个实例修改,会连带修改其他实例的该属性值】
const child1 = new Child();
const child2 = new Child();
console.log(child1.arr === child2.arr)
child1.arr.push(4)
console.log(child2.arr)

image.png

  1. 不能实现多继承(多继承:可以继承多个类)【子类的prototype只能指向一个值】

二、构造函数继承

过程分析

  • 子类(派生类)需要能够访问父类内部的成员变量
  • => 利用call方法将父类的this绑定为子类的this
  • => 相当于复制父类的实例属性给子类(原型没复制)

代码实现

function Child(name){
    Parent.call(this, name);
}

const child = new Child("Tim")
child.study(); // "Tim is studying" 父类构造函数的属性和方法可以继承
child.learn(); // 报错,不会继承父类原型上的方法和属性

image.png

优点

  • 可以实现多继承,只要在子类构造函数中调用多个父类构造函数即可

缺点

  • 只能继承父类实例的属性和方法,不能继承原型上的属性和方法

三、组合继承

过程分析

原型链继承 + 构造函数继承

代码实现

function Child(name){
    Parent.call(this, name);
}

Child.prototype = new Parent();
Child.prototype.constructor = Child;

优点

  • 既可以继承父类内部的属性和方法,也可以继承原型链上的属性和方法
const child = new Child("Tim")
child.study();
child.learn();

image.png

缺点

  • 可以继承多个父类构造函数内部的属性和方法,但是只能继承一个父类原型链上的属性和方法
  • 调用了两次父类构造函数,生成了两份实例

四、原型式继承

针对某个对象进行的简单继承

  • 向函数中传入一个对象,然后返回一个以这个对象为原型的对象
  • => 实现Object.create() => Object.create(proto, propertiesObject)

五、寄生式继承

【有点含糊,勉强理解】

const person = {
    name:'Jack',
    friends:['John','Bob']
}

function createAnother(proto){
    let clone = Object.create(proto)
    //增强对象,添加属于自己的方法
    clone.sayHi = function(){
        console.log('hi')
    }
    return clone
}
const person1 = createAnother(person)
const person2 = createAnother(person)
person1.friends.push('Tom')
console.log(person2.friends) //  ['John', 'Bob', 'Tom']
person1.sayHi() // hi

多个子类共享的父类属性会相互影响

六、寄生组合继承

  • 通过构造一个没有实例属性的类,将该类的prototype指向父类的prototype
  • => 不会实例化两次父类构造函数内的属性和方法
function Child(name){
    Parent.call(this, name);
}

(function () {
    function Super() {}
    Super.prototype = Parent.prototype;
    Child.prototype = new Super();
})()