更多相關內容見部落格 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容器)來使這些操作變純。
參考資料: