函式的增強
August 12, 2022ES6 以後對函式的增強,在之前的文件中多少都有看過了。
Destructuring、Rest 與 Spread
例如〈增強的數值與字串〉看過的,函式中可以使用 ...
運算子,用來將多於參數的引數收集在一個陣列中:
> function some(a, b, ...others) {
... console.log(a);
... console.log(b);
... console.log(others);
... }
undefined
> some(10);
10
undefined
[]
undefined
> some(10, 20);
10
20
[]
undefined
> some(10, 20, 30);
10
20
[ 30 ]
undefined
> some(10, 20, 30, 40);
10
20
[ 30, 40 ]
undefined
>
這可以用來取代函式中的 arguments
,在其他語言中,這個特性可能被稱為不定長度引數,規則也類似,...
只能用在最後一個參數,不能有兩個以上的 ...
。
反過來,如果一個物件是可迭代的,在它前面可以放置 ...
,這時稱其為 Spread 運算子,與函式結合使用的時候,可以將可迭代物件的元素,逐個分配給對應的參數,例如:
> some(...[1, 2, 3, 4]);
1
2
[ 3, 4 ]
undefined
>
而在〈增強的數值與字串〉中也看過,當函式使用標記模版時,會是一個函式的特殊呼叫形式,詳請看參考該文件,這邊不再說明了。
在〈Destructuring、Rest 與 Spread 運算〉中看過解構語法,也看過函式中,在參數設置上也可以使用解構語法,那時還玩了個函數式風格的範例:
function sum([head, ...tail]) {
return head ? head + sum(tail) : 0;
}
console.log(sum([1, 2, 3, 4, 5])); // 15
話說,透過 Rest 與 Spread,也可以寫出底下有趣的函式呢!不過不鼓勵這麼寫啦!
function sum(head, ...tail) {
return head ? head + sum(...tail) : 0;
}
console.log(sum(...[1, 2, 3, 4, 5])); // 15
函式的特性/參數
ES6 以後,每個函式實例都會有個 name
特性,用來指出函式的名稱,其實這個特性在 ES6 之前已經被廣泛使用,只不過在 ES6 以後才標準化。
> function f() {}
undefined
> f.name;
'f'
> (function() {
... }).name;
''
> let f2 = function() {};
undefined
> f2.name;
'f2'
> let f3 = f2;
undefined
> f3.name;
'f2'
>
在 ES6 之前,函式的參數無法設置預設值,若想要有預設值的效果,通常會透過偵測參數是否為 undefined
來達成,ES6 以後,函式的參數可以指定預設值了:
> function doSome(a, b, c = 10) {
... console.log(a, b, c);
... }
undefined
> doSome(1, 2);
1 2 10
undefined
> doSome(1, 2, 3);
1 2 3
undefined
>
參數的預設值,每次都會重新運算,這可以避免其他語言中有預設值,然而預設值持續被持有的問題(像是 Python):
> function f(a, b = []) {
... b.push(a);
... console.log(a, b);
... }
undefined
> f(1)
1 [ 1 ]
undefined
> f(1)
1 [ 1 ]
undefined
> f(1, [1, 2, 3])
1 [ 1, 2, 3, 1 ]
undefined
>
參數的預設值,也可以指定運算式(個人覺得應該避免使用,除非有很好的理由):
> function f(y = x + 1) {
... console.log(y);
... }
undefined
> let x = 10;
undefined
> f();
11
undefined
> x = 20;
20
> f();
21
undefined
>
> f2(1);
1 2
undefined
> f2(10);
10 11
undefined
>
理論上,你只能把有預設值的參數寫在參數列的後面,不過,要玩也是可以(也是不鼓勵的寫法)…
> function f(a = 10, b) {
... console.log(a, b);
... }
undefined
> f(0, 2);
0 2
undefined
> f(undefined, 20);
10 20
undefined
>
函式實例有個 length
特性,可以用來取得定義函式時參數的個數,不過,在使用了 ...
Rest 運算,或者是指定了預設值的參數,是不會計入 length
的:
> function f(a, b) {}
undefined
> f.length;
2
> function f2(a, b = 10) {}
undefined
> f2.length;
1
> function f3(a, ...b) {}
undefined
> f3.length;
1
>
Tail Call Optimization
ES6 若是在嚴格模式下,支援 Tail Call Optimization,只要在呼叫下個函式前,當前的函式不需要保留任何狀態,也就是所謂的 Proper Tail Call,ES6 規定就要進行最佳化,不需要使用一個新的 Stack frame,只需要重複使用目前的 Stack frame 就可以了。
如果你不知道什麼是 Proper Tail Call,或者沒聽過 Tail Call Optimization,那表示你可能沒遇過相對應的問題,或者很少寫遞迴的東西,許多語言也不支援 Tail Call Optimization,就暫時記得這邊曾經提過這個東西就可以了。
如果真的想知道的話,可以看一下〈Tail call〉或者參考〈遞迴的美麗與哀愁〉。