函数参数详解

在开发过程中,有很多时候,开发者没搞清楚函数参数,而随意传递参数,这可能会给函数增加很多不必须的参数,那么这次来细说一下这个函数使用过程的参数的使用。

先看下面这个例子:

1
2
3
4
var b = 3;
function(a = 42, b = a + b + 5){
// ..
}

b = a + b + 5 在参数b(=右边的b,而不是函数外的那个)的TDZ中访问b,所以会出错。而访问a却没出错,因为此时刚好跨出了参数a的TDZ。
在ES6中,如果参数被省略或者值为undefined,则取该参数的默认值:

1
2
3
4
5
6
7
8
9
function foo( a = 42, b = a + 1){
console.log(a, b);
}
foo(); // 42 43
foo(undefined) // 42 43
foo(5) // 5 6
foo(void 0, 7) // 42 7
foo(null) // null 1

表达式中 a + 1 中的null被强制类型转换为0.

对ES6中的参数默认值而言,参数被省略或被赋值为undefined效果都一样,都是取该参数的默认值。然而某些情况下,它们之间还是有区别的:

1
2
3
4
5
6
7
8
9
10
11
12
function foo(a = 42, b = a + 1){
console.log(
arguments.length,
a, b,
arguments[0],
arguments[1]
)
}
foo(); // 0 42 43 undefined undefined
foo(10); // 1 10 11 10 undefined
foo(10, undefined); // 2 10 11 10 undefined
foo(10, null); // 2 10 null 10 null

虽然参数a和b都有默认值,但是函数不带参数时,arguments数组为空。
相反,如果向函数传递undefined值,则arguments数组中会出现一个值为undefined的单元,而不是默认值。

ES6参数默认会导致arguments数组和相对应的命名参数之间出现偏差,ES5也会出现这种情况:

1
2
3
4
5
6
function foo(a){
a = 42;
console.log(arguments[0]);
}
foo(2); // 42 (linked)
foo(); //undefined (not linked)

向函数中传递参数是,arguments数组中的对应单元会和命名参数建立关联(linkage)以得到相同的值。相反,不传递参数就不会建立关联。

但是,在严格模式中并没有建立关联这一说:

1
2
3
4
5
6
7
function foo(a){
"use strict";
a = 42;
console.log(arguments[0]);
}
foo(2); // 2 (not linked)
foo(); // undefined (not linked)

因此,在开发中不要依赖这种关联机制。实际上,它是JS语言引擎底层实现的一个抽象泄漏(leaky abstraction),并不是语言本身的特性。

arguments数组已经被废止(特别是在ES6引入剩余参数…之后),不过它并非一无是处。

在ES6之前,获得函数所有参数的唯一途径就是arguments数组。此外,即使将命名参数和arguments数组滥用也不会出错,只需遵守一个原则,即「不要同时访问命名参数和其对应的arguments数组单元」。

1
2
3
4
function foo(a){
console.log( a + arguments[1]); //安全
}
foo(10,32); // 42