基于类的继承是大多数人所熟悉的,也是比较容易理解的。当咱们造成类型继承的思惟定势后,再次接触原型继承可能会以为有些奇怪并难以理解。你更可能会吐槽,原型继承根本就不能叫作继承,一点都不面向对象。本人最初也是这样认为的,但深刻仔细的对比后发现,二者其实并无本质的差异,只是表面有点不同而已。且看下面的分析。java
先看一个类型继承的例子,代码以下:浏览器
public class A { //... } public class B extends A { //... } public class C extends B { //... } C c = new C();
A、B、C为三个继承关系的类,最后将类C实例化。下面这张图描述了类和实例的对应关系。左边为类,右边为其对应实例。app
咱们看到,类C实例化后,内存中不只存在c对象,同时还有a、b两个对象。由于在java中,当咱们在执行new C()操做时,jvm中会发生以下过程:jvm
建立A的实例a。函数
建立B的实例b,并将实例b的super指针指向a。this
建立C的实例c,并将实例c的super指针指向b。spa
过程1和过程2对用户是透明的,不须要人工干预,引擎会按照“蓝图”把这两个过程完成。经过上图右半部分咱们能够看到,super指针将a、b、c三个实例串起来了,这里是实现继承的关键。当咱们在使用实例c的某个属性或方法时,若实例c中不存在则会沿着super指针向父类对象查找,直到找到,找不到则出错。这就是继承可以达到复用目的内部机制。看到这里你们或许已经联想到原型链了,super所串起来的这个链几乎和原型链同样,只是叫法不同而已。下面咱们就来看看原型继承。.net
上面是原型继承的示意图。先看图的右半部分,__proto__指针造成的对象链就是原型链。__proto__是一个私有属性,只能看不许访问(某些浏览器看也不给看)。__proto__的做用和前面的super是同样的,原型链实现复用的机制和类型继承也几乎是同样的,这里再也不重复。有一点不同就是原型继承中的属性写操做只会改变当前对象并不会影响原型链上的对象。prototype
如何去构造原型链呢?看上去要稍微麻烦一些。原型继承里面没有类的概念,咱们须要经过代码,手动完成这个过程。上图中的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里面实现继承的一个更简单有效的方法。
前面讨论了两种继承方式,能够看到,继承的本质其实就是对象的复用。本人以为原型继承更加的简单和明确,它直接就是从对象的角度考虑问题。固然,若是你须要一个很是强大的继承体系,你也能够构造出一个相似类型继承的模式。相对来讲,本人以为原型继承更灵活和自由些,也是很是巧妙和独特的。