原型对象的constructor

function Super(){
    this.name = "this is super";
}

Super.prototype = {
    say:function(){
        console.log(this.name);
    }
}

function Sub(){
    this.name = "this is sub";
}

Sub.prototype = new Super();

var o = new Sub();
o.say(); //this is sub

实例 o 的原型对象的constructor是Super,那么 this.name 应该是Super的构造函数。但是输出是 this is sub

阅读 3.5k
5 个回答

首先纠正题主理解上的错误:
对象o的constructor是Sub而不是Super。对象o的指向是Sub,然后Sub继承自Supervar o = new Sub()执行的是构造函数Sub,而Sub中又执行了this.name = "this is sub";

下面我们来深入分析一下题主代码的原理以及通过prototype继承发生的事情。别的不多说,我们来run一遍代码:

这是题主的源代码:

function Super(){
    this.name = "this is super";
}

Super.prototype = {
    say:function(){
        console.log(this.name);
    }
}

function Sub(){
    this.name = "this is sub";
}

Sub.prototype = new Super();

var o = new Sub();
o.say(); //this is sub

我们来看执行,前面的一堆声明我们先不看。

 

第一步,原型链继承

Sub.prototype=new Super();

这里发生了几件事:

  1. Sub.prototype设置为了new Supper

  2. 执行new Super,构建对象,暂且称这个对象为temp

  3. temp的原型指向挂在temp.__proto__下,大体可理解为temp.__proto__ = Sub.prototype

  4. 设置temp的属性,即这段代码:

    temp.name="this is super"

当这句Sub.prototype = new Super()执行完了之后,Sub.prototype是个什么状态呢?console一下我们就知道了:

{
   name : "this is super",
   __proto__ : {
        say : function () {
            console.log(this.name);
        },
        __proto__:...
   }
}

或者我们可以简单的理解为这样:

Sub.prototype={
   name : "this is super",
   say : function () {
      console.log(this.name);
   }
};

其实这时候,Sub已经成功继承了Super,内存指向如图。

clipboard.png

 

第二步,创建对象o

var o = new Sub();

这里发生了几件事:

  1. 执行构造函数Sub

  2. 设置o.__proto__=Sub.prototype,这时候对象o大概可以理解为这样:

    {
        name: 'this is super',
        say: function () {
            console.log(this.name);
        }
    }
  3. 然后执行的,就是题主困惑的地方:构造函数Sub中执行了代码this.name="this is sub",即设置了o.name="this is sub",执行完了这里,对象o大体已经长成这样了:

    {
        name: 'this is sub',
        say: function () {
            console.log(this.name);
        }
    }

相比到了这里,大家应该知道是怎么一回事了吧,这时候内存指向如图:

clipboard.png

所以最后,当执行o.say();的时候,输出的是this is sub

-------------- update - 应题主后来的追问:

题主后来追问内容:

var o = new Sub(); // 之前 Sub.prototype已经修改了,很疑惑构造函数是Sub。
Sub.prototype.constructor === Sub //false
new Sub() 执行的构造函数 不根据Sub.prototype.constructor 的指向么?

 

首先constructor是不安全属性,它是允许被重写的:

function Foo() {
    this.name = 'foo';
}

Foo.prototype = {};

var foo = new Foo;//{ }
foo.constructor == Foo.prototype.constructor //true
foo instanceof Foo //true

更鲜明一点的例子,我们直接声明一个对象:

var foo = { };
foo.constructor == Object.prototype.constructor //true

源代码中,prototype.constructor已经被重写为Object,因为这几段关键代码:

//省略部分代码

Super.prototype = {
    //这时候Super.prototype.constructor就已经指向Object了
    say: function () {
        console.log(this.name);
    }
}

//省略部分代码

//这时候Sub.prototype.constructor也已经指向了Object
Sub.prototype = new Super();

所以在题主的源代码上,这两段代码可以印证一切:

Super.prototype.constructor === Object //true
Sub.prototype.constructor === Object //true

因为constructor是不安全属性,它是允许被重写的,所以题主追问的判定代码并不正确。

另外constructor在对象的实现上是在构造函数里传递指向的,具体的实现可以参阅《ECMA-262标准实现规范 —— createdynamicfunction()》。

也即是说,是先运行构造函数,后指向constructor的(在构造函数里指向constructor)。


  • 本答案原创于 @linkFly ,和segmentfault.com共享,转载需保留本声明。

  • 前端开发QQ群:377786580

方法是从原型链中继承来的没错。然而方法体中的this是什么,与这个方法来自哪里没有关系,而是跟方法调用方式有关。你是用o调用的,所以this就是o,就是你new出来的Sub实例。

所以this.name首先是到你的o上面去找,找不到再沿原型链向上查找。

o->Sub->Super
你要是不在Sub构造函数里面加上

this.name = "this is sub";

那就能得到 this is super.
翻看一下js 原型链的知识- -

无论 o.say()say()方法是从哪里继承下来的
其函数内部的指令肯定是 console.log(this.name);

这里的这句命令,会被带回到调用者那里。
this也会指向 调用者 o
此时 o根据原型链最近的方法调用了say函数
所以会输出 "this is sub"

@linkFly

var o = new Sub(); // 之前 Sub.prototype已经修改了,很疑惑构造函数是Sub。
Sub.prototype.constructor === Sub //false
new Sub() 执行的构造函数 不根据Sub.prototype.constructor 的指向么?
o 对象的constructor属性会默认调用 Sub.prototype.constructor 的构造函数。

 翻阅了相关资料, new Sub() 构造函数为Sub,但是Sub.prototype.constructor 的指向不修改会造成什么问题呢? 这点不是很清楚。之前一直认为 prototype.constructor 的指向就是构造函数。


撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题