摘要:上图中的在原型继承称作构造器。构造器就是一个普通的函数,但是将操作符用到构造器上时,它会执行一个叫的过程。从第条可以看到,构造器生成的对象的属性会指向构造器的值,这就是我们构造原型链的关键。
基于类的继承是大多数人所熟悉的,也是比较容易理解的。当我们形成类型继承的思维定势后,再次接触原型继承可能会觉得有些奇怪并难以理解。你更可能会吐槽,原型继承根本就不能叫做继承,一点都不面向对象。本人最初也是这样认为的,但深入仔细的对比后发现,两者其实并没有本质的差别,只是表面有点不一样而已。且看下面的分析。
类型继承先看一个类型继承的例子,代码如下:
public class A { //... } public class B extends A { //... } public class C extends B { //... } C c = new C();
A、B、C为三个继承关系的类,最后将类C实例化。下面这张图描述了类和实例的对应关系。左边为类,右边为其对应实例。
我们看到,类C实例化后,内存中不仅存在c对象,同时还有a、b两个对象。因为在java中,当我们在执行new C()操作时,jvm中会发生如下过程:
创建A的实例a。
创建B的实例b,并将实例b的super指针指向a。
创建C的实例c,并将实例c的super指针指向b。
过程1和过程2对用户是透明的,不需要人工干预,引擎会按照“蓝图”把这两个过程完成。通过上图右半部分我们可以看到,super指针将a、b、c三个实例串起来了,这里是实现继承的关键。当我们在使用实例c的某个属性或方法时,若实例c中不存在则会沿着super指针向父类对象查找,直到找到,找不到则出错。这就是继承能够达到复用目的内部机制。看到这里大家或许已经联想到原型链了,super所串起来的这个链几乎和原型链一样,只是叫法不一样而已。下面我们就来看看原型继承。
原型继承上面是原型继承的示意图。先看图的右半部分,__proto__指针形成的对象链就是原型链。__proto__是一个私有属性,只能看不准访问(某些浏览器看也不给看)。__proto__的作用和前面的super是一样的,原型链实现复用的机制和类型继承也几乎是一样的,这里不再重复。有一点不一样就是原型继承中的属性写操作只会改变当前对象并不会影响原型链上的对象。
如何去构造原型链呢?看上去要稍微麻烦一些。原型继承里面没有类的概念,我们需要通过代码,手动完成这个过程。上图中的A、B、C在原型继承称作构造器。构造器就是一个普通的函数,但是将new操作符用到构造器上时,它会执行一个叫[[construct]]的过程。大致如下:
创建一个空对象obj。
设置obj的内部属性[[Class]]为Object。
设置obj的内部属性[[Extensible]]为true。
设置obj的[[__proto__]]属性:如果函数对象prototype的值为对象则直接赋给obj,否则赋予Object的prototype值。
调用函数对象的[[Call]]方法并将结果赋给result。
如果result为对象则返回result,否则返回obj。
从第4条可以看到,构造器生成的对象的__proto__属性会指向构造器的prototype值,这就是我们构造原型链的关键。下面的代码是上图原型链的构造过程。
function A(){ //... } function B(){ //... } function C(){ //... } var a = new A(); B.prototype = a; var b = new B(); C.prototype = b; var c = new C();
上述代码虽然能达到目的,但有点繁琐,我们可以将这个过程封装一下。backbone的实现是这样的:
var extend = function(protoProps, staticProps) { var parent = this; var child; if (protoProps && _.has(protoProps, "constructor")) { child = protoProps.constructor; } else { child = function(){ return parent.apply(this, arguments); }; } _.extend(child, parent, staticProps); child.prototype = _.create(parent.prototype, protoProps); child.prototype.constructor = child; child.__super__ = parent.prototype; return child; }
其中_.extend(child, parent, staticProps)是将staticProps和parent对象的属性复制给child。_.create方法的实现大概如下。
_.create = function(prototype, protoProps){ var F = function(){}; F.prototype = prototype; var result = new F(); return _.extend(result, protoProps); }
有了extend方法,我们的代码就可以写成:
A.extend = extend; var B = A.extend({ //... ); var C = B.extend({ //... ); var c = new C();
这段代码和类型继承的代码十分相似,通过原型继承我们也可以达到类型继承的效果。但是通过前面的比较我们发现,继承的本质就其实就是对象的复用。原型继承本身就是以对象为出发点考虑的,所以大多时候我们并不一定要按照类型继承的思维考虑问题。而且js是弱类型,对象的操作也极其自由,上述的_.create方法可能是js里面实现继承的一个更简单有效的方法。
总结前面讨论了两种继承方式,可以看到,继承的本质其实就是对象的复用。本人觉得原型继承更加的简单和明确,它直接就是从对象的角度考虑问题。当然,如果你需要一个非常强大的继承体系,你也可以构造出一个类似类型继承的模式。相对来说,本人觉得原型继承更灵活和自由些,也是非常巧妙和独特的。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/64688.html
摘要:上图中的在原型继承称作构造器。构造器就是一个普通的函数,但是将操作符用到构造器上时,它会执行一个叫的过程。从第条可以看到,构造器生成的对象的属性会指向构造器的值,这就是我们构造原型链的关键。 基于类的继承是大多数人所熟悉的,也是比较容易理解的。当我们形成类型继承的思维定势后,再次接触原型继承可能会觉得有些奇怪并难以理解。你更可能会吐槽,原型继承根本就不能叫做继承,一点都不面向对象。本人...
摘要:通常有这两种继承方式接口继承和实现继承。理解继承的工作是通过调用函数实现的,所以是寄生,将继承工作寄托给别人做,自己只是做增强工作。适用基于某个对象或某些信息来创建对象,而不考虑自定义类型和构造函数。 一、继承的概念 继承,是面向对象语言的一个重要概念。通常有这两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。 《JS高程》里提到:由于函数没有签名,...
摘要:如下所示在规范中,已经正式把属性添加到规范中也可以通过设置和获取对象的原型对象对象之间的关系可以用下图来表示但规范主要介绍了如何利用构造函数去构建原型关系。 前言 在软件工程中,代码重用的模式极为重要,因为他们可以显著地减少软件开发的成本。在那些主流的基于类的语言(比如Java,C++)中都是通过继承(extend)来实现代码复用,同时类继承引入了一套类型规范。而JavaScript是...
摘要:深入之继承的多种方式和优缺点深入系列第十五篇,讲解各种继承方式和优缺点。对于解释型语言例如来说,通过词法分析语法分析语法树,就可以开始解释执行了。 JavaScript深入之继承的多种方式和优缺点 JavaScript深入系列第十五篇,讲解JavaScript各种继承方式和优缺点。 写在前面 本文讲解JavaScript各种继承方式和优缺点。 但是注意: 这篇文章更像是笔记,哎,再让我...
摘要:在规范中,引入了的概念。使用中的声明一个类,是非常简单的事。中面向对象实例化的背后原理,实际上就是原型对象。与区别理解上述原理后,还需要注意与属性的区别。实际上,在中,类继承的本质依旧是原型对象。 在 ES6 规范中,引入了 class 的概念。使得 JS 开发者终于告别了,直接使用原型对象模仿面向对象中的类和类继承时代。 但是JS 中并没有一个真正的 class 原始类型, clas...
阅读 2613·2023-04-25 15:22
阅读 2797·2021-10-11 10:58
阅读 959·2021-08-30 09:48
阅读 1777·2019-08-30 15:56
阅读 1697·2019-08-30 15:53
阅读 1046·2019-08-29 11:16
阅读 1021·2019-08-23 18:34
阅读 1603·2019-08-23 18:12