JS原型链

作者: 一直玩编程 | 来源:发表于2016-03-23 15:21 被阅读757次

    JS原型链

    这篇文章是「深入ECMA-262-3」系列的一个概览和摘要。每个部分都包含了对应章节的链接,所以你可以阅读它们以便对其有更深的理解。

    对象

    ECMAScript做为一个高度抽象的面向对象语言,是通过对象来交互的。即使ECMAScript里边也有基本类型,但是,当需要的时候,它们也会被转换成对象。

    一个对象就是一个属性集合,并拥有一个独立的prototype(原型)对象。这个prototype可以是一个对象或者null。*
    

    让我们看一个关于对象的基本例子。一个对象的prototype是以内部的[[Prototype]]属性来引用的。但是,在示意图里边我们将会使用__<internal-property>__下划线标记来替代两个括号,对于prototype对象来说是:__proto__\。
    对于以下代码:

    var foo = {
      x: 10,
      y: 20
    };
    

    我们拥有一个这样的结构,两个明显的自身属性和一个隐含的proto属性,这个属性是对foo原型对象的引用:

    basic-object.png

    这些prototype有什么用?让我们以原型链(prototype chain)的概念来回答这个问题。

    原型链

    原型对象也是简单的对象并且可以拥有它们自己的原型。如果一个原型对象的原型是一个非null的引用,那么以此类推,这就叫作原型链。

    原型链是一个用来实现继承和共享属性的有限对象链。
    

    考虑这么一个情况,我们拥有两个对象,它们之间只有一小部分不同,其他部分都相同。显然,对于一个设计良好的系统,我们将会重用相似的功能/代码,而不是在每个单独的对象中重复它。在基于类的系统中,这个代码重用风格叫作类继承-你把相似的功能放入类A中,然后类B和类C继承类A,并且拥有它们自己的一些小的额外变动。

    ECMAScript中没有类的概念。但是,代码重用的风格并没有太多不同(尽管从某些方面来说比基于类(class-based)的方式要更加灵活)并且通过原型链来实现。这种继承方式叫作委托继承(delegation based inheritance)(或者,更贴近ECMAScript一些,叫作原型继承(prototype based inheritance))。

    跟例子中的类ABC相似,在ECMAScript中你创建对象:a,b,c。于是,对象a中存储对象b和c中通用的部分。然后b和c只存储它们自身的额外属性或者方法。

    var a = {
          x: 10,
        calculate: function (z) {
            return this.x + this.y + z
          }
    };
    
    var b = {
      y: 20,
      __proto__: a
    };
    
    var c = {
      y: 30,
      __proto__: a
    };
    
    // call the inherited method
    b.calculate(30); // 60
    c.calculate(40); // 80
    

    足够简单,是不是?我们看到bc访问到了在对象a中定义的calculate方法。这是通过原型链实现的。
    规则很简单:如果一个属性或者一个方法在对象自身中无法找到(也就是对象自身没有一个那样的属性),然后它会尝试在原型链中寻找这个属性/方法。如果这个属性在原型中没有查找到,那么将会查找这个原型的原型,以此类推,遍历整个原型链(当然这在类继承中也是一样的,当解析一个继承的方法的时候-我们遍历class链( class chain))。第一个被查找到的同名属性/方法会被使用。因此,一个被查找到的属性叫作继承属性。如果在遍历了整个原型链之后还是没有查找到这个属性的话,返回undefined值。

    注意,继承方法中所使用的this的值被设置为原始对象,而并不是在其中查找到这个方法的(原型)对象。也就是,在上面的例子中this.y取的是b和c中的值,而不是a中的值。但是,this.x是取的是a中的值,并且又一次通过原型链机制完成。

    如果没有明确为一个对象指定原型,那么它将会使用__proto__的默认值-Object.prototype。Object.prototype对象自身也有一个__proto__属性,这是原型链的终点并且值为null。

    下一张图展示了对象a,b,c之间的继承层级:

    prototype-chain.png

    注意: ES5标准化了一个实现原型继承的可选方法,即使用 Object.create 函数:

    var b = Object.create(a, {y: {value: 20}});
    var c = Object.create(a, {y: {value: 30}});
    

    你可以在对应的章节获取到更多关于ES5新API的信息。 ES6标准化了 __proto__属性,并且可以在对象初始化的时候使用它。

    通常情况下需要对象拥有相同或者相似的状态结构(也就是相同的属性集合),赋以不同的状态值。在这个情况下我们可能需要使用构造函数(constructor function),其以指定的模式来创造对象。

    构造函数

    除了以指定模式创建对象之外,构造函数也做了另一个有用的事情-它自动地为新创建的对象设置一个原型对象。这个原型对象存储在ConstructorFunction.prototype 属性中。

    换句话说,我们可以使用构造函数来重写上一个拥有对象b和对象c的例子。因此,对象a(一个原型对象)的角色由Foo.prototype来扮演:

    // a constructor function
    function Foo(y) {
      // which may create objects
      // by specified pattern: they have after
      // creation own "y" property
      this.y = y;
    }
    
    // also "Foo.prototype" stores reference
    // to the prototype of newly created objects,
    // so we may use it to define shared/inherited
    // properties or methods, so the same as in
    // previous example we have:
    
    // inherited property "x"
    Foo.prototype.x = 10;
    
    // and inherited method "calculate"
    Foo.prototype.calculate = function (z) {
      return this.x + this.y + z;
    };
    
    // now create our "b" and "c"
    // objects using "pattern" Foo
    var b = new Foo(20);
    var c = new Foo(30);
    
    // call the inherited method
    b.calculate(30); // 60
    c.calculate(40); // 80
    
    // let's show that we reference
    // properties we expect
    
    console.log(
    
      b.__proto__ === Foo.prototype, // true
      c.__proto__ === Foo.prototype, // true
    
      // also "Foo.prototype" automatically creates
      // a special property "constructor", which is a
      // reference to the constructor function itself;
      // instances "b" and "c" may found it via
      // delegation and use to check their constructor
    
      b.constructor === Foo, // true
      c.constructor === Foo, // true
      Foo.prototype.constructor === Foo // true
    
      b.calculate === b.__proto__.calculate, // true
      b.__proto__.calculate === Foo.prototype.calculate // true
    
    );
    

    这个代码可以表示为如下关系:

    constructor-proto-chain.png
    这张图又一次说明了每个对象都有一个原型。构造函数Foo也有自己的__proto__,值为Function.prototypeFunction.prototype也通过其__proto__属性关联到Object.prototype。因此,重申一下,Foo.prototype就是Foo的一个明确的属性,指向对象b和对象c的原型。

    正式来说,如果思考一下分类的概念(并且我们已经对Foo进行了分类),那么构造函数和原型对象合在一起可以叫作「类」。实际上,举个例子,Python的第一级(first-class)动态类(dynamic classes)显然是以同样的属性/方法处理方案来实现的。从这个角度来说,Python中的类就是ECMAScript使用的委托继承的一个语法糖。

    注意: 在ES6中「类」的概念被标准化了,并且实际上以一种构建在构造函数上面的语法糖来实现,就像上面描述的一样。从这个角度来看原型链成为了类继承的一种具体实现方式:
    // ES6
    class Foo {
    constructor(name) {
    this._name = name;
    }

      getName() {
        return this._name;
      }
    }
    
    class Bar extends Foo {
      getName() {
        return super.getName() + ' Doe';
      }
    }
    
    var bar = new Bar('John');
    console.log(bar.getName()); // John Doe
    

    转自JavaScript. The core.

    全面的课程内容,只为打造企业最需要的WEB前端工程师, 最新前端视频分享,请关注微信公众号“一起玩前端”或扫描二维码关注。
    qrcode_for_gh_7a765c30aeb7_258.jpg
    回复以下内容,即可获取对应最新视频。
    • 001------ HTML
    • 002------ CSS
    • 003------ DIV+CSS
    • 004------ HTML5
    • 005------ CSS3
    • 006------ Web响应式布局
    • 007------JavaScript视频教程

    相关文章

      网友评论

      本文标题:JS原型链

      本文链接:https://www.haomeiwen.com/subject/kdnllttx.html