今天看啥  ›  专栏  ›  清欢辞

JavaScrip继承方案

清欢辞  · 掘金  ·  · 2021-04-13 17:44
阅读 5

JavaScrip继承方案


JavaScrip继承方案

1、原型链继承

166c2c0107fd80c7.png 构造函数、原型和实例之间的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个原型对象的指针。

原型链继承核心: 将父类的实例作为子类的原型。

继承的本质就是复制,即重写原型对象,代之以一个新类型的实例,

function Foo(name){
    this.color = ['red','blue','black']
    this.name = name
};
Foo.prototype.sayName = function () {
     return ('my name is '+this.name)
}
function Car(age){
    this.age = age
}
Car.prototype = new Foo('jack')

a = new Car(16)
b = new Car()
b.color.push('white') // 在Car的实例b的color属性上添加了一个white
console.log(a.name,a.age)
console.log(a.color,a.sayName())
复制代码

20210413164918.png 我们在打印实例a的color属性意外的发现多出来一个白色,这是因为在原型链的继承中,引用类型会被所有的实例所共享,多个实例对引用类型的操作会被篡改。而不是单独属于自己的一份,

这也是原型链继承存在的问题:

问题1:原型中包含的引用类型属性将被所有实例共享;

问题2:子类在实例化的时候不能给父类构造函数传参;

为此我们想出来构造函数式继承方法

2、借用构造函数继承

基本思想:

借用构造函数的基本思想就是利用call或者apply把父类中通过this指定的属性和方法复制(借用)到子类创建的实例中。 因为this对象是在运行时基于函数的执行环境绑定的。也就是说,在全局中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象。 call、apply 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。

不知道this的指向的话可以仔细看看这篇关于this的完整讲解

This指向问题的超详解

function SuperType(){
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.getColor = function () {
    return  this.colors
}
function SubType(){
    // 继承了 SuperType
    SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
console.log(instance2.colors); //"red,blue,green"
instance2.getColor() // 报错  TypeError: instance2.getColor is not a function
复制代码

当我们想要使用父类原型上的方法时报错,发现顺着原型链上去查找,找不到getColor这个方法, 这是因为子类的实例不能继承原型属性/方法

核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

缺点: 方法都在构造函数中定义, 只能继承父类的实例属性和方法,不能继承原型属性/方法, 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

3、组合继承

原理:使用原型链实现对原型属性和方法的继承,借用构造函数实现对实例属性的继承。 结合原型继承和构造函数的继承方法,对之进行优化,达到即可继承原型的方法,引用类型也不会被所有实例所共享。


        function Person  (name) {
             this.name = name;
             this.friends = ['小李','小红'];
         };

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

        function Parent (age) {
            Person.call(this,'老明');  //第二次调用构造函数
            this.age = age;
        };

        Parent.prototype = new Person('老明');  //这一步也很关键
        var result = new Parent(24);   // 第一次调用构造函数
            console.log(result.name);    //老明
        result.friends.push("小智");  //
        console.log(result.friends);  //['小李','小红','小智']
        console.log(result.getName());  //老明
        console.log(result.age);    //24

        var result1 = new Parent(25);   //通过借用构造函数都有自己的属性,通过原型享用公共的方法
        console.log(result1.name);  //老明
        console.log(result1.friends);  //['小李','小红']
复制代码

优点:

结合原型链和借用构造函数继承两种方法,取长补短。实现了函数复用,又能够保证每个子类不会共享父类的引用类型属性。

缺点:

调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。

四. 寄生组合式继承

function inheritPrototype(subType, superType){
  var prototype = Object.create(superType.prototype,{}); // 创建对象,创建父类原型的一个副本
  prototype.constructor = subType;                    // 增强对象,弥补因重写原型而失去的默认的constructor 属性
  subType.prototype = prototype;                      // 指定对象,将新创建的对象赋值给子类的原型
}

// 父类初始化实例属性和原型属性
function SuperType(name){
  this.name = name;
  this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
  alert(this.name);
};

// 借用构造函数传递增强子类实例属性(支持传参和避免篡改)
function SubType(name, age){
  SuperType.call(this, name);
  this.age = age;
}

// 将父类原型指向子类
inheritPrototype(SubType, SuperType);

// 新增子类原型属性
SubType.prototype.sayAge = function(){
  alert(this.age);
}

var instance1 = new SubType("xyc", 23);
var instance2 = new SubType("lxy", 23);

instance1.colors.push("2"); // ["red", "blue", "green", "2"]
instance1.colors.push("3"); // ["red", "blue", "green", "3"]

复制代码

这个例子的高效率体现在它只调用了一次SuperType 构造函数,并且因此避免了在SubType.prototype 上创建不必要的、多余的属性。于此同时,原型链还能保持不变; 因此,还能够正常使用instanceof 和isPrototypeOf() 这是最成熟的方法,也是现在库实现的方法

五、class

在 es6 中,官方给出了 class 关键字来实现面向对象风格的写法,但本质上是寄生组合式继承的语法糖。

class Person {
    constructor(age) {
        this.age_ = age;
    }
    sayAge() {
        console.log(this.age_);
    }
    // 静态方法
    static create() {
        // 使用随机年龄创建并返回一个 Person 实例
        return new Person(Math.floor(Math.random()*100));
    }
}
// 继承普通构造函数
class Doctor extends Person {}

const doctor = new Doctor(32);
doctor.sayAge(); // 32
复制代码

总结

1.ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.apply(this)).

2.ES6的继承机制完全不同,实质上是先创建父类的实例对象this(所以必须先调用父类的super()方法),然后再用子类的构造函数修改this。

3.ES5的继承时通过原型或构造函数机制来实现。

4.ES6通过class关键字定义类,里面有构造方法,类之间通过extends关键字实现继承。

juejin.cn/post/684490…




原文地址:访问原文地址
快照地址: 访问文章快照