__proto__ 屬性與 ES6 classes 的繼承

關於 __proto__ 屬性,MDN 上的解釋是這樣的[1]javascript

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.java

便是說,__proto__ 屬性指向了實例對象的原型 Constructor.prototype。那麼,這個屬性裏隱藏着怎樣的黑魔法呢?es6

ES6 class 的實現

最近看 ECMAScript 6 的 spec,發現了一些有意思的東西,好比 class 章節:express

14.5.14 Runtime Semantics: ClassDefinitionEvaluation[2.1]

With parameter className.
ClassTail : ClassHeritageopt { ClassBodyopt }
...babel

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").ui

  3. ReturnIfAbrupt(protoParent).this

  4. If Type(protoParent) is neither Object nor Null, throw a TypeError exception.lua

  5. Let constructorParent be superclass.spa

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;
}

道理我都懂,但是爲何要這樣作?

pic-1

[[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!

pic-2

(全文完)

參考資料

  1. Object.prototype.__proto__ - JavaScript | MDN

  2. ECMAScript 2015 Language Specification

  3. Babel · The compiler for writing next generation JavaScript

重編自個人博客,原文地址:https://idiotwu.me/proto-property-and-es6-classes-inheritance/

相關文章
相關標籤/搜索