阅读 162

JavaScript寄生继承的理解

看了冴羽大大的avaScript深入之继承的多种方式和优缺点,记录一些理解

继承到底是要干什么

js里面继承总给人怪怪的感觉,大概是因为js类的概念太奇怪了,和其他语言继承的观感上总有些不同

其他语言里,类继承是根本目的方法和属性的复用,那么js里也这么理解。

需要注意的是,原型链和继承其实不一样,尽管属性访问会往回查找原型链,但是继承需要对象的属性时这个实例独有的,冴羽大大提到说是委托,我觉得叫代理也ok

js的类是个伪概念,首先要确定的是,js类的本质就是一个函数,这个函数会构造一个Object。

那么我们要复用什么呢?首先是属性,子类的对象需要有父类的所有属性。那么就可以调用一下父类的构造函数的属性都初始化到子对象里面。其次是方法。如果是挂在this下面的,调用构造函数的时候就带上了,关键是要是在prototype里面的。既然属性访问会查找原型链,那就把父类的原型添加到子类的原型链中就行了。

我这里说得不够清楚,原型上挂在的包括属性和方法

从上面说的内容继承我们主要要干几件事:

  • 把父类的属性和方法想办法绑定到子类上
  • 把父类添加到子类的原型链上

从头看继承方法

原型链继承

直接把冴羽大大代码拿过来

function Parent () {
    this.name = 'kevin';
}

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

function Child () {

}

Child.prototype = new Parent();

var child1 = new Child();

console.log(child1.getName())
复制代码

因为Child.prototype是一个Parent对象,所以属性访问就会遍历到父对象,对父类原型方法的访问会遍历到Child.prototype.__proto__时找到,这就是Parent.prototype

但是这个方法中,属性其实并不是子对象自己持有的,严格来说不算继承,还是属于上面说的代理。

还有就是因为变量引用的关系,非基础类型所有子类都是一样的。根本原因就是上面说的,属性并不由对象自己持有。

构造函数继承

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

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

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"]
复制代码

这个办法的核心是用call函数让把子类的this拿给构造函数使用,让所有属性都直接挂在子类的对象中。这和python继承的方式是一样的,很好理解。

那么prototype中的方法能够被访问到吗?

Parent.prototype.t = function () {}
child1.t // undefined
复制代码

是不能的。原型链没有处理过,自然访问不到。所以这个方法的缺陷是很明显的。

组合继承

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);
    
    this.age = age;

}

Child.prototype = new Parent();
Child.prototype.constructor = Child;
复制代码

从上面两个可以发现,原型链继承做不到属性独立,经典继承做不到原型链处理,那融合起来就行。

用原型链继承的思路,把父类实例作为子类prototype

再用经典继承的思路,用call调用一次父类构造函数把属性创建在子对象上。

这个方法的问题是,父类的构造函数执行了两次,处理原型链一次,绑定属性一次。太浪费了,这就是寄生继承出现的原因。

寄生继承

我们再强调一遍继承要做的事:

  • 把父类的属性和方法想办法绑定到子类上
  • 把父类添加到子类的原型链上

实际上组合继承在每一件事上用了一次构造函数。那么我们可不可少一次呢?

绑定子类肯定是需要把子类的this给父类构造一遍的。这砍不掉,那就考虑原型链。

其实看原型链继承的方法,巧妙地利用了原型链的遍历规则,但其实并不是很雅观。直接把一个对象附加到原型上总有地方怪怪的,有种投机取巧的感觉。

那么有没有其他方式呢?就是通过中间量。所以就叫寄生了。

思路是这样的:

我们期望得到的结果是, 能让子类的原型间接和父类联系起来。

如果能有一个原型, prototype是父类的, 然后让子类的prototype指向这个原型,不就联系上了吗?

Obj.prototype = Parent.prototype
Child.prototype = new Obj()
复制代码

可以发现,prototype被添加到了原型链中,而且并没有调用构造函数

其实可以看到寄生继承的核心就是,构造一个instance,让instance的prototype代理父类的prototype。

看一看代码

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);
    this.age = age;
}

// 关键的三步
var F = function () {};

F.prototype = Parent.prototype;

Child.prototype = new F();
复制代码

有个问题:一定需要中间变量吗,直接把Chile的prototype指向Parent不可以吗?

Child.prototype = Parent.prototype
复制代码

其实这样做是能有效果的,但是两个prototype指向了同一个对象的引用,子类的独立性就没了。这就是为什么处理原型链时必须new一次。

关注下面的标签,发现更多相似文章
评论