ES6 in Depth: Rest Parameters and Defaults

ES6 in Depth: Rest parameters and defaults

今天文章是关于JS函数中更具有表达力的两个特性:剩余参数和默认参数。

剩余参数

创建一个可变的函数作为API是个常见的需求,而这函数可接受任意个参数。例如,String.prototype.concat方法可接受任意数量的字符串参数。利用剩余参数,ES6提供了一种编写可变函数的方式。

为了说明,让我们编写一个简单的可变函数containsAll,用来检查一个字符串是否包含有一定数量的子串。例如,containsAll("banana", "b", "nan") 会返回 true,而containsAll("banana", "c", "nan") 会返回false

这里是一个传统的实现方式:

1
2
3
4
5
6
7
8
9
function containsAll(haystack) {
  for (var i =1; i < arguments.lenght; i++){
    var needle = arguments[i];
    if (haystack.indexOf(needle) == -1 ){
      return false;
    }
  }
  return true;
}

这种实现方式使用了具有魔力的 arguments 对象,它是一个类似数组的对象,包含了传递给函数的所有参数。这代码确实可以达到我们所需求的目标,但是其可读性并不是最佳的。这函数的参数列表中只包括了一个参数haystack,所以如果只看一眼的话,是不可能知道这函数实际上是支持多个参数的。另外,我们必须小心迭代方式,arguments需要是以 1 开始,而不是 0 ,因为 arguments[0] 对应于 haystack。如果我们需要在 haystack 之前或者之后添加额外的参数时,我们不得不要记得更新循环方法。剩余参数 可解决这些问题。下面是一种利用剩余参数的自然的ES6实现方式:

1
2
3
4
5
6
7
8
function containsAll(haystack, ...needles){
  for (var needle of needles){
    if (haystack.indexOf(needle) === -1)I{
      return false;
    }
  }
  return true;
}

这个版本的函数拥有的行为和之前第一个的一样,但是它包含了特殊的...needles语法。让我们看一下containsAll("banana", "b", "nan")是怎么调用此函数的。参数 haystack 如平常一样接受的是传递的第一个参数,值为“banana”。needles之前的省略号表示为一个剩余参数,所有其它的参数会组成一个数组被安排到变量needles中。对于这个例子,needles的值为["b", "nan"]。函数则按正常执行。(注意,我们这里使用了ES6 for-of的循环结构。)

只有函数的最后一个参数可以作为一个剩余参数。在一次调用中,剩余参数之前的参数会和平常一样。所有额外的参数会被放到数组中,并被分配给剩余参数。如果没有额外的参数,剩余参数会简单地被置为空数组;剩余参数不可能会是 undefined

默认参数

通常来说,一个函数并不需要调用所有由调用者传递给的参数,当没有传递参数时,可以智能地使用默认值来作用于这些参数。JS 也有顽固的表单默认参数,当参数没有被传递时会默认设置为default。ES6 推出一种可以指定任意的默认参数的方法。

这里是例子。(其中音符号表示模板字符串,上周已经讨论过了。)

1
2
3
function animalSentence(animals2="tigers", anmals3="bears"){
  return `Lions and ${animals2} and ${animals3}! Oh my!`'
}

对于每个参数,当调用者没有传递值时,包含=的部分是个表达式,会默认将值赋予到参数中。所以,animalSentence() 返回 "Lions and tigers and bears! Oh my!animalSentence("elephants") 返回 "Lions and elephants and bears! Oh my!"animalSentence("elephants", "whales") 返回 "Lions and elephants and whales! Oh my!"

这些微妙的变化来源于 默认参数:

  • 并不像Python, ES6的默认值表达式会在每次函数调用时从左到右执行。这就意味着默认表达式可以使用参数之前的参数值。例如,我们可以让我们的动物句子更有想象力些:
1
2
3
4
5
function animalSentenceFancy(animals2="tigers",
     animals3=(animals2 == "bears") ? "sealions" : "bears")
{
  return `Lions and ${animals2} and ${animals3}! Oh my!`;
}

然后,animalSentenceFancy("bears")会返回"Lions and bears and sealins. Oh my!"

  • 传递 undefined会被认为等价于没有传递参数,因此, animalSentence(undefined, "unicorns") 会返回 "Lions and tigers and unicorns! Oh my!"

  • 当参数传递一个没有值的变量时,其传递的值会被设置为 undefined,所以,

1
function myFunc(a=42, b){ ... }

是允许的,而且其等价于

1
function myFunc(a=42, b=undefined){ ... }

关闭 arguments

我们现在看到,剩余和默认参数可能替代arguments对象的用处,并且,移除 arguments 通常会使得代码更加易读。除了影响可读性,arguments臭名昭著还因为它 影响JS的VM效率

为了使得剩余和默认参数完成取代 arguments,首先第一步需要函数使用剩余或者默认参数,禁止使用 argumentes 对象。尽管支持 arguments 的特性最近不会被移除,但是现在应该尽量避免使用 arguments,而尽可能地使用剩余和默认参数。

浏览器支持

Firefox 从 版本15之后就支持 剩余和默认参数了。

不幸的是,没有其它发布的浏览器版本支持剩余和默认参数。V8 最近支持剩余参数,并且 默认参数 已经提到日程中。JSC 也将 剩余参数默认参数 提到的日程中。

BabelTraceur 解释器都是支持默认参数的,所以可以开始使用它们。

结论

尽管技术上并不允许其它新的行为,但是剩余参数和默认参数可以让JS函数声明更加具有表现力和可读性。欢迎来电!


Note: 感谢 Benjamin Peterson 为 Firefox 实现这些特性,包括他对项目的贡献,同样包括这周的文章教程。

下周,我们将会介绍另外 简单、强大、实现、常用的 ES6 特性。它语法类似于你已经使用的数组和对象,并会生成出一种新的、精确的数组和对象的分离方式。这意味着什么呢?为什么你需要分离对象呢?在下周三加入我们并一同来寻找,来自 Mozilla 工程师 Nick Fitzgerald 展示的 【深入ES6 的去结构化(析构)】。

Comments