JS-继承

25 阅读4分钟

1. 原型链继承

function Person(){
    this.name = 'Person'
}
Person.prototype.getName = function(){
    console.log(this.name)
}
function Child(){}

Child.prototype = new Person() // 子类的prototype指向父类的实例

let child1 = new Child()
child1.getName()
  • 重点:子类的prototype指向父类的实例,子类的实例的原型指向父类的实例。
  • 优点:实例可继承的属性有:(新实例不会继承父类实例的属性)
    • 实例的构造函数的属性
    • 父类构造函数的属性
    • 父类原型对象的属性
  • 缺点:
    • 新实例实例化时无法向父类构造函数传参
    • 继承单一(无法实现多继承)
    • 所有新实例会共享父类实例的属性

2. 构造函数继承(盗用构造函数 / 对象伪装 / 经典继承)

function Person(){
    this.name = 'xiaoming';
    this.colors = ['red', 'blue', 'green'];
}

Person.prototype.getName = function(){
    console.log(this.name);
}

function Child(age){
    // 子类中调用父类的构造函数,可以传参,使用call修改this
    Person.call(this);
    this.age = age
}

var child1 = new Child(23);
var child2 = new Child(12);
child1.colors.push('yellow');
console.log(child1.name); // xiaoming
console.log(child1.colors); // ["red", "blue", "green", "yellow"]
console.log(child2.colors); // ["red", "blue", "green"]
  • 重点:用call或apply将父类构造函数引入子类函数中(在子函数中做了父类函数的自执行)
  • 优点:
    • 继承了父类构造函数的属性
    • 解决了原型链继承缺点123
    • 可以继承多个构造函数多继承
    • 在子实例可以向父实例传参
  • 缺点:
    • 只能继承父类构造函数的属性,没有继承父类原型的属性(父类的prototype没有在子类实例的原型链上)
    • 无法实现构造函数的复用(每次用每次都要重新调用)
    • 每个新实例都有父类构造函数的副本(复制,臃肿)

3. 组合继承(原型链 + 构造函数)

function Parent(name){
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.getName = function(){
    console.log(this.name);
}

function Child(name,age){
    Parent.call(this,name);// 第二次调用 Parent(),构造函数继承
    this.age = age;
}

Child.prototype = new Parent(); // 第一次调用 Parent(),原型链继承

var child1 = new Child('xiaopao',18);
var child2 = new Child('lulu',19);
  • 重点:两种结合:引入构造函数 + 原型指向父类实例
  • 优点:
    • 可以继承父类原型上的属性,可以传参 / 复用
    • 每个新实例引入的构造函数属性是私有的
  • 缺点:
    • 调用了两次父类的构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数(重复了,子类实例屏蔽了父类实例的属性)

4. 原型式继承

其实就是用Object.create,过程:

  1. 创建一个函数
  2. 函数的prototype指向传入的对象
  3. 使用 new 创建实例,并return(示例的的 __proto__指向传入的对象)
function CreateObj(o){
    function F(){} // 1
    F.prototype = o; // 2
    console.log(o.__proto__ === Object.prototype);
    console.log(F.prototype.constructor === Object); // true
    return new F(); // 3
}

var person = {
    name: 'xiaopao',
    friend: ['daisy','kelly']
}

var person1 = CreateObj(person);

person1.name = 'person1';
person1.friend.push('taylor');
// console.log(person2.friend); // ["daisy", "kelly", "taylor"]"taylor"]
  • 重点:Object.create同原理。(一个CreateObj的函数,接收一个对象,内部声明一个构造函数,其原型对象指向所传入对象,最后返回使用new得到的内部构造函数的实例)
  • 优点:
    • 类似于复制一个对象,用函数来包装。
    • 不需要单独的构造函数
  • 缺点:
    • 所有实例都会继承原型上的属性
    • 无法实现复用(新实例属性都是后面添加的)

5. 寄生式继承

在原型式继承外再封装了一层用以修改实例的属性。

function createAnother(original){
    let clone = object(original)
    clone.sayhi = () => { //以某种方式增强示例
        console.log('hi')
    }
    return clone
}

6. 寄生组合式继承

  • 使用借用构造函数来继承父类中this声明的属性和方法
  • 使用寄生式继承来设置父类prototype为子类prototype的原型来继承父类的属性和方法
function SuperType(name){
    this.name = name;
    this.colors = [];
}

function SubType(name, age){
    SuperType.call(this,name); // 盗用继承函数
    this.age = age;
}

let prototype = Object.create(superType.prototype); // 原型式
SubType.prototype = prototype; // 寄生式
prototype.constructor = SubType;
  • 只调用一次SuperType
  • instanceof / isPrototypeOf 正常

7. 类的继承

ES6 的继承机制的实质是先将父类实例对象的属性和方法,加到this上面(所以先调用super方法),然后再用子类的构造函数修改this。

子类继承父类:class 子类 extends 父类;在子类的构造方法中调用父类的构造方法:super()。

// class继承
class Parent {
    constructor(name, gender) {
        this.name = name;
        this.gender = gender;
        this.greet = function() {
            console.log('greet');
        };
    }
    speak() {
        console.log("parent speak")
    }
​
    static speak() {
        console.log("static speak")
    }
}
​
//class 子类 extends 父类
class Son extends Parent {
    //在子类的构造方法中调用父类的构造方法
    constructor(name, gender, hobby) {
        super(name, gender);
        this.hobby = hobby;
    }
    //子类中声明的方法名和父类中的方法名相同时,子类中的方法将覆盖继承于父类的方法
    speak() {
        console.log("Son speak");
    }
}
const grandson = new Son('lucky', 'male', 'reading');
console.log(grandson.name, grandson.gender, grandson.hobby); //lucky male reading
grandson.greet(); //greet
grandson.speak(); //Son speak
Son.speak(); //static speak