iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 23
0
Modern Web

跟著 YDKJS 作者 Kyle Simpson 打造全新 JavaScript Mindset系列 第 23

[day22] YDKJS (Closure) : Closure 入門: persistent lexical scope referenced data

  • 分享至 

  • xImage
  •  

Closure 的歷史故事

文章寫很多次,你現在應該也知道,JavaScript 被創造的目標是一個「自認為很聰明的語言」 ,有很多「很聰明(自認)的語言才會有的特性」,所以可以說 JavaScript 是一種 consumer-oriented language。

很久以前就有 Closure 這個概念,但是以前的人不一定知道要怎麼用,
以歷史的角度來看,現代的語言都是有 Closure 特性的,如此才能說一個程式語言是真正有威力的語言。

時間拉回 1995 年,Brendan Eich(JavaScript 主要創造者與架構師)加入 Netscape,
那個時候很多現代語言的特性都是存在於 academic language,比如 Closure 基本上只存在學術研究。

1995 年有 Closure 特性,最常見的目的之一是為了寫 functional programming language,但 Brendan Eich 想把它加進去 JavaScript。
順帶一提,1995 年有 Closure 這個特性,但不是研究用的程式語言,另一個是 Perl,同時期幾乎其他語言都沒有,所以才會說 Closure 是 academic language 的東西。

很顯然的,1995 年那群工程師根本不會在意這種學院派的東西,更不可能會使用到 Closure 這種東西,他們需要一個商業用,實務上可以用的程式語言。

然後你可以從上帝視角看到,現今的 JavaScript 和 Closure 不可分割,那時候 Brendan Eich 某種程度上,為了隱含這個意圖,創造一堆大括號 {..} 和分號的語法,然後還命名叫做 JavaScript 試圖瞞混過去。
(你可以從 JavaScript 這個名字上,看出當時 Java 有多受歡迎,一個想蹭熱度流量的概念)

1995 年 JavaScript 的 大括號 {..} 真的沒啥意義,另外分號要不要使用現在已經是一種團隊默契了,但最開始的想法只是想做的和 C++ 或是目標 Java 程式撰寫風格做得很像,藉此偷渡 JavaScript 其實就是 Closure 的意圖(直接說反而被拒絕,換個皮就成功拉 XDD)。

講了那麼多故事,Kyle 只是想說,JavaScript 活那麼久沒有被消滅,很大一部分是提前拿到 Closure 這個特殊能力。

如果你要找書上或是 wiki 的 Closure 定義,那多半還是學術定義,大多數的時候並不能幫助你程式開發。

所以我們接下來會把目光放在 有 Clousre 的語言,會有什麼天性

但首先,你必須理解 lexical scope。

註:如果你好奇 JavaScript 歷史故事
dotJS 2017 - Brendan Eich - A Brief History of JavaScript

  • 補充:之前寫過的 static scoping(靜態作用域)
    [day20] YDKJS (Scope) : Advanced Scope
    Lexical Scope : 可以預測的 Scope
    Dynamic scope : 很彈性,隨時會改變的 Scope

interview 的時候,你要怎麼回答 Closure 是什麼?

Closure is when a function is able to remember and access its lexical scope, the variables outside of itself, so-called free variables, when it's able to access that lexical scope, even when that function executes in a different scope.

大家看原文,我不敢翻譯怕有錯 XDD

簡體翻譯:

閉包就是函式能夠記住並訪問它的詞法作用域,即使當這個函式在它的詞法作用域之外執行時。

換句話說,Closure 為什麼重要?

  1. lexical scope 是執行前就決定好的,這也就是 lexical scoping 被稱為「static scoping(靜態作用域)」的原因。
    在 lexical scope 之下,找不到的東西會往上層找,直到 global。 只有這個特性還不算 Closure,只是一般的 lexical scope。

只是正常 lexical scope : 找不到的東西會往上層找,直到 global。

需要滿足另一個特性:

  1. 可以記得存取原本的 lexical scope
    • 為什麼重要?
    • 因為原本 function 的 lexical scope 使用完後,整塊 function 的 Execution Context 就會被回收,包含 function 內部的變數和任何函數。但現在可以被保留!!

現在被 Closure 保留住了。 如果有圖來說 :

先體驗看看 Closure 的範例:

這邊先不解釋 setTimeout 的運作機制,
你可以想像 JavaScrip 引擎委託 網頁瀏覽器 存放一這個 waitASec(){...} 並且設定時間 100 毫秒後,再排隊執行(要排隊等全部 JS 執行後才能輪到 waitASec(){...} )。

參考資料

  1. 剛好最近 huli 有新文章
  2. 我也推薦 What the heck is the event loop anyway? | Philip Roberts | JSConf EU,大家的啟蒙,好像有人做簡體字幕另外上傳可以自己找

想像中,code會這樣跑:

  1. 執行 ask("What is closure?");
  2. 執行之後 ask("What is closure?"); 會全部消失
  3. (過100 ms)
  4. 執行 waitASec(){ console.log(question); }
  5. question 找不到 => is not defined

這樣想你是對的 ,但是少一個步驟

一個神奇的地方出現了,question 居然被保留著 !!
如果有找到就回傳 Magic Scope 的東西,

  1. question 找到 Closure 存的字串,並 console.log 出來。

