[[Prototype]] and prototype

经常会谈论到prototype,实例对象的prototype对象和function的prototype属性是两个不同的概念,但是经常被初学者混淆。

这儿用实例对象表示通过对应function创建的object,是相对于对应的constructor而言的。在ES中没有实例对象这个说法。

  • 实例对象的prototype对象,一般称之为原型对象,内部属性[[Prototype]](一般用[[]]表示内部属性)指向此对象,不过通常[[Prototype]]不能直接被访问,部分浏览器提供非标准的__proto__,可以它可以访问原型对象
  • function的prototype属性可以直接访问。使用function创建的实例,实例的[Prototype]属性即指向prototype属性指向的对象。

以function A 和 A的实例a为例

function A () { }
A.prototype.x = 2;

var a = new A();

// Sometimes a.[[Prototype]] == A.prototype
// a.[[Prototype]] ---> Prototype <--- A.prototype
alert(a.x); // 2

// modify A.prototype
A.prototype = {
    y: 3,
    z: 4,
    method: function () {
        alert(this);
    }
};

// 当A.prototype被更改后,与a.[[Prototype]]不再指向同一个对象
// a.[[Prototype]] 仍然指向旧的对象
alert(a.y); // undefined
alert(a.x); // a.[[Prototype]]仍然指向旧的原型对象

var b = new A();

alert(b.x); // undefined 新的A.prototype上未定义x属性
alert(b.y); // 3

[[Prototype]] 和 prototype 的关系

这儿的prototype指function的prototype属性。当然function作为实例对象(Function的实例)也有[[Prototype]]属性,不过这儿不讨论它

在使用函数创建对象时(new A())实际上是调用了函数的[[constructor]]属性,规范中定义了[[Constructor]]属性被调用后的内部过程表述如下:

When the [[Construct]] property for a Function object F is called, the following steps are taken:

  1. Create a new native ECMAScript object.
  2. Set the [[Class]] property of Result(1) to "Object".
  3. Get the value of the prototype property of F.
  4. If Result(3) is an object, set the [[Prototype]] property of Result(1) to Result(3).
  5. If Result(3) is not an object, set the [[Prototype]] property of Result(1) to the original Object prototype object as described in 15.2.3.1.
  6. Invoke the [[Call]] property of F, providing Result(1) as the this value and providing the argument list passed into [[Construct]] as the argument values.
  7. If Type(Result(6)) is Object then return Result(6).
  8. Return Result(1).

用伪代码可以表示如下:

F.[[Construct]](initialParameters):

O = new NativeObject();

// 设置对象的[[Class]] 属性为"Object" 
O.[[Class]] = "Object"

// 获取当前时刻F.prototype的指向,注意是当前时刻的,因为F.prototype以后可能会改变
var __objectPrototype = F.prototype;

// if __objectPrototype is an object, then:
O.[[Prototype]] = __objectPrototype
// else:
O.[[Prototype]] = Object.prototype;
// where O.[[Prototype]] is the prototype of the object

// initialization of the newly created object
// applying the F.[[Call]]; pass:
// as this value – newly created object - O,
// arguments are the same as initialParameters for F
R = F.[[Call]](initialParameters); this === O;
// where R is the returned value of the [[Call]]
// in JS view it looks like:
// R = F.apply(O, initialParameters);

// 如果调用[[Call]]的返回值是个对象则返回R,否则返回上面创建的对象O
// if R is an object
return R
// else
return O

我们有这样的结论:

  • [[Prototype]]是实例对象的属性,prototype是function的属性
  • [[Prototype]]是在对象被创建时确定的
  • [[Prototype]]在对象刚创建时与prototype指向同一个对象
  • prototype可以被更改,在更改之后将于之前通过该function创建的实例对象的[[Prototype]]不再指向同一个对象
  • 如果function中显式的return一个对象,则使用new调用构造函数时返回的是return的对象,而不是上面创建的NativeObject的对象

关于最后一点可以看一个示例:

function A () {
    this.x = 1;
    this.y = 2;
    return { 
        x: this.x
    };
}

// 这儿相当于 a = A.apply(O); 其中O是创建的NativeObject对象
// this.x === O.x
var a = new A();

alert(a.x); // 1
alert(a.y); // undefined 没有返回y,不能访问O.y

__prototype__

部分Ecmascript的实现提供__proto__可以访问实例对象的内部属性[[Prototype]],如chrome,ff,safari等(具体从什么版本开始支持,没有考证过)

function A () { }
A.prototype.x = 2;

var a = new A();

var __newPrototype = {
    y: 3,
    z: 4,
    method: function () {
        alert(this);
    }
};

A.prototype = __newPrototype;

var b = new A();

alert(b.x);
alert(b.y); // 3
alert(a.y); // undefined

a.__proto__ = __newPrototype;

alert(a.x); // undefined
alert(a.y); // 3

A.prototype.y = 0;
alert(a.y); // 0
alert(b.y); // 0

上面示例通过__proto__修改了[[Prototype]], 在将原有对象实例的[[Prototype]]指向function的新prototype之后,原有对象实例和新对象实例的[[Prototype]]相同了。

需要注意一点__protp__不是规范中定义的standard属性,只被部分浏览器支持,使用时要慎重。

ES5中引入了Object.getPrototypeOf(O)方法,它可以直接返回一个对象的[[Protptype]],不过与__proto__不同,这个返回值是只读的,我们不能通过它对原型对象设置和更改属性。

See also

comments powered by Disqus