說明
【跟月影學可視化】加餐篇學習筆記。
兩種程式設計範式:指令式與聲明式
程式設計範式有兩種,分别是指令式(Imperative)和聲明式(Declarative)。
- 指令式:強調做的步驟也就是怎麼做
- 聲明式:強調做什麼本身,以及做的結果。
程式設計語言也可以分成指令式和聲明式兩種類型
- 指令式:可以分成過程式和面向對象
- 聲明式:可以分成邏輯式和函數式
JavaScript 同時擁有指令式和聲明式的特征。
指令式的實作代碼:
let kaimo = [1, 2, 3, 4, 5, 6];
let tempArr = [];
for(let i = 0; i < kaimo.length; i++){
tempArr.push(kaimo[i] * 2);
}
聲明式的實作代碼:
let kaimo = [1, 2, 3, 4, 5, 6];
const double = x => x * 2;
kaimo.map(double);
函數式與純函數
函數是對過程的封裝,但函數的實作本身可能依賴外部環境,或者有副作用(Side-effect)。所謂函數的副作用,是指函數執行本身對外部環境的改變。我們把不依賴外部環境和沒有副作用的函數叫做純函數,依賴外部環境或有副作用的函數叫做非純函數。
例子1:add 是一個純函數,它的傳回結果隻依賴于輸入的參數,與調用的次數、次序、時機等等均無關。
function add(x,) {
return x + y;
}
例子2:getId 是一個非純函數,它的傳回值除了依賴于參數 id,還和外部環境(文檔的 DOM 結構)有關。
function getId(id) {
return document.getElementById(id);
}
例子3:join 也是一個非純函數,它的副作用是會改變輸入參數對象本身的内容。
funciton join(arr1,) {
arr1.push(...arr2);
return arr1;
}
純函數的優點
- 易于測試:純函數不需要依賴外部環境
- 可并行計算(時序無關)
- 在浏覽器中,可以利用 Worker 來并行執行多個純函數
- 在 Node.js 中,可以用 Cluster 來實作同樣的并行執行
- 使用 WebGL 的時候,純函數有時候還可以轉換為 Shader 代碼,利用 GPU 的特性來進行計算。
- 有良好的 Bug 自限性:純函數不會依賴和改變外部環境,是以它産生的 Bug 不會擴散到系統的其他部分
函數式程式設計範式與純函數
盡可能多設計純函數,少設計非純函數,這樣能夠提升系統的可測試性、性能優化空間以及系統的可維護性。
如何讓系統的純函數盡可能多,非純函數盡可能少呢?
答案是用函數式程式設計範式。
例子:實作一個子產品,用它來操作 DOM 中清單元素,改變元素的文字顔色
// 設定一個 DOM 元素的文字顔色
function setColor(el,){
el.style.color = color;
}
// 批量設定
function setColors(els,){
els.forEach(el => setColor(el, color));
}
上面兩個函數都改變了外部環境(DOM)是以它們是兩個非純函數。
下面實作一個 batch 高階函數 (High Order Function)來優化。
所謂高階函數,是指輸入參數是函數,或者傳回值是函數的函數。
function batch(fn){
return function(target, ...args){
if(target.length >= 0){
return Array.from(target).map(item => fn.apply(this, [item, ...args]));
}else{
return fn.apply(this, [target, ...args]);
}
}
}
這樣子產品就可以減少為一個非純函數。
function setColor(el,){
el.style.color = color;
}
let setColors = batch(setColor);
高階函數與函數裝飾器
如果輸入參數和傳回值都是函數,這樣的高階函數又叫做函數裝飾器(Function Decorators)。
當一個高階函數是用來修飾函數本身的,它就是函數裝飾器。也就是說,它是在原始函數上增加了某些帶有輔助功能的函數。
function deprecate(fn, oldApi,) {
const message = `The ${oldApi} is deprecated. Please use the ${newApi} instead.`;
return function(...args) {
console.warn(message);
return fn.apply(this, args);
}
}
// 引入要廢棄的 API
import {foo, bar} from './foo';
...
// 用高階函數修飾
const _foo = deprecate(foo, 'foo', 'newFoo');
const _bar = deprecate(bar, 'bar', 'newBar');
// 重新導出修飾過的API
export {
foo: _foo,
bar: _bar,
...
}