如果 Magic Scope 沒有找到,就像是正常 lexical scope 一樣繼續回去 Current Scope 往上層找,就會是原先的 is not defined

Magic Scope(Closure) 怎麼做到的?

  1. 在 function被呼叫時,會檢查有沒有其他人引用這個 scope 內部的值,如果有就會貼上一張[[Scope]] 的貼紙當作標籤。
    在正常 lexical scope 往上層找,其實就是沿著 [[Scope]] 貼紙找 (a.k.a. 沿著 scope chain )。

  2. 然後 function執行完,會撕掉一開始 identifier 的 [[Scope]] 貼紙。

  3. 很顯然,Closure 就是那一張亂貼的機制,被貼上[[Scope]] ,所以會優先去找到這個被保留著的 Magic Scope (Closure)。

  4. 要注意,Closure 保留下來的時候,只剩下 這張貼紙,其他貼紙都撕掉了。
    其他貼紙都撕掉了,也意味著原本 function declaration 宣告的 identifer 不能存取到當時的 function 了。

這邊比較麻煩一點,可以透過 Spce. 來輔助說明,
Spce. 裡面針對有寫 Object 是 mutability,並且都是指向同一個 Object.

用常用術語說就是 by reference,但是有貼到貼紙都可以碰到這個 reference ,所以這個 reference 是被 shared , a.k.a. pass by shared

var a = { obj: 'someObj'};
var b = a;
a; // {obj: "someObj"}
b; // 

這時候代表 {obj: "someObj"} 上面貼兩張 label ,分別叫做 a, b 。

我們這個系列一開始就說過 JavaScript 的比較重要。
這邊補充:變數是我們比較方便存取的方法

然後,function 是一種 Object。
我們舉例的程式,不嚴謹的看大概是這種感覺:

  1. 執行 ask("What is closure?");
  2. 執行之後 ask 標籤會全部消失。
  3. 原本應該整個黃色圈圈都會消失,但是因為還有被引用(setTimeout 裡面的函式等等會引用),所以被保留著。
  4. 這時候執行排隊回來的 waitASec,還能找到值
  5. console.log 執行之後沒有人引用,整個區域被釋放掉

有問題請幫我指證 QQ

其中,圈圈很大一圈,代表 closure 是 scope-based不是 以變數為單位。

另一個例子:

原本 ask 應該會被撕掉,然後整個 function 被清空,
但是今天我把傳進去的參數保留在 myQuestion 這個 identifer 上:

line 7 : 其實就是貼一個標籤在 真實code 上面
如果你今天輸入 identifer,會看到是原本 ask function return 整個 function object(code)

myQuestion; // ƒ holdYourQuestion(){ console.log(question)}

還是一句話: function 也是物件。
所以你要執行 function ,後面要加上() 才會是執行 expression。

myQuestion(); // 執行 holdYourQuestion() : console.log(question)
但今天 clousre 保留整個黃色圈圈,
也就是把一開始傳入 ask 的 parameters、augment 也都保留著,
所以我們 console.log 會先找到 之前傳的字串
myQuestion(); // console.log(question)
myQuestion(); // "What is closure?"

這個例子比較偏向 functional programming 的概念,把函數回傳另一個函數

順帶一提,這個部分因為 myQuestion 這個 identifer 貼在整個 scope 上面,
所以你可以重複使用 這整塊存好的 code,
並且也只有 myQuestion() 做 expression 可以執行它!
(因為其他人沒有這張貼紙,也不可能改到裡面的東西了。)

說到這邊,484 覺得這個隔離的感覺,有一種 private, 有一種內聚力(Cohesion)很高的味道?


下集待續 ...

註:明天來聊看看其他運用。


上一篇
[day21] YDKJS (Scope) : Hoisting ? let 會 Hoist 嗎 ?
下一篇
[day23] YDKJS (Closure) : 從 Closure 到 Module Pattern
系列文
跟著 YDKJS 作者 Kyle Simpson 打造全新 JavaScript Mindset31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
ayugioh2003
iT邦新手 1 級 ‧ 2019-10-11 02:03:30

他們創造新語言的目的是要抗衡最強勢的語言 Java ,
這也是創造 JavaScript 的目標之一:成功吃掉 Java 或 消失。

這邊跟我的印象不太一樣
我印象中創造 Java 的 Sun,與創造 JavaScript 的 Netscape 反而是合作聯盟的關係
主要是對抗 MicroSoft 的樣子

當時瀏覽器還沒有能夠操作圖形界面的語言,只有 HTML
Netscape 內部因此想要有個語言,能操作畫面上的元素
本來有考慮用 Java,但好像性能的關係就沒採用
但因為要跟 Sun 聯盟,語法上跟 API 就借鑒了很多 Java 的東西
Netscape 瀏覽器也支援 Java 的插件
連名字也從 LiveScript 變成 Java 的形狀了

最後,Sun 在 1995 年將 Oak 改名成 Java
NetScape 也在同一年推出 JavaScript
印象中,當時的說明文件中還說 JavaScript 是 Java 的腳本語言 XD

Ashe Li iT邦新手 5 級 ‧ 2019-10-11 02:57:48 檢舉

這邊可能是我誤解 Kyle 說的 Orz

那我直接拿掉這一段好了,感謝資訊~

我要留言

立即登入留言