前言
本周進入到原生 JavaScript 背後的運作模式,其中比較複雜的是原型鍊(prototype chain)這一部分,所已整理了一系列的筆記在這邊整理一下腦中的思路。
而這一篇的主題是 new
在背後做了甚麼,主要在整理 huli 老師在課堂中講解 new
的邏輯推演,從新自己寫了一個相似的例子,順過一遍,適合 prototype 新手閱讀。
new 在背後到底做了甚麼?
先看一個常見的例子
//設立一個 constructor function
function Stuff(name, job){
this.name = name;
this.job = job;
}
//丟一個 method 到 Stuff 的 prototype裡
Stuff.prototype.log = function(){
console.log(this.name + " " + this.job);
}
//設立兩個 Stuff 的 instance
let james = new Staff("james", "doctor");
let amy = new Staff("amy", "designer");
console.log(james)//{name:"james", job:"doctor"}
console.log(amy)//{name:"amy", job:"designer}
james.log()//"james" "doctor"
amy.log()//"amy" "designer"
從這邊可已發現,兩個 instance 雖然自己沒有 log 這個 method,但卻有辦法依著 prototype chain 呼叫到放在 Stuff.prototye 裡面的 function。
究竟 new
在背後做了甚麼呢? 讓他們可以這樣運行呢?
其實一點也不複雜,huli 老師在課程中實作了一個 function,讓我們了解 new
在背後的運作機制,在這邊想要遵循這個思路自己建造一個 function,一步步推演 new
的運行機制。
在實作之前,先看看new
做出來的東西要有那些特質:
- 一個物件。
- 物件裡面的屬性是根據 Staff() 裡面的設定(就是有 name 和 work 倆的個屬性啦。)
- 這個物件能夠用到 Staff.prototype 裡的 method。
根據以上的 requirement,我寫了以下這段程式:
function Stuff(name, job){
this.name = name;
this.job = job;
}
Stuff.prototype.func = function(){
console.log(this.job)
}
function newFake(name, job){
let obj = {};
Stuff.call(obj, name, job);
obj.__proto__ = Stuff.prototype;
return obj
}
let charles = newFake("charles", "student");
console.log(charles);//{name:"charles", job:"student"}
仔細一看,其實這個函式的結構很簡單。
- 設定一個空 object,準備用於在未來接收值。
- 用 call() 來呼叫 Stuff,Staff 接受收了 name 和 age 兩個參數,建立 instance。
---------------- 哀不對怪怪的
Staff 明明只有兩個參數,為甚麼 call() 裡面多了一個參數,而且那個參數的值還是樓上的 obj。
這是因為如果要創建一個新的 instance 的話,我們必須要指定好 this
指向的對象,說起來,如果連可以連接的對象都沒有的話,怎麼可能可以創造出一個 instance 呢?
有了前面的認知之後,我們現在的目標很明確,就是將 Stuff() 裡的 this 指向我們未來要創造的 instance,在這邊就是 newFake 裡面的 obj。那我們要怎麼做呢?
這時候就輪到 call()
method 登場啦!
詳細的介紹大家可以看這裡,這邊先丟上 MDN 對 call()
method 簡單的定義:
The call() method calls a function with a given this value and arguments provided individually.
從這段定義我們可以得到以下幾個資訊:
call()
可以用來呼叫函數。call()
可以傳進該函數所需要的參數。- 不單如此,
call()
還可以傳進this
所指向的對象。
換言之,我們可以利用 call()
method 將我們想指定的 this 對象傳進去,也就可以達成我們的目標了,但是,要麼做呢?
文件在往下拉,裡面介紹了 call()
的 syntax:
call()
call(thisArg)
call(thisArg, arg1)
call(thisArg, arg1, arg2)
call(thisArg, arg1, ... , argN)
原來 call()
method 的第一為參數就是留給 this 的對象的。看到這裡,我想這一段這樣寫的原因就很清楚了:
Staff.call(obj, name, job)
第一個參數是 this 要指向的對象,後面兩個則是 Staff 原本的兩個參數。這段完成之後就建構出一個根據 Staff
的 instance 了!
最後還有一個問題要解決,要怎麼讓新創見的 obj 也能夠用到 Staff.prototype 裡的 method 呢? 答案是將 obj 的 proto 屬性連接上 Staff.prototype。
所以最後還得加上這一段,連接兩者才是真正建構出一個根據 Staff
的 instance。
obj.__proto__ = Staff.prototype;
總結
最後在這邊用條列式,一行行總結一下 new
的背後機制吧:
設立一個空 object 準備在未來放東西進去。
利用 constructor function 將東西放進去空 object,而且 object 的結構長得跟 constructor function 定義的一樣。
將 object 的 proto 屬性連結上 constructor function 的 prototype,這麼一來,這個 object 不只有 constructor function 鎖定一個結構,還可以呼叫到在 constructor function 的 prototype 裡的 method。(這段有點饒口,有空回來修)
將這個設定好的 object 回傳。