关于 __proto__ 属性,MDN 上的解释是这样的[1]

The __proto__ property of Object.prototype is an accessor property (a getter function and a setter function) that exposes the internal [[Prototype]] (either an object or null) of the object through which it is accessed.

即是说,__proto__ 属性指向了实例对象的原型 Constructor.prototype。那么,这个属性里隐藏着怎样的黑魔法呢?

ES6 class 的实现

最近看 ECMAScript 6 的 spec,发现了一些有意思的东西,比如 class 章节:

14.5.14 Runtime Semantics: ClassDefinitionEvaluation[2.1]

With parameter className.

ClassTail : ClassHeritageopt { ClassBodyopt }

...

6.g (for class heritage)
  1. If superclass has a [[FunctionKind]] internal slot whose value is "generator", throw a TypeError exception.
  2. Let protoParent be Get(superclass, "prototype").
  3. ReturnIfAbrupt(protoParent).
  4. If Type(protoParent) is neither Object nor Null, throw a TypeError exception.
  5. Let constructorParent be superclass.
7. Let proto be ObjectCreate(protoParent).

...

12. Let constructorInfo be the result of performing DefineMethod for constructor with arguments proto and constructorParent as the optional functionPrototype argument.

...

16. Perform MakeConstructor(F, false, proto).

...

18. Perform CreateMethodProperty(proto, "constructor", F).

...

这几行规定了类继承(class SubClass extends SuperClass {})的行为,除了众所周知的 SubClass.prototype = Object.create(SuperClass.prototype) 以外,还做了一件有趣的事:Let constructorParent be superclass, proto be ObjectCreate(protoParent), and performing DefineMethod for constructor with arguments proto and constructorParent as the optional functionPrototype argument.

追溯 functionPrototype 变量的去向,发现是这样的:

14.3.8 Runtime Semantics: DefineMethod[2.2]

With parameters object and optional parameter functionPrototype.

...

6. Let closure be FunctionCreate(kind, StrictFormalParameters, FunctionBody, scope, strict). If functionPrototype was passed as a parameter then pass its value as the functionPrototype optional argument of FunctionCreate.

...

9.2.5 FunctionCreate (kind, ParameterList, Body, Scope, Strict, prototype)[2.3]

The abstract operation FunctionCreate requires the arguments: kind which is one of (Normal, Method, Arrow), a parameter list production specified by ParameterList, a body production specified by Body, a Lexical Environment specified by Scope, a Boolean flag Strict, and optionally, an object prototype.

...

4. Let F be FunctionAllocate(prototype, Strict, allocKind).

...

9.2.3 FunctionAllocate (functionPrototype, strict [,functionKind] )[2.4]

...

12. Set the [[Prototype]] internal slot of F to functionPrototype.

...

原来 functionPrototype 被用作了 SubClass 的 [[Prototype]] 属性

Babel[2] 对继承的实现如下:

function _inherits(subClass, superClass) {  
    if (typeof superClass !== "function" && superClass !== null) {
        throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
    }
    subClass.prototype = Object.create(superClass && superClass.prototype, {
        constructor: {
            value: subClass,
            enumerable: false,
            writable: true,
            configurable: true
        }
    });
    if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}

道理我都懂,可是为什么要这样做?

[[prototype]] 与原型链

要检测一个对象是否是一个构造函数的实例,我们通常会用 O instanceof C 这样的表达式,在 spec 中,instanceof 运算符这样被定义:

12.9.4 Runtime Semantics: InstanceofOperator(O, C)[2.5]

...

2. Let instOfHandler be GetMethod(C,@@hasInstance).

...

19.2.3.6 Function.prototype[@@hasInstance] ( V )[2.6]

1. Let F be the this value.
2. Return OrdinaryHasInstance(F, V).

7.3.19 OrdinaryHasInstance (C, O)[2.6]

...

4. Let P be Get(C, "prototype").

...

7. Repeat
  1. Let O be O.[[GetPrototypeOf]]().
  2. ReturnIfAbrupt(O).
  3. If O is null, return false.
  4. If SameValue(P, O) is true, return true.

9.1.1 [[GetPrototypeOf]] ( )[2.6]

1. Return the value of the [[Prototype]] internal slot of O.

大致描述如下:instanceof 运算符掉用了 Function.prototype 上的内部方法 @@hasInstance,此方法将 this 对象(即 C)的 prototype 属性与实例对象 O 的 [[prototype]] 对比,如果后者 [[prototype]]null 则返回 false,如果两者相等,则返回 true,否则沿原型链向上比较,直到得出结果。

即是:

O instanceof C =>  
  O.__proto__ === C.prototype ? true:
    O.__proto__.__proto__ === C.prototype ? true :
        ...

由此我们可以轻松伪造一个实例对象:

class A {  
    whoami() {
        return 'Instance of A';
    }
}

let a = new A();

let b = {};  
Object.setPrototypeOf(b, A.prototype); // b.__proto__ = A.prototype

a.whoami() === b.whoami(); // true  
b instanceof A; // true  

可是这是对对象的 __proto__ 属性的修改,和 SubClass.__proto__ 有什么关系?

静态方法的继承

少年,可别忘了 JavaScript 函数本身也是个对象哟!

在上面的代码中,我们使无关对象 b__proto__ 指向构造函数 Aprototype,于是使 b 被判定为 A 的实例。同时,A 的所有原型方法都被 b 所继承!

换句话说,如果将 SubClass__proto__ 属性指向 SuperClass,父类上的所有属性都将被子类继承!比如:

class A {  
    static whoami() {
        return 'A Constructor!';
    }

    greet() {
        return 'hello world!';
    }
}

function B() {}  
Object.setPrototypeOf(B, A);

B.whoami(); // 'A Constructor!'  

此时,我们再将 B.prototype__proto__ 属性指向 A.prototype,即可完成原型方法的继承:

Object.setPrototypeOf(B.prototype, A.prototype);

let b = new B();  
b.greet(); // 'hello world!'

b instanceof B; // true  
b instanceof A; // true  

如此一来,子类就构造完成了!可以开开心心造孩子去了!

恶搞:让函数 B 成为函数 A 的实例

利用 instanceof 运算符的定义,我们还能玩出一些神奇的魔法,比如:

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

function B() {};  
Object.setPrototypeOf(B, A);

B instanceof A; // true!  

(全文完)

参考资料

  1. Object.prototype.__proto__ - JavaScript | MDN
  2. ECMAScript 2015 Language Specification
  3. Babel · The compiler for writing next generation JavaScript