资讯专栏INFORMATION COLUMN

继承的那些事

CoyPan / 1311人阅读

摘要:借用构造函数继承针对上面的继承方法的缺点,开发人员使用一种叫做借用构造函数的技术,也就是我们平时说的跟继承。

继承是 OO 语言中一个最为津津乐道的概念,许多 OO 语言都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。由于函数没有签名,在 ECMAScript 中无法实现接口继承。ECMAScript 只支持实现继承而且实现继承主要是依靠原型链来实现的。

关于原型链,我之前的文章里面有介绍,如果有些忘记了,可以看这篇文章。
下面我将详细的介绍前端前辈在开发过程中不断摸索创造的几种继承方式。看完面试的时候千万不要简单的回答 call 跟 apply 了。
为了说起来省事,虽然 js 没有严格意义的类,我还是以父类和子类来做区分继承关系。

1. prototype模式继承

既然子类想要继承父类的全部方法,而且我们知道父类的实例拥有父类所有的方法,那么接下类就好办了,我将子类的 prototype 指向父类的实例,子类就拥有了父类的全部方法了

// 定义父类
function Parent (name, age) {
    this.name = name;
    this.age = age;
}
Parent.prototype.sayName = function () {
    alert(this.name);
}
// 定义子类
function Child (sex) {
    this.sex = sex;
}
// 实现继承
var p = new Parent("leizore", 25);
Child.prototype = p;
var child = new Child("男");
child.sayName();            // leizore

那么对应的关系图如下:

这种方式 Child 继承了 Person 的全部方法,但是也是有缺点的。

创建子类实例时,无法向父类构造函数传参。指定 prototype 时,实例化 Person 传的参数,会出现在所有子类上,不灵活。

由图可以看到,p 的 contructor 指向 Person, 所以 Child.prototype.constructor 也指向 Person,显然会导致继承链的紊乱。

2.借用构造函数继承

针对上面的继承方法的缺点1,开发人员使用一种叫做借用构造函数的技术,也就是我们平时说的 call 跟 apply 继承。

// 定义父类
function Parent (name, age) {
    this.name = name;
    this.age = age;
}
Parent.prototype.sayName = function () {
    alert(this.name);
}
// 定义子类
function Child (name, age, sex) {
     // 继承,同时传递了参数
    Parent.call(this, name, age)
    this.sex = sex;
}

这里简单讲一下 call(apply)是如何实现的,其实就是将 call(apply) 前面的函数立即执行一遍,并且执行时将作用域 this 指向 call(apply) 函数的第一个参数,比如这里的 call 就是将 Parent 实例一遍,将 name 跟 age 当成参数传过去
这种继承方式解决了继承过程中的传参问题,但是缺点是并没有继承到父类的原型,为了解决这个问题,我们很容易想到将上面两个方法结合起来不久好了。于是另一种继承方式出现了

3.组合继承

没错,就是两种方式并用,从而发挥两者之长的一种继承模式,代码如下

// 定义父类
function Parent (name, age) {
    this.name = name;
    this.age = age;
}
Parent.prototype.sayName = function () {
    alert(this.name);
}
// 定义子类
function Child (name, age, sex) {
    // 继承,同时传递了参数
    Parent.call(this, name, age)
    this.sex = sex;
}
Child.prototype = new Parent("leizore", 25);

嗯,这种方式基本上解决了开发过程中继承的痛点,成为好多人常用的继承模式之一。但是缺点也是有的

重复定义了属性,可以看到将 Child 的 prototype指向 Perent 的实例时,继承了name 跟 age 属性,实例 Child 的时候,调用 call 函数,又继承了一次,虽然使用 call 调用这次的属性是在实例属性上,当获取name时优先返回实例属性,然后在 prototype 上,所以并不会出大问题。

第一种继承方式方式的缺点二也完美的继承过来了,Child.prototype.constructor 还是指向 parent

那么肯定有人会说,既然Child.prototype.constructor 不指向自己,那么直接让他指向自己不就好了?

Child.prototype.constructor = Child;

答案是不行的。因为 Child.prototype 是 Parent 的实例,这样操作会将 Parent.prototype.constructor 也指向 Child,显然也是不合理的。

4.原型式继承

为了解决上面 Child 与 Parent 继承之后纠缠不清的问题,道格拉斯在2006年提出一种继承方法,它的想法是借助原型可以给予已有的对象创建新对象,同时还不必因此创建自定义类型。函数如下

function object (o) {
    function F() {}
    F.prototype = o;
    return new F();
}

这个模式相当与创建一个新的对象,对象继承了o所有属性,当然这里也只是实现了浅拷贝。

5.组合寄生式继承

