iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 24
0
Modern Web

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

[day23] YDKJS (Closure) : 從 Closure 到 Module Pattern

  • 分享至 

  • xImage
  •  

Closure 不能快照(snapshot)值?

今天如果你以快照(snapshot)保留一個值,這樣是 closure 嗎?

No, Closure 和 並沒有關係 。
Closure 抓住(close over) 的是一個變數 ,或是更明確的說,抓住整個 scope-based 裡面的那一個 identifer

這邊是一個具體的例子,你可以先看 line 3
可以想像, myTeacher 這時候 抓住(close over)整個 function object(code)

假設在 line 6 : myTeacher(); ,這時候會印出 "Kyle",
因為 closure 是抓住整個 lexical scope ,代表往上層找 這個 lexical scope 的特性依然還是存在,所以此時印出 "Kyle"。

同理,如果把 teacher = "Suzy" ,此時往外找的對象值被改變了,line 9 就會印出 "Suzy" 。

Closure 不能快照(snapshot)值 , 所以有一個常見的考題:

這邊不是要強調 setTimeout ,而是昨天說 setTimeout 裡面的函式是運用到 Closure 機制,所以可以使用不會報錯。

但是,剛剛有提到 Closure 不能快照(snapshot),
所以當 setTimeout 可以執行的時候,
for 迴圈的 i 已經被更新過了,也就是前一個 teacher = "Suzy" 的例子。

這邊印出來的值都會是 i 被改變之後的值 4

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

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

如何改成 印出 1,2,3 ?

前一個例子跑 for loop 的時候,
只有一個變數可以存 console.log 的值,
同時還要存 for loop 的條件,
所以 for loop 的條件會一直覆蓋 console.log 所需要的值。

那就換個想法,給兩個變數存值。

這邊細部拆分:

很簡單的想法,我們在 closure 的內部存一個變數,
這樣每次迴圈都會有一個 新的 let j , 我們就改成 console.log(j) 的值。

每一次都會產生一個新的 let j ,所以其實背後真實的感覺是

// j   : 1
// j'  : 2
// j'' : 3

有 ES6 let 之後

這時候你應該知道,這三個 i 其實是不一樣的 i 了,
因為 Closure 不能快照(snapshot)一個 value,我們就只能存在多個變數裡面放值。

// i   : 1
// i'  : 2
// i'' : 3

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

沒錯!

Modules (Module Pattern)

開始說 Modules 以前,我們先說 什麼不是 Modules:

Not a Module

描述一下上面的 code 做什麼事 :

  1. 用一些變數存資料 (a set of data )
  2. 用一些 function 當作操作 ( functions operate )
  3. 搜集起來當做一個 logical unit

簡單來說,
其實是做一個 object,讓 namespace 不要污染到外面,
非官方的說:這只是 namespace pattern。

聽起來和 Module Pattern 很像,但為什麼說不是

Module Pattern 需要有 封裝(Encapsulation) 的概念。

能不能把你之前做的事情,加上 隱藏資料和行為的細節
讓別人不能隨意取用你的資料?

註:
封裝(Encapsulation) 是很常見的軟體工程用字,表示能夠 隱藏資料和行為的細節
也就是大家常見 public, private 的字眼。

可以用一個 function 回傳另一個包裝好的 object ,
這是我們的老套路。

但我們以前有提過一個可以創造新的 lexical scope 的方法 : IIFE

把東西全部裝到 IIFE 裡面:

  1. IIFE 只會立即執行一次
  2. 可以視為 單例 (singleton)

這個例子之中,你可以用 workshop.ask 去存取 closure 著的 function (有被 return),

但是不能用 workshop.teacher 去存取 teacher 的值,
也就是成功把 teacher 隱藏起來了。

這也是我們用 Module Pattern 很重要的原因,

  • 保護我們的 teacher 狀態不要被其他程式碼更動,可以追蹤(debug)。

封裝(Encapsulation) 這個概念也符合之前說的最小權限原則

參考: [day20] YDKJS (Scope) : Advanced Scope
如果你讀資工或相關書籍,會有讀過 最小權限原則(Principle of least privilege),
你應該預設把值隱藏起來(keeping everying private),只把最少量真正需要的訊息公開。

如果你按照最小權限原則,你可以處理掉三個問題:

  1. 變數命名重複問題 (naming collision problem)。
  2. 別人不會意外改到你的 code,也不能隨意存取你寫好的東西。
  3. Kyle 認為最重要的: 你可以保護你未來 重構(refactoring) , code 不會因為被其他人使用而不能改動。

Function + Closure = Factory Pattern

當然,除了 IIFE 之外,我們還是可以用之前回傳 funtion object(code) 的方式來做 Moduel。

差別是, IIFE 是單例,也就是大家使用方法都是同一個實例。

用 function 會是儲存在一個變數上,可以產生很多個變數,
也就是多例, a.k.a Factory Pattern

最後補充:以上的 Pattern 都是一種程式語言習慣(idiom)

這些習慣用某些方法或是語言機制(比如 closure),
實作做出來的東西,並不是一個真正在 JavaScript 內規範的東西。

會提到這個,是因為在 ES6,JavaScript 內規範一個相似的新東西 :

ES6 Modules

  1. ES6 Modules 預設 private。
  2. 有 export 才是 public。
  3. 以檔案為單位。
  4. 單例 singleton (你 import 很多次,只會執行一次,大家都是存取同一個 instance )。

Kyle 認為,現在使用 ES6 Modules 要很謹慎,
因為 ES6 Modules 出來之後,很多以前存在的規範,或是根據不同執行環境產生的結果會有所不同,
可能結果並不相容 (主要是 Node.js 上使用 ES6 Modules 的相容問題 )。

Kyle 傾向用工具轉成(transpile) ES6 Modules 的語法,
至少在確定 node 整合完成以前 Kyle 都是這樣。

如果你想知道更多 :
node 想整合舊 Common JS module 和新 ES6 Modules 共用,產生一些還需要解決的問題,
比如很多 Corner case 還要處理,例如:循環引用。

其他風格的 import style :

把 import 視為 「import 一個 identifier」,
import 到現有 NameSpace(line 7 語法) 或是 Scope(line 1 語法) 內。

Kyle 是使用 UMD style modules (universal module definition)
舊的手動創造 Module Pattern


心得 :
從一開始的 lexical scope 到 Closure,
從 Closure 到 Modules 其實都還是 Scope.

希望大家讀 YDKJS scope & closures,這幾天的文章可以有所幫助。


上一篇
[day22] YDKJS (Closure) : Closure 入門: persistent lexical scope referenced data
下一篇
[day24] YDKJS (Objects) : 「this」 是 JavaScript 使用 Dynamic scope 的方法?
系列文
跟著 YDKJS 作者 Kyle Simpson 打造全新 JavaScript Mindset31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言