在javascript中使用rest参数和spread运算符

时间:2013-12-12 10:47:04

标签: javascript ecmascript-6 ecmascript-5 spread-syntax

将在ECMAScript 6中添加的rest参数的用途是什么?

例如,在ECMAScript 5中,您可以执行以下操作以从第二个元素开始获取参数数组:

// ES 5
store('Joe', 'money');
store('Jane', 'letters', 'certificates');
function store(name) {
  var items = [].slice.call(arguments, 1); //['money'] in first case
  items.forEach(function (item) {
    vault.customer[name].push(item);
  });
}

,这将等同于ECMAScript 6中的以下代码:

// ES 6
store('Joe', 'money');
store('Jane', 'letters', 'certificates');
function store(name, ...items) {
  items.forEach(function (item) {
    vault.customer[name].push(items)
  });
}

它们之间的差异只是语法还是存在性能问题?

也适用于点差运算符(...)

//in ecmascript5
var max = Math.max.apply(null, [14, 3, 77]);
//but in ecmascript6
var max = Math.max(...[14, 3, 77]);

这只是语法更改还是性能问题?

6 个答案:

答案 0 :(得分:19)

  

它们之间的差异只是语法还是存在性能问题?

两者,还有更多...

休息参数:

  1. 在其他语言(Ruby,Python)中是已知成语
  2. 更易于阅读并维护(与slice相比)。
  3. 初学者更容易理解
  4. 可以(并且可能会)导致更好的性能,因为引擎可以进行优化。
  5. 工具更友好,因为它们可以静态分析。

答案 1 :(得分:18)

除了@kangax的回复之外,我还会详细说明,在调用arguments对象时,性能和正确性会出现问题。如果将arguments对象传递给另一个函数,则会向其传递修改范围中相应局部变量的权利

function foo(arg) {
    bar(arguments);
    return arg;
}

function bar(args) {
    args[0] = 20;
}

foo(10) // 20

此功能的存在使函数内的局部推理无效,这使得JIT优化更加困难并且引入了某些安全隐患。专为速度而设计的程序必须使用比Array.prototype.slice.call(arguments)惯用语更复杂的样板代码解决此问题,并且在安全上下文中,必须严格禁止arguments的传递。

消除使用arguments对象提取可变参数的需要是新语法的众多优势之一。

答案 2 :(得分:4)

在您给出的示例中,您直接传递对象文字。虽然仍然是语义的,但它似乎与扩展运算符的应用类似。 IMO,当列出每个参数会破坏代码的可读性(以及替换传统的push,splice,concat方法时的可维护性)时,扩展运算符的值变得明显。

使用点差运算符

let nums = [1.25,5.44,7.15,4.22,6.18,3.11];   

function add(a, b, ...nums){
  let sum = parseInt(a + b);
  nums.forEach(function(num) {
    sum += num;
  })
  return parseInt(sum);
}

console.log(add(1, 2, ...nums)); //30

ES6Fiddle demo(由traceur-compiler编译)

没有传播运营商

var nums = [1.25,5.44,7.15,4.22,6.18,3.11];   

function add(a, b, nums){
  var sum = parseInt(a + b);
  nums.forEach(function(num) {
    sum += num;
  })
  return parseInt(sum);
}

console.log(add(1, 2, nums)); //30

<强> JSFiddle demo

最后,我认为使用扩展运算符不会改善或阻碍性能,但它确实提供了改进的代码可读性,并且可以说是可维护性。遗憾的是,ES6尚未得到广泛实施,我将断断续续地推测,浏览器支持需要一段时间才能实现。

有趣的事实:PHP 5.6引入了与 variadic functions 类似的功能,这将使func_get_args()变得多余。

答案 3 :(得分:3)

我认为rest参数的主要区别是:

  • ECMA5参数中的参数类似于数组而不是数组,因此数组常规方法不可用,但在ECMA6参数中是一个数组。
  • 我认为在ECMA5代码示例中构建项目数组会使代码变慢,因为实例化了新数组。

答案 4 :(得分:3)

当您在函数参数定义中使用点时,rest参数将收集单个参数到数组中,而当使用function call中的点时,展开运算符将数组扩展为单个参数。

如果您想将单个参数收集到数组中,可以在函数参数定义中使用rest运算符。

let sum = (...numbers) => {
  let result = 0;

  numbers.forEach(function(n){
  result += n;
  });

  return result;
};

