函式的增強

August 12, 2022

ES6 以後對函式的增強,在之前的文件中多少都有看過了。

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〉或者參考〈遞迴的美麗與哀愁〉。

分享到 LinkedIn 分享到 Facebook 分享到 Twitter