函数的增强


ES6 中对函数的增强,在之前的文件中多少都有看过了,例如〈增强的数值与字符串〉看过的,函数中可以使用...运算符,用来将多于参数的实参收集在一个数组中:

> 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
>

ES6 若是在严格模式下,支持 Tail Call Optimization,只要在调用下个函数前,当前的函数不需要保留任何状态,也就是所谓的 Proper Tail Call,ES6 规定就要进行最佳化,不需要使用一个新的 Stack frame,只需要重复使用目前的 Stack frame 就可以了。

如果你不知道什么是 Proper Tail Call,或者没听过 Tail Call Optimization,那表示你可能没遇过相对应的问题,或者很少写递归的东西,许多语言也不支持 Tail Call Optimization,就暂时记得这边曾经提过这个东西就可以了。

如果真的想知道的话,可以看一下〈Tail call〉或者参考〈递归的美丽与哀愁〉。


展开阅读全文