函数参数值
另一个违反TDZ的例子可以在ES6的参数默认值(参见本系列的 ES6与未来)中看到:
var b = 3;
function foo( a = 42, b = a + b + 5 ) {
// ..
}
在赋值中的b
引用将在参数b
的TDZ中发生(不会被拉到外面的b
引用),所以它会抛出一个错误。然而,赋值中的a
是没有问题的,因为那时参数a
的TDZ已经过去了。
当使用ES6的参数默认值时,如果你省略一个参数,或者你在它的位置上传递一个undefined
值的话,就会应用这个默认值。
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
值之间没有区别。然而,有一个办法可以在一些情况下探测到这种区别:
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
参数,在数组argument
中就会为这个参数存在一个元素,但它将是undefined
,并且与同一值槽中的被命名参数将被提供的默认值不同。
虽然ES6参数默认值会在数组arguments
的值槽和相应的命名参数变量之间造成差异,但是这种脱节也会以诡异的方式发生在ES5中:
function foo(a) {
a = 42;
console.log( arguments[0] );
}
foo( 2 ); // 42 (链接了)
foo(); // undefined (没链接)
如果你传递一个参数,arguments
的值槽和命名的参数总是链接到同一个值上。如果你省略这个参数,就没有这样的链接会发生。
但是在strict
模式下,这种链接无论怎样都不存在了:
function foo(a) {
"use strict";
a = 42;
console.log( arguments[0] );
}
foo( 2 ); // 2 (没链接)
foo(); // undefined (没链接)
依赖于这样的链接几乎可以肯定是一个坏主意,而且事实上这种连接本身是一种抽象泄漏,它暴露了引擎的底层实现细节,而不是一个合适的设计特性。
arguments
数组的使用已经废弃了(特别是被ES6...
剩余参数取代以后 —— 参见本系列的 ES6与未来),但这不意味着它都是不好的。
在ES6以前,要得到向另一个函数传递的所有参数值的数组,arguments
是唯一的办法,它被证实十分有用。你也可以安全地混用被命名参数和arguments
数组,只要你遵循一个简单的规则:绝不同时引用一个被命名参数 和 它相应的arguments
值槽。如果你能避开那种错误的实践,你就永远也不会暴露这种易泄漏的链接行为。
function foo(a) {
console.log( a + arguments[1] ); // 安全!
}
foo( 10, 32 ); // 42