javascript - 繼承與原型鏈

Alongite發表於2019-03-03

背景

看到繼承與原型鏈就會想到prototype__proto__,作為一個菜鳥,在昨天之前只用過prototype,知道有一個叫__proto__的東西,但是不知道是做什麼的。 但是昨天遇到一個vue的繼承問題,通過instanceof 判斷不出來變數是不是通過Vue.extend生成子類,程式碼如下:

let component = Vue.extend({
    mounted(){
        console.log('mounted')
    }
})

console.log(component instanceof Vue) // false
複製程式碼

vuejs官方文件是這麼介紹vue.extend這個方法的

javascript - 繼承與原型鏈

既然是"子類"為什麼instanceof的結果是false。

帶著這個問題,開始深入學習一下js的繼承與原型鏈。

學習

說到繼承與原型鏈,不得不先放張圖鎮樓

javascript - 繼承與原型鏈

這張圖看不懂沒關係,一步一步慢慢來。

先看下面程式碼

// 宣告一個類
function Test(){
    this.a = 1
    this.b = 2
}
// 例項化
let ins = new Test()
// 原型鏈上追加屬性
Test.prototype.b = 3
Test.prototype.c = 4

console.log(ins.a) // 1
// 這裡的a是例項上的屬性
console.log(ins.b) // 2
// 這裡的b是例項上的屬性
console.log(ins.c) // 4
// 這裡的c是原型鏈上的屬性
複製程式碼

依次列印這幾個變數,會有以下這樣的結果

-> console.log(ins)
{
  "a": 1,
  "b": 2,
  "__proto__": {
    "b": 3,
    "c": 4,
    "__proto__": {
      "hasOwnProperty": function
    }
  }
}
複製程式碼
-> console.log(Test)
{
    "arguments": null,
    "caller": null,
    "length": 0,
    "name": "Test",
    "prototype": {
        "b": 3,
        "c": 4,
        "__proto__": {
          "hasOwnProperty": function
        }
    }
}
複製程式碼
console.log(ins.__proto__ === Test.prototype) // true
複製程式碼

這就很明確了,ins.__proto__指向了Test.prototype。通過檢視MDN,通過new生成一個例項的過程是這樣的:

// js程式碼
var o = new Foo();

// js實際執行程式碼
var o = new Object();
o.[[Prototype]] = Foo.prototype;
Foo.call(o);
複製程式碼

這段程式碼說明讓我產生了極大的興趣,下面是梳理ecma官方標準

當執行new操作的時候會執行以下步驟:

  1. Assert: constructExpr is either a NewExpression or a MemberExpression.
  2. Assert: arguments is either empty or an Arguments.
  3. Let ref be the result of evaluating constructExpr.
  4. Let constructor be ? GetValue(ref).
  5. If arguments is empty, let argList be a new empty List.
  6. Else,
  7. Let argList be ArgumentListEvaluation of arguments.
  8. ReturnIfAbrupt(argList).
  9. If IsConstructor(constructor) is false, throw a TypeError exception.
  10. Return ? Construct(constructor, argList).

最後一步的Construct是這麼定義的: Construct ( F [ , argumentsList [ , newTarget ]] )

  1. If newTarget is not present, set newTarget to F.
  2. If argumentsList is not present, set argumentsList to a new empty List.
  3. Assert: IsConstructor(F) is true.
  4. Assert: IsConstructor(newTarget) is true.
  5. Return ? F.[[Construct]](argumentsList, newTarget).

之後呼叫了內部方法[[construct]]

  1. Assert: F is an ECMAScript function object.
  2. Assert: Type(newTarget) is Object.
  3. Let callerContext be the running execution context.
  4. Let kind be F.[[ConstructorKind]].
  5. If kind is "base", then
    • Let thisArgument be ? OrdinaryCreateFromConstructor(newTarget, "%ObjectPrototype%").
  6. Let calleeContext be PrepareForOrdinaryCall(F, newTarget).
  7. Assert: calleeContext is now the running execution context.
  8. If kind is "base", perform OrdinaryCallBindThis(F, calleeContext, thisArgument).
  9. Let constructorEnv be the LexicalEnvironment of calleeContext.
  10. Let envRec be constructorEnv's EnvironmentRecord.
  11. Let result be OrdinaryCallEvaluateBody(F, argumentsList).
  12. Remove calleeContext from the execution context stack and restore callerContext as the running execution context.
  13. If result.[[Type]] is return, then
    • If Type(result.[[Value]]) is Object, return NormalCompletion(result.[[Value]]).
    • If kind is "base", return NormalCompletion(thisArgument).
    • If result.[[Value]] is not undefined, throw a TypeError exception.
  14. Else, ReturnIfAbrupt(result).
  15. Return ? envRec.GetThisBinding().

其中第5步,會建立物件,呼叫OrdinaryCreateFromConstructor,其定義如下:
OrdinaryCreateFromConstructor ( constructor, intrinsicDefaultProto [ , internalSlotsList ] )

  1. Assert: intrinsicDefaultProto is a String value that is this specification's name of an intrinsic object. The corresponding object must be an intrinsic that is intended to be used as the [[Prototype]] value of an object.
  2. Let proto be ? GetPrototypeFromConstructor(constructor, intrinsicDefaultProto).
  3. Return ObjectCreate(proto, internalSlotsList).

第2步,獲取原型鏈,第三部建立物件 ObjectCreate ( proto [ , internalSlotsList ] )

  1. If internalSlotsList is not present, set internalSlotsList to a new empty List. Let obj be a newly created object with an internal slot for each name in internalSlotsList. Set obj's essential internal methods to the default ordinary object definitions specified in 9.1.
  2. Set obj.[[Prototype]] to proto.
  3. Set obj.[[Extensible]] to true.
  4. Return obj.

哈哈哈,沒時間整理了,官方標準搬運工,有興趣的自己看官方標準www.ecma-international.org

最後放一個官方關於prototype的說明

javascript - 繼承與原型鏈

相關文章