JavaScript函數語言程式設計之副作用

磚用冰西瓜發表於2019-03-04

更多相關內容見部落格 github.com/zhuanyongxi…

概念:

副作用是在計算結果的過程中,系統狀態的一種變化,或者與外部世界進行的可觀察的互動

上文中的純函式的概念很嚴格,這個副作用的概念也是。它的要求很高,概括的講,只要是跟函式外部環境發生的互動就都是副作用。從“副作用”這個詞語來看,它更多的情況在於“改變系統狀態”。

教程中列舉的一些副作用:

  • 更改檔案系統
  • 往資料庫插入記錄
  • 傳送一個http請求
  • 可變資料
  • 列印/log
  • 獲取使用者輸入
  • DOM查詢
  • 訪問系統狀態

如果完全沒有副作用,那我們的程式碼就是單純的跑一遍浪費了一點電而已,除此之外什麼都沒有發生,這樣的話我們寫程式碼就沒有意義了。所以,在JS中,我們的目的不是完全消除副作用注1,而是避免那些不應該出現的副作用。

JS原生的方法中,map就很函式式,他會返回一個新的陣列,不會改變原陣列。而pop這種方法就很不好,它在操作了陣列之後,也改變陣列本身。

所以當我們要使用那些有副作用的方法寫純函式的時候,記得做一次深拷貝:

例1

const myPop = x => {
  let [...y] = x;
  return y.pop();
}
複製程式碼

使用一個固定的共享狀態或者呼叫一個純函式不算是副作用,例子如下:

例2

const a = 5;
function A(b) {
  return a + b;
}
A(5);
複製程式碼

呼叫純函式的例子:

例3

function foo(x) {
  return bar(x);
}

function bar(y) {
  return y + 1;
}

foo(1);
複製程式碼

雖然不算是副作用,可更加推薦的方式是把函式bar用引數的方式傳進來,這樣就做到了解耦,用起來更加的方便:

例4

function foo(fn, x) {
  return fn(x);
}

function bar(y) {
  return y + 1;
}

foo(bar, 1);
複製程式碼

如果使用柯里化的方式,會更加的清爽和方便:

例5

function foo(fn) {
  return function(x) {
    return fn(x);   
  }
}

function bar(y) {
  return y + 1;
}

foo(bar)(1);
複製程式碼

這個例子依然存在一個會令我們感到不安的地方,那就是bar可能會被修改。例如:

例6

function foo(fn, x) {
  return fn(x);
}

function bar(y) {
  return y + 1;
}
bar = undefined;
foo(bar, 1);
複製程式碼

當然我們平時很少會大腦抽筋在全域性作用域下寫出一個bar = undefined來讓我們的系統出錯,這更可能在某個有副作用的函式內出現這種情況。這就是為什麼我們要避免副作用。這個情況在ES6中會得到改善,例如:

例7

const foo = function(fn, x) {
  return fn(x);
}

const bar = function(y) {
  return y + 1;
}
bar = undefined;	// error
foo(bar, 1);
複製程式碼

個人建議用const的方式,這樣更加的安全,即便出錯也可以快速定位。

註釋:

  • 注1: 如果繼續深入學習,對與上面列出的一些副作用,函式式還有一種延遲執行的方式(IO容器)來使這些操作變純。

參考資料:

相關文章