發表文章

目前顯示的是 四月, 2017的文章

JavaScript async/await 的奇淫技巧

圖片
JavaScript async/await 的奇淫技巧
話說,最新的 ECMAScript 已經引入了 async/await 語法,讓開發者可以更容易控制非同步的程式邏輯,換言之,我們可以減少許多 callback 的使用,讓 JavaScript 這種單線程、事件驅動的程式語言更易讀、好寫。

關於 async/await 的基礎使用,有興趣的人可以參考舊文「JavaScript 好用的 async 異步函數!」,而本文將探討更多實際使用上的小技巧。

另外,瀏覽器不一定有支援 async/await,你可以在新版的 Node.js 上面測試本文的內容。

呼叫 async 函數與一般的函數沒有差別
想像一下,async 函數就是一個在執行後會回傳 Promise 物件的「普通函數」,和一般常見的函數的使用差異,僅僅只是 async 函數在執行後「不是回傳函數執行結果」。這代表我們可以把 async 函數當作一般函數來呼叫使用,用法一模一樣。

async/await 與 Promise 是可以共通的
非常有趣,async 函數與 Promise 其實能夠共通,這代表我們可以玩一些特別的組合技。所以,若要把 async/await 玩得通透,建議你盡量熟悉 Promise 的各種用法。

實現 delay 函數
過去因為單線程和事件驅動的關係,JavaScript 不可能實現一個沒有嚴重副作用的 delay 函數,所以取而代之的是使用 setTimeout() 加上 callback 來實現「一定時間後執行什麼工作」的需要。

不過來到 async/await 的世界後,我們可以一行行描述程式邏輯,無論是不是同步(Synchronous)的程式碼,所以我們可以用 Promise 來包裝 setTimeout(),以實現一個在 async 函數裡可以跑的 delay 函數:

// 實現一個等待函數 const delay = (interval) => { return new Promise((resolve) => { setTimeout(resolve, interval); }); }; const main = async () => { console.log('Starting...…

自幹 JavaScript 的 Tail Call Optimization

圖片
ECMAScript 6 開始,規範中出現了一項被稱為「尾呼叫優化(Tail Call Optimization, TCO)」的優化技術,這讓開發者可以在函數的執行過程中,減少 Stack Frame 的數量,進而提升效能。TCO 尤其是在遞迴這種不停呼叫自己或新函數的工作上,能得到最大的優化效益,能提升遞迴的執行效能如同迴圈一樣。

只不過很可惜的是,截至本文發稿前,大多數瀏覽器及 JavaScript 引擎尚未支援這項技術。但我們還是可以自幹並模擬一個 TCO 的行為,雖然比起語言本身、編譯器(Compiler)及虛擬機(VM)層面的實現,效果差了些,但仍然可以減少 Stack Frame 的數量避免達到 Stack Frame 的數量上限。

什麼時候會啟用尾呼叫優化機制?
如果 JavaScript 引擎有支援,通常一個函數執行到最後一行 return 時,是回傳另一個函數的執行結果,就會啟用 TCO 機制,如:

const f = () => { return 999; }; const g = () => { // 執行並直接回傳 f 函數的執行結果:會啟用尾呼叫優化機制 return f(); }; g();
但要注意的是,回傳的「必定為函數的直接回傳值」,所以下面這些寫法不會啟用 TCO 機制:

// 不會啟用 TCO 機制的設計 const g = () => { return f() + 1; }; // 不會啟用 TCO 機制的設計 const g = () => { let ret = f(); return ret; };
創造一個跑不完的函數
首先我們先創造一個肯定跑不完的遞迴,然後改善它:

const func = (x) => { // 讓他跑 10000000 次 if (x === 10000000) return x; return func(x + 1); }; let ret = func(0);
理論上,如果你直接執行上述程式碼,會得到 stack size 超過上限的錯誤訊息:

RangeError: Maximum call stack size exceeded
簡單模擬實現自己的 TCO 效果
簡單…

實現 JavaScript 的 Memoization

圖片
函數式程式設計(Functional Programming)是近年來越來越被軟體開發者常提及的話題,許多人討論它時,不外乎說其就是在程式設計中引入了數學方法,彷彿有神奇又高深的理論加持一般。事實上,對於一般開發者而言,函數式程式設計比較通俗且直接的好處,就是讓開發者可以在「函數」的層面和維度,進行邏輯或是效能上的優化。所以說,比起命令化的執行程式、管理物件,怎麼去設計和管理函數這件事,就是函數式程式設計所關心的重點。

JavaScript 這語言在設計上,天生就支援 first-class function,這代表函數在 JavaScript 是一種資料型態,可以被當成普通物件傳遞、處理,這讓開發者在使用 JavaScript 時可以時不時引入 Functional Programming 的技巧和概念。

本文將介紹 Functional Programming 中大量被使用的 Memoization 機制,然後我們如何在 JavaScript 中引入並實地使用它,無論你會不會 Functional Programming,這都是一個可以常用於日常開發中的優化技巧。

Memoization 是什麼?
用一般程式開發的說法就是快取(Cache)機制,只不過 Memoization 是針對「函數」進行快取。快取的好處在於我們只要執行過一次工作後,之後在執行相同工作前,就能提前知道執行結果為何,所以我們可以不用「真正的」去執行工作,而直接取用執行結果就好,可大量提升程式執行的效能。

同樣快取概念套用在函數上,若我們給予特定「輸入(Input)」到一個函數中,而該函數會回傳一個特定的「輸出(Output)」,理論上函數執行一次後,下次再使用這個函數時,只要「輸入」和過去一樣,我們就能提前知道結果。

而這樣對函數進行的快取機制,就是所謂的 Memoization。

只能使用在純函數
你可能會聽一些人說,只有「純函數」才能引入快取機制,然後開始討論數學上所謂函數的定義,然後你就聽到昏了,後面在講什麼你就都聽不進去了。

但如果撇除函數的數學定義,若白話來說,能被快取的東西,就是能被預測的東西,這代表函數的執行結果也要能被預測,也就是一樣的輸入值,就會有一樣的輸出結果。

所以,如果一個函數每次執行,代入的輸入值一樣,但回傳結果卻是可能不一樣,這就不是一個純函數,像是取亂數的函數就屬…