在先前的章節中,我們在說明究竟什麼是純函式時,不斷提到一個關鍵字「副作用(Side Effects)」,且在純函式中,我們極力避免產生副作用。
那麼什麼是副作用呢?在這個章節中,我們會很仔細地來討論何謂副作用,及在 JavaScript 中常見的副作用程式碼範例。
關於副作用,我們首先來看看維基百科對此的定義:
在電腦科學中,在程式的執行過程中,修改到全域、區域變數狀態、有傳參考特性之參數或是物件等值的改變,都可以稱的上是副作用。
然而牽涉到前端開發的範疇,副作用的範圍可能還會包含:
接著讓我們針對這三種類型的副作用進行更深入的討論吧!
在 Google 任職長達八年之久的軟體工程師 Philip Walton 曾撰寫了一篇部落格文章《Side Effects in CSS 》來說明沒有導入設計模式的 CSS 可能會遇到所謂:選擇器名稱間的衝突(Naming collisions)與堆疊(Subtree matches)。
假設我們不小心創造了兩個相同的選擇器名稱:
.content {
marging: 20px 40px;
}
.content {
marging: 40px 40px;
}
此時就會因為下覆蓋上的語言特性導致,上方的 .content
完全被覆蓋過去,而造成上方程式碼的無效。
事實上 CSS 所帶來的副作用也可以透過導入通用類別的設計模式來解決,通用類別的設計理念(針對樣式的最小任務定義樣式並重複使用),倒是跟純函式的單一任務原則有一些重疊之處,由於本系列文章旨在探討 Funcitonal Programming,這邊故不針對 CSS 的副作用進行更深入的討論。
在初學前端框架時,大家一定都有聽過「避免直接針對 DOM 進行操作」,這是為什麼呢?了解副作用的概念後,可能能更理解為什麼這些框架在文件中都會提醒開發者們 「避免直接針對 DOM 進行操作」了。
試想若是我們直接透過 documant.querySelector
語法,一直頻繁操作某些節點,且該節點下還會延伸我們所無法預期數量之節點的狀況下,由於父節點被修改了,所以瀏覽器就必須重新渲染一次,才能讓底下的子節點也即時的共享到狀態。
這個情境是不是聽起來很熟悉?沒錯!當節點共用的節點「被更改」到了,其他相依的節點也會依此被修改到,這不就是副作用的概念嗎?
為了避免瀏覽器一直被重複的觸發渲染機制,在大部分的前端框架中,都會使用虛擬 DOM( Virtual DOM )來進行畫面的操作,在系列文章最後的篇幅中,我們也會針對 DOM 與 Functional Programming 的應用進行更深入的討論。
相對於前兩者的副作用,JavaScript 的副作用可以說更好理解,不論是全域或是區域的變數發生改動,甚至是物件中參考物件值的改變,都可以被視為副作用。
讓我們來複習前面章節所提到的例子:試想看看如果我們現在有一個情境,果園的農夫正在收成,由於水果採摘下來會因為各種因素導致有一成的水果無法販賣,所以我們函式需要一個函式將農夫收成的所有品項乘以 0.9 才是會是倉庫中最終的庫存量,此時我們可能會有哪些作法呢?
const appleAmount = 10;
let appleStockAmount;
function calcAppleStockAmount () {
appleStockAmount = appleAmount * 0.9;
}
calcAppleStockAmount();
// 9
在上方的例子中,我們針對 appleStockAmount
進行了重新指值(賦值)的動作,此時就是一種「副作用」。
在 FP 實踐純函式最重要的原因,就是要消弭掉副作用對於程式碼的不良影響,以避免元件莫名被影響,但卻又難以被測試、觀察出來。
這也是先前我們提到,為什麼在 FP 中我們不使用 Mutable 資料的原因,因為 Mutable 不但會產生預期外的副作用,也有可能連帶影響到其他元件的狀態。
因此在 FP 及 Pure Function 中,我們只使用 Immutable Data 且避開副作用的產生。
現在我們漸漸把 FP 的拼圖拼起來了!
從 JavaScript 的型別、傳參考特性, Mutation、Immutable 的概念 ,到純函式的使用情境與規範,接著讓我們來看看什麼是 Impure Function 吧!那麼我們下一章節見!
Philip Walton: Side Effects in CSS