JS 中的继承

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

继承方式的分类

原型链继承

  • 重点:子的原型对象为new一个父的实例 Child.prototype = new Parent();
  • 缺点:多个实例对引用类型属性的操作会被篡改;在创建Child实例时,不能向 Parent 传参
function Animal() {}
function Dog(){}  
Dog.prototype = new Animal();

引用类型的属性被所有实例共享的栗子

function Parent () {
    this.names = ['kevin', 'daisy'];
}
function Child () {
}
Child.prototype = new Parent();

var child1 = new Child();
child1.names.push('yayu');
console.log(child1.names); // ["kevin", "daisy", "yayu"]

var child2 = new Child();
console.log(child2.names); // ["kevin", "daisy", "yayu"]

构造函数继承

  • 重点:在子构造函数内部调用父构造函数 Parent.call(this)
  • 缺点:方法都在构造函数中定义,每次创建实例都会创建一遍方法
function Cat(name) {            
    Animal.call(this, name);   // 核心,把父类的实例方法属性指向子类
}

组合继承(原型链继承和构造函数继承组合)

  • 重点:使用原型链继承共享的属性和方法,通过借用构造函数继承实例属性
  • 优点:
    1、创建子类实例,可以向父类构造函数传参数;
    2、父类的实例方法定义在父类的原型对象上,可以实现方法复用;
    3、不共享父类的构造方法及属性;
  • 缺点:无论在什么情况都会调用两次父构造函数,一次是创建子类型原型,另一次是在子构造函数内部
function Cat(name) {
    // 核心,把父类的实例方法属性指向子类。即,继承属性 。                 
    Animal.call(this, name);                          
}
// 核心, 父类的实例作为子类的原型对象,即继承方法
Cat.prototype = new Animal()
 // 修复子类Cat的构造器指向,防止原型链的混乱                                
Cat.prototype.constructor = Cat;                           

原型式继承

  • 重点:将源对象赋给目标对象的原型,原型式继承的object方法本质上是对参数对象的一个浅复制。
  • 优点:父类方法可以复用
  • 缺点:和原型链继承一样,父类的引用属性会被所有子类实例共享、子类构建实例时不能向父类传递参数。

实现方式 1:

    function object(o) {
        // 创建临时新的构造函数
        function F() { }
        // 将传入的这个对象作为这个构造函数的原型
        F.prototype = o;
        // 返回这个临时类型的一个新的实例
        return new F();
    }

    let person = {
        name: "可莉",
        friends: ["七七", "雷泽"]
    }

    let anotherPerson = object(person);
    anotherPerson.name = "七七";
    anotherPerson.friends.push("可莉");

    console.log(person);
    //{name: "可莉", friends: ["七七", "雷泽", "可莉"]}
    console.log(anotherPerson);
    //{name: "七七", __proto__: person}

实现方法 2:
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。规范了原型式继承。
方法接收两个参数,一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象,这第二个参数和 Object.defineProperties() 方法的第二个参数格式相同。

    let anotherPerson = Object.create(person, {
        name: {
            value: "琴团长"
        }
    });
    console.log(anotherPerson);
    //{name: "琴团长", __proto__: person}

寄生式继承

  • 重点:就在原型式继承的基础上,给浅复制对象增加方法,增强了这个浅复制对象的能力。
    function createOther(original) {
        let clone = Object.create(original);
        // 以某种方式增强这个对象
        clone.say = function() {
            console.log("sayHi");
        } 
        return clone;
    }

寄生组合继承

function Cat(name) {
     // 核心,把父类的实例方法属性指向子类;                     
    Animal.call(this, this.name);                         
}
// 核心,利用空对象作为中介;
var F = function(){};
 // 核心,将父类的原型赋值给空对象F;                                       
F.prototype = Animal.prototype; 
 // 核心,将F的实例赋值给子类;                           
Cat.prototype = new F(); 
// 修复子类Cat的构造器指向,防止原型链的混乱;                                 
Cat.prototype.constructor = Cat;                            
    function Father(name) {
        this.name = name;
    }

    Father.prototype.sayName = function() {
        console.log(this.name);
    }

    function inheritPrototype(f_constructor, c_constructor) {
        // 创建父类原型的浅复制,(原型式继承) 
        let prototype = Object.create(f_constructor.prototype);
        // 修正原型上构造函数的指向
        prototype.constructor = child;
        // 将子类的原型替换为这个原型
        c_constructor.prototype = prototype;
        
        
    }

    // 继承父类上的属性, (构造函数继承)
    function Child(name, age) {
        Father.call(this, name);
        this.age = age;
    }

    inheritPrototype(Father, Child);

ES6 class extends关键字继承

  • 重点:子类必须在 constructor 方法中调用 super 方法,其次就算你子类不写 constructor 方法,但是编译的时候还是会默认给你加上。
  • 和 ES5 其他各种思路的区别:
    其实上面各种形式,但凡只要使用到了构造函数的继承,都是子类的实例 (this) 调用父类的构造函数。但是 ES6 的 extends 的核心概念就是父类实例对象的属性和方法,加到this上面,然后再用子类的构造函数修改 this
class Animal {
    constructor(name) {
        this.name = name;
    }

    sayName() {
        console.log(this.name)
    }
}

class Dog extends Animal {
    constructor(name, age) {
        // 这个 super 就相当于调用父类的构造函数,调用完成之后就得到了与父类属性和方法
        // 否则如果不调用super子类就得不到this对象
        // 简而言之,调用super就能通过父类的构造函数创建子类的 this 对象
        super(name);
        this.age = age;
    }

    wangwang() {
        console.log("wangwang~~~~");
    }
}

const dog1 = new Dog("teddy", 18);

dog1.sayName();
dog1.wangwang();

你可能感兴趣的:(JS 中的继承)