嗯,想必大家也想到了,上面这种继承方式可以解决 Child 与 Parent 继承后的纠缠不清的关系。可以由 object 方法创建一个临时对象,从而斩断跟 Parent 的联系。就可以放心的对 Child 原型的constructor 随便指了,当然了为了继承链的不紊乱,还是指向自己比较好

// 定义父类
function Parent (name, age) {
    this.name = name;
    this.age = age;
}
Parent.prototype.sayName = function () {
    alert(this.name);
}
// 定义子类
function Child (name, age, sex) {
    // 继承,同时传递了参数
    Parent.call(this, name, age)
    this.sex = sex;
}
function object (o) {
    function F() {}
    F.prototype = o;
    return new F();
}
var prototype = object(Parent.prototype);
prototype.constructor = Child;
Child.prototype = prototype;


var c = new Child("leizore", 11, "men");
c.sayName()                // leizore
c.constructor === Child    // true

到此,基本上解决了上面所说的所有缺点。当然了,也是有一点问题的,就是方法四的实现其实是浅拷贝,如果 Parent.prototype 里又引用类型比如数组,对象,改变Parent.prototype,Child 也会跟着变,解决方式也很简单,使用深拷贝就行了,同时又可以写很多继承方式。当然了,按照我上面顺下来的思想,也可以写出自己的继承方式
比如下面改变object函数:

// 定义父类
function Parent (name, age) {
    this.name = name;
    this.age = age;
}
Parent.prototype.sayName = function () {
    alert(this.name);
}
// 定义子类
function Child (name, age, sex) {
    // 继承,同时传递了参数
    Parent.call(this, name, age)
    this.sex = sex;
}
function object (o) {
    var c = {};
   for (var i in o) {
     c[i] = o[i];
   }
   return c
}
var prototype = object(Parent.prototype);
prototype.constructor = Child;
Child.prototype = prototype;


var c = new Child("leizore", 11, "men");
c.sayName()                // leizore
c.constructor === Child    // true

当然了,es6 中,可以通过extends关键字实现继承,这里就不多说了

参考

javascript 高级程序设计

Javascript面向对象编程(二):构造函数的继承

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/95660.html

相关文章

  • JavaScript 继承那些

    摘要:实际上也就是在原型链继承的代码中添加在子类的构造函数中调用父类构造函数。寄生组合式继承在指定子类的原型的时候不必调用父类的构造函数,而是直接使用创建父类原型的副本。 原本地址:http://www.ahonn.me/2017/01/2... 众所周知,JavaScript 的继承是实现继承,而没有 Java 中的接口继承。这是因为 JavaScript 中函数没有签名,而实现继承依靠的...

    singerye 评论0 收藏0
  • 【面试】Java基础那些-Two

    摘要:前言面试中对于技术职位,一般分笔试与面谈,如果面试官的一些小问题你可以立马找到对应的知识点扩展开来,那么这就是你的优势,本系列将讲述一些面试中的事,不会很详细,但是应该比较全面吧。 前言 面试中对于技术职位,一般分笔试与面谈,如果面试官的一些小问题你可以立马找到对应的知识点扩展开来,那么这就是你的优势,本系列将讲述一些java面试中的事,不会很详细,但是应该比较全面吧。 主要内容 pa...

    you_De 评论0 收藏0
  • js面向对象浅析--继承那些

    摘要:有需要还可以修改指向谦龙寄生组合式继承思路是通过借用构造函数来继承属性,通过原型链的混合形式来继承方法改变执行环境实现继承有需要还可以修改指向谦龙谦龙拷贝继承该方法思路是将另外一个对象的属性和方法拷贝至另一个对象使用递归 前言 js中实现继承的方式只支持实现继承,即继承实际的方法,而实现继承主要是依靠原型链来完成的。 原型链式继承 该方式实现的本质是重写原型对象,代之以一个新类型的实例...

    molyzzx 评论0 收藏0
  • 关于继承那些

    摘要:格式子类名父类名好处提高了代码的复用性提高了代码的维护性通过少量的修改,满足不断变化的具体要求让类与类产生了一个关系,是多态的前提要求有共同的属性或操作有细微的差别继承的弊端让类的耦合性增强。 showImg(https://segmentfault.com/img/remote/1460000019321816?w=600&h=242); 第二阶段 JAVA面向对象 第二章 继承 其...

    soasme 评论0 收藏0
  • 【JS基础】原型对象那些(一)

    摘要:通过同一个构造函数实例化的多个实例对象具有同一个原型对象。所以当给原型对象赋值一个新对象时,切记将原型对象的指回原构造函数以上就是本次分享的内容,关于原型对象的其他知识,下一篇基础原型对象的那些事二会讲到。 谈起js的基础,绕不过去的坎就是:原型链、作用域链、this(em...好吧,还有闭包),今天总结一下关于原型对象的一些知识,供自己和大家复习。 概念理解 什么是原型对象呢?有以下...

    edgardeng 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<