第五章:文法 - 函数参数值

优质
小牛编辑
117浏览
2023-12-01

函数参数值

另一个违反TDZ的例子可以在ES6的参数默认值(参见本系列的 ES6与未来)中看到:

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

在赋值中的b引用将在参数b的TDZ中发生(不会被拉到外面的b引用),所以它会抛出一个错误。然而,赋值中的a是没有问题的,因为那时参数a的TDZ已经过去了。

当使用ES6的参数默认值时,如果你省略一个参数,或者你在它的位置上传递一个undefined值的话,就会应用这个默认值。

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

注意: 在表达式a + 1null被强制转换为值0。更多信息参考第四章。

从ES6参数默认值的角度看,忽略一个参数和传递一个undefined值之间没有区别。然而,有一个办法可以在一些情况下探测到这种区别:

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

即便参数默认值被应用到了参数ab上,但是如果没有参数传入这些值槽,数组arguments也不会有任何元素。

反过来,如果你明确地传入一个undefined参数,在数组argument中就会为这个参数存在一个元素,但它将是undefined,并且与同一值槽中的被命名参数将被提供的默认值不同。

虽然ES6参数默认值会在数组arguments的值槽和相应的命名参数变量之间造成差异,但是这种脱节也会以诡异的方式发生在ES5中:

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

如果你传递一个参数,arguments的值槽和命名的参数总是链接到同一个值上。如果你省略这个参数,就没有这样的链接会发生。

但是在strict模式下,这种链接无论怎样都不存在了:

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

依赖于这样的链接几乎可以肯定是一个坏主意,而且事实上这种连接本身是一种抽象泄漏,它暴露了引擎的底层实现细节,而不是一个合适的设计特性。

arguments数组的使用已经废弃了(特别是被ES6...剩余参数取代以后 —— 参见本系列的 ES6与未来),但这不意味着它都是不好的。

在ES6以前,要得到向另一个函数传递的所有参数值的数组,arguments是唯一的办法,它被证实十分有用。你也可以安全地混用被命名参数和arguments数组,只要你遵循一个简单的规则:绝不同时引用一个被命名参数 它相应的arguments值槽。如果你能避开那种错误的实践,你就永远也不会暴露这种易泄漏的链接行为。

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