(要加s)
Oriented 和你想的 Object-oriented programming 不一樣一般 Object-oriented programming (OOP)
使用 inheritance pattern,也就是大家熟悉的 Class 繼承。
JavaScript 的 Objects Oriented 屬於委託模式 (Delegation pattern)
,
特別要強調是來自於 Prototype-based programming 的觀念,
比較類似 Objects linked to other objects ( Objects 都是複數)。
Kyle 認為 委託模式 (Delegation pattern) 比 Class 繼承 更強大,
但很多人使用 JavaScript 都只有使用 Class 繼承,然後抱怨 JavaScript 很奇怪, Kyle 覺得很可惜。
不要拿其他語言的 this 來理解 JavaScript , JavaScript 自認很聰明,JavaScript 預判你的預判,你懂的。
重點在
如何被呼叫
這個概念我們之前有提過:
[day20] YDKJS (Scope) : Advanced Scope
Dynamic scope : 很彈性,隨時會改變的 Scope
makes it more flexible reusable context
,這點和我們說的 Dynamic scope
就很像了。
這張圖也是同一篇有提到過,
當時候說 JavaScript 是靜態 Lexical Scope ,所以不能動態存取,
才把圖上面說 「Theoretical」。
但現在講到 this
,對比一下,是不是有點感覺了?
this
嘗試做一些 Dynamic scope 的能力,所以能 flexible reusable context
。小整理目前已知的資訊 :
JavaScript 是靜態 Lexical Scope
closure
是可以把執行 Lexical Scope 全部備份下來的能力。
(否則 Lexical Scope 會把執行過的 function 回收)this
有可能是想要做到某些 Dynamic scope 的能力(透過function 什麼時候被呼叫
來決定內容)。
(否則 Lexical Scope 在 compiler time 就決定好 scope ,沒什麼好疑惑)
如何被 invoked
而不像是 Lexical Scope 關注在可以
被預測
。
之前我們提到,Lexical Scope 像是在建築物裡面,
從底部開始往上層找,直到 找到 或是到頂層global scope為止(左圖)。
但是 this 關注不同點, this 比較像是 「哪一棟是我要去的建築物?」
建築物可能是一個 function ,可能每一個 建築物(function) 都有 2 樓(都有一個變數 var teacher) 。
this 會觀察是哪個建築物,誰邀請他
。
如果是 Lexical Scope
:
知道是哪一棟建築物(一開始就有規劃書),從一樓開始往上找,看看什麼時候會找到,直到頂樓( global scope )為止。
如果是 this
:
知道目的地是二樓,但不知道要去哪一棟建築物的二樓。
如何被 invoked
。這個和之前提到的 name space pattern 很像。
所以 this 在 name space pattern 會有什麼行為?
一樣,思考 function 什麼時候被呼叫
。
workshop.ask 的 .
是主要 invoke 的時候,
這個 workshop.ask
就是說要呼叫 workshop (Object) 裡面的的 ask() 方法,
所以存在的環境就是 workshop 這個 object,
所以這時候的建築物
就是 workshop (Object)
。
workshop.ask
被呼叫,ask() 之中的 this 此時此刻 也就是代表workshop (Object)
順帶一提,這也是大多數語言的 this
行為。
但是 JavaScript 還有其他 3 種方法,所以才容易混淆。
這種隱式綁定(implicit binding)
的想法是很有用的,因為這樣我們可以在不同的 context 共享一種方法,不用每一次都寫固定的 Lexical Scope 。
這邊 定義一個 ask function,但是可以在兩種不同的 Objects (物件) 裡面共享方法,
因為 line 7, line 12 各有一個 reference 指向 ask function。
當我對 ask function 使用保留的 reference,
每一次 implicit binging 會找「誰」 invoke function,
所以在每個執行的時間點,都可以綁定不同的 context。
用
.call , .apply
很顯性的告訴 JavaScript,哪一個對象是我們要綁定的 this 。
第一個 argument 是要綁定的對象。
現在我們比較少直接用 explicit binging ,比較常用的情境是一種變體方法。
裡面
)setTimeout 事實上會用
.call
來 explicitly invokes 一個 global context,
而不是直接呼叫 workshop.ask ,反而像是 透過 cb.call window(global context) 來呼叫 workshop.ask 。
都不理解也沒關係,不是這次的重點,只要知道這時候 setTimeout 綁不到你預測的 this
.bing 並沒有 invoke function,比較像創造一個新的 function,把你特別指定的參數傳入 context 。
這樣又很像我們之前說的 Lexical Scope 方法所追求的 可預測性
,有點浪費原本 this 綁定可以彈性
的方法。
過於彈性就會難以預測,這也是寫 code 常常要思考的問題 : 可預測(過於靜態) 還是 彈性(不可預測)。
這是一種 trade-off ,魚與熊掌不可得兼。
Kyle 的建議是,他自己如果整份 code 偏向 flexible dynamism ,那就整份都用維持一致風格,如果偶爾需要使用 hard binding 確保程式可以預期,那是 ok 的,因為你從 flexible dynamism 上面獲得很多好處。
相反的,如果你整份都用 hard binding ,那你應該要思考是不是要使用 Lexical Scope(closure) 方法所追求的 可預測性
, 否則你只是繞遠路,多做很多不必要的事。
new
keyword
使用 new
keyword ,看起來好像是和 OOP 的 class constructot 有關,
其實只是故意做很像,但完全沒有關係。
可惜,在這層意義上(故意做很像)反而很多人誤會或誤解 new
keyword 。
new
keyword 有三種方法來 invoke 一個 function,其中又做 4 種特別的事,特別在你很難觀察它們的出現。
不過 new
的目標都是要 透過 this
keyword 來 invoke function,放在一個 new empty object 裡面。
差異:
仔細觀察,其實第二種方法( .call, .apply, .bind)
傳入的特定 Object ,如果是 {}
(new empty object) 就是我們說的 new binding。
function 都要 return
,不過如果沒有指定, return 的預設會變成 this
其實如果全部講完 JavaScript 再回來看,這就是 new keyword 的行為,
不限於 invoke function。
如果不是以上三種方法,就是這種,所以叫做 default binding。
其實是一種錯誤使用 this 的 binding error 處理。
(我們前面提到,使用 this 的目的是 flexible dynamism)
line 12 : 沒有 context object 就呼叫 function
如果非嚴格模式 : global
你不會想看到這個 = ="
如果嚴格模式 : type error
default behavior is to leave this undefined,但你不能 access 一個屬性是 undefined value
Kyle : lexical (this) behavior
因為 arrow function 沒有定義 this keyword ,
所以你把 this 放到 arrow function ,其實等於放一個變數而已。
==> 那就是 lexical scope behavior ,往上層找。
line 5 的 arrow function
裡面有 this
,
但是 arrow function 不管 this ,就當作 lexical scope
behavior ,往上層找什麼時候有 this keyword invoked,
(而且此時有 closure 保留 Execution Context )
所以 line 5 的外層 scope 是 ask function
。
this 就變成找 ask
function 如何被 invoke。
Spec.:
arrow function 沒有定義 local binding
object is not scope,所以 this 會往外找到 global。
此時非嚴格模式,就變成 global 動態宣告 var teacher = undefined;
因為可以讓 this 也做到 lexical scope 的能力。
你用正確的方法,就可以不用寫var _this = this
Kyle
lexical scope + this
,使用 arrow function。