JavaScript中rest参数和展开运算符的用法

19

ECMAScript 6 新增了什么是剩余参数的用法?

例如,在 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]);

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


显然,...并不是一个运算符。 - hippietrail
我越读越觉得这个问题越来越棘手。它缺乏焦点。标题和正文的第一句表明它是关于如何在 JavaScript 中使用三个点的,而正文后半部分则问及性能问题。参见提出多个问题。事实上,其他问题作为此问题的重复被关闭,使得问题变得更加复杂。 - Henke
1
这个回答解决了你的问题吗?ES2015 / ES6中的Spread语法和Rest参数 - Henke
6个回答

19
它们之间的区别仅仅是语法吗,还是存在性能问题?
两者都有,而且还有更多……
剩余参数:
1. 在其他语言(如 Ruby、Python)中是一个已知的习惯用法。 2. 比起 slice 更容易阅读和维护。 3. 对于初学者来说更易理解。 4. 可能会导致更好的性能,因为引擎可以进行优化。 5. 对工具更友好,因为可以静态分析。

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 对象来提取可变参数的需要是新语法的众多优点之一。


4
rest参数是指在函数参数定义中使用三个点(...)来将多个参数收集到一个数组中,而展开运算符则是在函数调用时使用三个点(...)将数组展开成多个参数。
如果你想将多个参数收集到一个数组中,就可以在函数参数定义中使用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需要访问arguments,则必须将其存储在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并使用rest参数将Array.prototype.filter.call(args)转换为args.filter()
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"参数a、b和c。 扩展运算符还可以展开数组以创建 array 文本中的单个元素。
var a = [4, 5, 6];
var b = [1, 2, 3, ...a, 7, 8, 9]
console.log(b);

ES6提供了更好的函数调用方式

ES5通过函数对象提供.apply()方法来解决这个问题。不幸的是,这种技术存在三个问题:

  • 需要手动指定函数调用的上下文
  • 无法在构造函数调用中使用
  • 更短的解决方案更可取

.apply()中重复指定上下文显得多余且无意义。

let countries = ['India', 'USA'];
let otherCountries = ['China', 'Japan'];
countries.push.apply(countries, otherCountries);
console.log(countries); // => ['India', 'USA', 'China', 'Japan']
< p > < code > spread 运算符使用来自< code > array 的值填充< code > function 调用参数。让我们使用展开运算符改进上面的示例:
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是一个单词数组的克隆版本。请注意,克隆仅在数组本身上进行,而不是在包含的元素上进行(即不是深克隆)。

参考资料:https://dmitripavlutin.com/how-three-dots-changed-javascript/


@DmitriPavlutin:请查看我的更新帖子,其中包含了很多内容...也许我应该发布一个参考资料。 - Thalaivar

4
在您给出的示例中,您直接传递了对象字面量。虽然仍然语义化,但它似乎与展开运算符的应用不相似。在我看来,当列出每个参数会影响代码可读性(并且在替换传统的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演示(由traceur编译器编译)

不使用展开运算符

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演示

总之,我认为使用展开运算符不会影响性能,但它确实提供了改进的代码可读性和更好的可维护性。遗憾的是,ES6还没有得到广泛实现,我断言这需要一些时间才能获得浏览器的支持。

有趣的事实: PHP 5.6引入类似的功能,称为可变函数,将使func_get_args()变得多余。


3

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

  • ECMA5中的arguments像数组一样而不是数组,因此数组普通方法不可用,但在ECMA6中,arguments是一个数组。
  • 我认为在ECMA5代码示例中构建items数组会使代码稍微慢一些,因为实例化了一个新数组。

2

Rest运算符

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])

刚在课堂上使用了你的例子,赞! - Louis345

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接