console.log(sum(1,2,3));

在嵌套函数中处理arguments对象变得非常棘手,让我们访问下面的情况,我们的内部函数需要访问传递的arguments对象。

如果我们的inner function filterNumbers需要访问参数,则必须将其存储在variable之上,然后再传递它,因为每个函数都有自己的arguments对象,这是一个类似的数组对象

function sumOnlyNumbers() {
  var args = arguments;
  var numbers = filterNumbers();
  return numbers.reduce((sum, element) => sum + element);
    function filterNumbers() {
      return Array.prototype.filter.call(args,
        element => typeof element === 'number'
    );
  }
}
sumOnlyNumbers(1, 'Hello', 5, false);

这种方法有效,但它太冗长了。可以省略var args = arguments,并使用Array.prototype.filter.call(args)参数将args.filter()转换为rest

function sumOnlyNumbers(...args) {
  var numbers = filterNumbers();
  return numbers.reduce((sum, element) => sum + element);
    function filterNumbers() {
      return args.filter(element => typeof element === 'number');
    }
  }
sumOnlyNumbers(1, 'Hello', 5, false); // => 6

如果您想将数组扩展为单个参数,可以在函数调用中使用扩展运算符。

let sum = (a,b,c) => {
  return a + b + c;
};

console.log(sum(...[1,2,3]));

在上面的代码中,点差运算符将“spread” parameters a, b, and c上的三个值的数组spreadarray运算符还可以展开数组以在var a = [4, 5, 6]; var b = [1, 2, 3, ...a, 7, 8, 9] console.log(b); 字面值中创建单个元素。

ES5

改进了ES6中的函数调用

.apply()在函数对象上提供.apply()方法来解决此问题。不幸的是,这种技术有3个问题:

  • 有必要手动指示功能的上下文 调用
  • 无法在构造函数调用中使用
  • 更优选更短的解决方案

似乎无关紧要的是在let countries = ['India', 'USA']; let otherCountries = ['China', 'Japan']; countries.push.apply(countries, otherCountries); console.log(countries); // => ['India', 'USA', 'China', 'Japan'] 第二次表明背景国家使其更加冗长。

spread

function运算符使用array中的值填充let countries = ['India', 'USA']; let otherCountries = ['China', 'Japan']; countries.push(...otherCountries); console.log(countries); // => ['Moldova', 'Ukraine', 'USA', 'Japan'] 调用参数。让我们用扩展运算符改进上面的例子:

Spread

.apply()运算符从数组中配置构造函数调用参数,这在使用class Actor { constructor(name, country) { this.name = name; this.country = country; } getDescription() { return `${this.name} leads ${this.country}`; } } var details = ['RajiniKanth the Great', 'India']; var Alexander = new Actor(...details); console.log(Alexander.getDescription()); // => 'RajiniKanth the Great leads India' 时会有点复杂和困难。

spread

此外,您可以在同一个调用中组合多个var numbers = [1, 2]; var evenNumbers = [4, 8]; const zero = 0; numbers.splice(0, 2, ...evenNumbers, zero); console.log(numbers); // => [4, 8, 0] 运算符和常规参数。以下示例是从数组中删除现有元素,然后添加其他数组和元素:

var words = ['Hi', 'Hello', 'Good day'];
var otherWords = [...words];
console.log(otherWords); // => ['Hi', 'Hello', 'Good day']
console.log(otherWords === words); // => false

克隆数组实例:

otherWords

<div id="nav"> <ul> <li><a href="./index"><div>About Me</div></a> <ul> <li><a href="./index">Bio</a></li> <li><a href="./Resume">Resume</a></li> </ul> </li> <li><a href="https://github.com/user_name" target="_blank"><div>Github</div></a></li> </ul> </div> </div> 是单词数组的克隆版本。请注意,克隆只发生在数组本身,而不是发生在包含的元素上(即它不是深层克隆)。

参考文献:https://rainsoft.io/how-three-dots-changed-javascript/

答案 5 :(得分:1)

其他运营商

function sumOfNumber(...args) {
    return args.reduce((a, b) => {
        return a + b
    })
}

console.log(sumOfNumber(1, 2, 3, 4))

传播算子

function sumOfNumber(args) {
    console.log(...args)
}

sumOfNumber([1, 2, 3, 4])