前言 在 ES5 的 JS 物件導向中,function 被當成建構子以及 class 用,因此被稱為構造函數。構造函數可透過語法 new
建造一個 instance 實體,也可以透過 prototype
做出共用的 method 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function Calculator (name ) { this .name = name; this .text = "" ; } Calculator.prototype.input = function (str ) { this .text += str; }; Calculator.prototype.getResult = function ( ) { return eval (this .text); }; let calculateA = new Calculator("A" );let calculateB = new Calculator("B" );console .log(calculateA.input === calculateB.input);
不過 JS 是如何知道 calculateA.input
等同於 calculateB.input
的呢?或者可以換個問題,JS 是如何繼承 method 跟 property 的呢?
透過 __proto__
__proto__
當我們建造 instance 時,JS 會自動幫我們在 instance 加上 __proto__
這個屬性,讓 instance 要使用該 method 找不到時,可以循著 __proto__
連結,往上找到有這個 method 的 prototype
,以上面的例子來說,有點類似這個概念:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 calculateA { name: A, text: '' , __proto__: Calculator.prototype } calculateB { name: B, text: '' , __proto__: Calculator.prototype } Calculator.prototype { input: function (str ) { this .text += str; }, getResult: function ( ) { return eval (this .text); }, __proto__: Object .prototype } Object .prototype { __proto__: null }
像這樣不停往上找,從
1 2 3 4 5 6 透過 calculateA.__proto__ 找到 calculateA --------------------------------> Calculator . prototype 透過 Calculator . prototype.__proto__ 找到 Calculator . prototype ------------------------------------------> Object . prototype
這個透過 __proto__
尋找的過程,被稱為 prototype chain 原型鍊 。
驗證 以下介紹三種方式驗證原型鍊:
=== 一、透過 ===
1 2 3 4 5 console .log(calculateA.__proto__ === Calculator.prototype); console .log(calculateA.__proto__.input === Calculator.prototype.input); console .log(calculateA.input === Calculator.prototype.input);
值得注意的是,不只有建構式的 prototype 才有 __proto__
,建構式本身也有 __proto__
。
以上面的例子來說,Calculator 這個建構式的 __proto__
會連接到 Function.prototype。 Function.prototype 的 __proto__
則連接到頂層的 Object.prototype。
仔細想想其實一切都很合理,Calculator 本來就是 Function,連接到 Function 很正常;而 Function 本來就是 Object,連接到 Object 也很正常。可以想像成以下這樣
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Calculator { __proto__ : Function .prototype } Function .prototype { __proto__: Object .prototype } Object .prototype { __proto__: null } console .log(Calculator.__proto__ === Function .prototype) console .log(Function .prototype.__proto__ === Object .prototype) console .log(Calculator.__proto__.__proto__ === Object .prototype)
hasOwnProperty 二、 透過 hasOwnProperty
1 2 3 console .log(calculateA.hasOwnProperty("input" )); console .log(calculateA.__proto__.hasOwnProperty("input" )); console .log(Calculator.prototype.hasOwnProperty("input" ));
instanceOf 三、 透過 instanceOf
如果 A instanceOf B
A 是 B 的實體的話,會回傳 true
。
1 2 3 4 5 6 7 8 9 10 11 12 console .log(calculateA instanceof Calculator); console .log(calculateA instanceof Object ); console .log(Calculator instanceof Function ); console .log(Calculator instanceof Object ); console .log(Function instanceof Object ); console .log(Object instanceof Function );
constructor 其實每個的 prototype 底下,除了共用的 method、property 以及 __proto__
外,還有一個叫做 constructor
的屬性。
在前面我們透過 instanceOf
驗證原形鍊時,得知不只是 new
創建出來的 instance 才是 instance,像是 constructor 也是 Object 的 instance。
所以相反的,Object.prototype
底下的 constructor 屬性也是 Object,因為他們自己就是建構式,可以 new 出 constructor。當然 Calculator.prototype.constructor = Calculator
,因為 Calculator 本身就是 constructor 拉,他的 constructor 也自然指向自己。
因此任何 X.prototype.constructor = X
。
new 了解原型鍊後就容易理解 new
是如何運作的,其實他幫我們做的步驟如下:
創建一個空物件(以下簡稱 O),將 O 當成是 new
出來的實體
透過 __proto__
讓 O 跟建構函式產生連結
將 O 當成建構函式裡的 this
並透過 call
或 apply
執行建構函式
回傳 O
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function Calculator (num ) { this .num = num; this .result = "" ; } Calculator.prototype.calculate = function (operator, num2 ) { return (this .result = eval (this .num + operator + num2)); }; function newCal (constructor, arguments ) { let O = {}; O.__proto__ = constructor .prototype ; constructor .apply (O, arguments ); return O; } let calA = newCal(Calculator, [5 ]);console .log(calA.calculate("*" , 2 ));
評論