天天看點

快速了解函數式程式設計

說明

【跟月影學可視化】加餐篇學習筆記。

兩種程式設計範式:指令式與聲明式

程式設計範式有兩種,分别是指令式(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,
  ...
}      

繼續閱讀