解構賦值 — 設計函數時的小技巧 JavaScript
透過解構賦值,我們可以輕鬆地把函數中的各種方法、變數傳出,也可以把多個參數變成參數陣列而易於操作。
解構賦值(Destructuring assignment)
解構賦值是 ES2015 的特性,在 9102 年了,這個 4 年前的東西老實說已經不算新了,是一個不學不會怎麼樣,學了很不一樣的特性。
陣列與物件皆有結構賦值的特性,我們舉例如下:
let arr = ['Pucci','Dio','Jotaro'];
let Dio = arr[1];
// or
let [Pucci] = arr;
// Puccilet arr2 = [...arr,'Gioruno'];
//['Pucci','Dio','Jotaro','Gioruno']
可以發現 Pucci
可以因為被包覆在[]裡頭,所以依照索引位置賦值了 arr[0]
的值,如果你各位最近瘋狂在寫 React Hooks 應該也是相當有感。
const [state,setState] = useState(false) //跟剛剛Pucci抽出來差不多
上述是陣列的解構例子,再來講講物件的解構。
let DioInfo= {name:'Dio',stand:'zawarudo',height:195};
let { stand } = DioInfo;
//zawarudo
概念上與陣列大同小異,不過再抽取出 Value 的時候比陣列靈活一點,不是透過 index,而是透過 key。
解構賦值在設計函數的幫助是什麼?
這邊我會分兩個部分來講,分別是
- 函數接收 arguments 的部分
- 函數 return 時的部分。
前者可以使用陣列的解構賦值靈活的操作參數,後者則是透過物件的解構賦值讓接口更明確。那我們開始吧!
函數回傳的解構 —物件解構
很多時候我們設計的函數可能會是一個很大的函數,其中可能有多個方法與變數等等的,當然我們可以設計成一個 Class 類,讓使用者實例化之後再決定要使用什麼方法、變數。
不過我們可以再想的更簡單一點,讓使用者拿他所想拿的。
function doSomething(arguments){
let someState = false; const method1 = ()=>{/* */};
const method2 = ()=>{/* */};
const method3 = ()=>{/* */};return {someState, method1, method2, method3};
}
將來使用者就可以只拿他所想要的:
// case 1
function case1(){
const {someState ,method1, method2} = doSomething(/**/)
method1();
doElseTast()
methid2();
return someState;
}// case 2
function case2(){
const { method2 } = doSomething(/**/);
method2()
}
這樣的好處是什麼?
- 可以很明確的針對要的方法抽取出來
- 不用一狗票拿出來做操作
比如像是這樣一個反例
//一個只打算用method1的狀況 const do = doSomething(/**/); // do擁有一狗票的 property do.method1();
+ do.method2(); //後來同事a加上去
+ do.someState++; //同事b為了方便加上去
很明顯有點不符合最小權限原則,也可以說這整個函數行為經過這段 hardcode 的歷史後意義上已經不一樣了。
補充 — 我不想要變數名被物件的 key 限制欸?
簡單,不就改名罷了!
我們用上面的例子舉例:
const {method1:stand, method2:proud} = doSomething(/**/)
stand() // result of method1 call
method1 // Uncaught ReferenceError:
接收參數的解構 — 陣列解構
一般來說我們寫函數會清晰的定義傳進來的參數順序,透過原本設定的變數名去對參數做操作。
function SumOfTwo(var1,var2){
return var1+var2;
}
console.log(SumOfTwo(11,13));
// 24
這種做法很普遍,也沒有什麼好抨擊的,純粹就是在某些情況下會稍微不方便。
如果我們要設計一個十個數字的總和、一百個數字的總和,這個時候就變成要var1
、var2
、…、var100
。
當然我們可以變換思考,強迫使用者傳陣列進來:
function SumOfAll(args){
// args <Array>
return args.reduce((a,b)=>a+b);
}
其實這樣子的做法已經接近我最終想講的了,至少我們現在能保證不管多少個數字都能計算出總和。
加一點解構的魔法吧!
const SumOfAll = (...args)=> args.reduce((a,b)=>a+b);SumOfAll(1,2,3);//6
SumOfAll(11,13);//24
接口用起來跟 SumOfTwo
一樣,我們把參數轉換為參數陣列,幫使用函數的人做掉化為陣列這件事。
參數解構好處
這種對參數解構好處在撰寫一個中間層的函數時相當有幫助,比如我曾經在某個專案裡頭撰寫了一個更安全的 addEventListener
的 React Hooks(useEventTarget)。
因為 addEventListener 可以接收兩個或三個參數,所以我希望能做一個跟原生一模一樣的接口。
基本思路:
const myAddEvent = target=> (...args)=>{ /* Do something like... const cbRef = args[1] */
target.addEventListener(...args);
}
順帶一提,這樣就能先在函數內部拿到回調函數的 reference ,儘管是匿名函數。
主動解構參數而獲得參數陣列的做法,會比起操作 function 的 arguments
稍微可以理解一點,畢竟操作一個沒有宣告的東西還是毛毛的。
結語
透過解構的概念我們可以從兩個面向來提升撰寫 JavaScript 時的思維,一個是設計函數的角度,另一個是使用函數的角度。
優秀的函數擁有強大的擴展性(scalability),讓我們不用在每次更新函數功能的時候又得強迫使用者換一種寫法適應新的接口。比如文內提到的 SumOfAll
同時可以表達 SumOfTwo
或是可能未來變成 SumOfThree
等等,接口不變,但接受更多的參數。
在使用三方的函式庫時,我們也可以只拿出對應的方法出來,在各種狀況使用各別的方法,有點像是 :
import Lib from 'lib';
//vs
import {add, sum} from 'lib';