扩展语法 ES6

Spread Syntax ES6

考虑以下示例代码

var x = ["a", "b", "c"];
var z = ["p", "q"];

var d = [...x, ...z];

var e = x.concat(z);

这里,de的值完全一样,等于["a", "b", "c", "p", "q"],所以,

  1. 这两者到底有什么区别?
  2. 哪个更有效,为什么?
  3. spread语法的具体用途是什么?
  1. 在你给出的例子中,两者之间基本上没有区别
  2. .concatsignificantly more efficient: http://jsperf.com/spread-into-array-vs-concat 因为 ...(扩展)只是更基本的基础语法之上的语法糖,显式迭代索引以扩展数组。
  3. Spread 允许在更笨重的直接数组操作之上加糖语法

为了扩展上面的第 3 点,您对 spread 的使用是一个有点人为的例子(尽管它可能会经常出现在野外)。当 - 例如 - 整个参数列表应该传递给函数体中的 .call 时,Spread 很有用。

function myFunc(){
    otherFunc.call( myObj, ...args );
}

对比

function myFunc(){
    otherFunc.call( myObj, args[0], args[1], args[2], args[3], args[4] );
}

这是另一个随意的例子,但它更清楚了为什么展开运算符在一些冗长笨拙的情况下会很好用。

作为@loganfsmyth

Spread also works on arbitrary iterable objects which means it not only works on Arrays but also Map and Set among others.

这是一个很好的观点,并且增加了这样的想法——虽然在 ES5 中并非不可能实现——扩展运算符中引入的功能是新语法中更有用的项目之一。


对于此特定上下文中展开运算符的实际基础语法(因为 ... 也可以是 "rest" 参数),请参阅 the specification。 "more fundamental underlying syntax that explicitly iterates over indexes to expand the array" 正如我上面写的那样足以说明这一点,但实际定义使用 GetValueGetIterator 作为后面的变量。

在给定的示例中,这两者之间没有区别。对于串联,我们可以在扩展运算符上使用 concat 方法。但是,展开运算符的使用不限于数组的串联。

扩展语法允许扩展数组表达式或字符串等可迭代对象。可用于以下场景

  1. 数组展开运算符

    • 数组串联
    • 字符串到数组
    • 数组作为函数的参数。
  2. 带对象的传播运算符

    • 对象的串联

要查看所有这些用途的演示并尝试使用代码,请按照下面的说明进行操作 link (codepen.io)

ES6-Demonstration of Spread Operator

/**
* Example-1: Showing How Spread Operator can be used to concat two or more     
arrays. 
*/
const americas = ['South America', 'North America'];

const eurasia = ['Europe', 'Asia'];

const world = [...americas, ...eurasia];

/**
* Example-2: How Spread Operator can be used for string to array.
*/
const iLiveIn = 'Asia';
const iLiveIntoArray = [...iLiveIn];

/**
* Example-3: Using Spread Operator to pass arguments to function
*/
const numbers = [1,4,5];

const add = function(n1,n2,n3){
return n1 + n2 + n3;
};

const addition = add(numbers[0],numbers[1],numbers[2]);
const additionUsingSpread = add(...numbers);

/**
* Example-4: Spread Operator, can be used to concat the array
*/

const personalDetails = {
  name: 'Ravi',
  age: '28',
  sex: 'male'
};

const professionalDetails = {
  occupation: 'Software Engineer',
  workExperience: '4 years'
};

const completeDetails = {...personalDetails, ...professionalDetails};

这个例子的输出是一样的,但是背后的行为是不一样的,

考虑(检查浏览器的控制台):

var x = [], y = [];

x[1] = "a";
y[1] = "b";

var usingSpread = [...x, ...y];
var usingConcat = x.concat(y);

console.log(usingSpread); // [ undefined, "a", undefined, "b"]
console.log(usingConcat); // [ , "a", , "b"] 

console.log(1 in usingSpread); // true
console.log(1 in usingConcat); // false

Array.prototype.concat will preserve the 在数组中,而 Spread 将用 undefined 值替换它们。

输入Symbol.iterator and Symbol.isConcatSpreadable:

Spread 运算符使用 @@iterator 符号遍历数组和类数组对象,例如:

  • Array.prototype
  • 打字Array.prototype
  • String.prototype
  • Map.prototype
  • Set.prototype

(这就是为什么你可以对它们使用 for .. of

我们可以覆盖默认的 iterator 符号以查看 spread 运算符的行为方式:

var myIterable = ["a", "b", "c"];
var myIterable2 = ["d", "e", "f"];

myIterable[Symbol.iterator] = function*() {
  yield 1;
  yield 2;
  yield 3;
};

console.log(myIterable[0], myIterable[1], myIterable[2]); // a b c
console.log([...myIterable]); // [1,2,3]

var result = [...myIterable, ...myIterable2];
console.log(result); // [1,2,3,"d","e","f"]

var result2 = myIterable.concat(myIterable2);
console.log(result2); // ["a", "b", "c", "d", "e", "f"]

另一方面,@@isConcatSpreadable

A Boolean valued property that if true indicates that an object should be flattened to its array elements by Array.prototype.concat.

如果设置为 falseArray.concat 将不会展平数组:

const alpha = ['a', 'b', 'c'];
const numeric = [1, 2, 3];

let alphaNumeric = alpha.concat(numeric);

// console.log(alphaNumeric);

numeric[Symbol.isConcatSpreadable] = false;

alphaNumeric = alpha.concat(numeric);

// alphaNumeric = [...alpha, ...numeric];
// the above line will output : ["a","b","c",1,2,3]

console.log(JSON.stringify(alphaNumeric)); // ["a","b","c",[1,2,3]]

但是,spreadbehaves differently when it comes to Objects since they are not iterable

var obj = {'key1': 'value1'};
var array = [...obj]; // TypeError: obj is not iterable
var objCopy = {...obj}; // copy

It copies own enumerable properties from a provided object onto a new object.

传播运算符更快,检查 spread-into-array-vs-concat(因为 Chrome 至少 67)

并检查 how three dots changed javascript for some use cases, among them is the Destructuring assignment(数组或对象):

const arr = [1, 2, 3, 4, 5, 6, 7];

const [first, , third, ...rest] = arr;

console.log({ first, third, rest });

并将字符串拆分为字符数组:

console.log( [...'hello'] ) // [ "h", "e" , "l" , "l", "o" ]

把问题打乱,让我们从最基本的问题开始:展开语法到底有什么用?

Spread 语法基本上是解包可迭代对象(如数组或对象)的元素。或者,更详细的解释来自 MDN Web Docs on spread syntax:

Spread syntax allows an iterable such as an array expression or string to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected, or an object expression to be expanded in places where zero or more key-value pairs (for object literals) are expected.

以下是扩展语法典型用例的一些简单示例,以及扩展语法和剩余参数之间差异的示例(它们可能看起来相同,但执行的功能几乎相反)。

函数调用:

const multiArgs = (one, two) => {
  console.log(one, two);
};

const args = [1, 2];
multiArgs(...args);
// 1 2

数组或字符串文字:

const arr1 = [2, 3];
const arr2 = [1, ...arr1, 4];
console.log(arr2);
// [1, 2, 3, 4]

const s = 'split';
console.log(...s);
// s p l i t

对象文字:

const obj1 = { 1: 'one' };
const obj2 = { 2: 'two' };
const obj3 = { ...obj1, ...obj2 };
console.log(obj3);
// { 1: 'one', 2: 'two' }

剩余参数语法与扩展语法不同:

Rest parameter 语法看起来与扩展语法相同,但实际上将未知数量的函数参数表示为数组。因此,与其“解包”可迭代对象,其余参数实际上将多个参数打包到一个数组中。

const multiArgs = (...args) => {
  console.log(args);
};

multiArgs('a', 'b', 'c');
// ['a', 'b', 'c']

传播语法性能/效率:

要解决与其他方法相比的效率问题,唯一诚实的答案是“视情况而定”。浏览器一直在变化,与特定函数关联的上下文和数据会产生截然不同的性能结果,因此您会发现各种相互冲突的性能时序,这表明传播语法比您可能会使用的各种数组或对象方法快得惊人又慢得离谱用于实现类似的目标。最后,任何对速度进行优化至关重要的情况都应该进行比较测试,而不是依赖忽略代码和数据细节的简单函数的通用时序。

concat()的比较:

最后,关于问题代码中显示的扩展语法和 concat() 之间的区别的快速评论。不同之处在于展开语法不仅仅可以用于连接数组,而且 concat() 可以在 IE 等较旧的浏览器中使用。如果您不关心与旧浏览器的兼容性并且不需要对速度进行微优化,那么在扩展语法和 concat() 之间进行选择只是您觉得更具可读性的问题:arr3 = arr1.concat(arr2)arr3 = [...arr1, ...arr2].

常量颜色 = ['Blue','Red','Black']; // 简单数组。

const my_colours = ['Blue','Red','Black','Yellow','Green'];

const favourite_colours = [...my_colours,'grey']; //[...] 在另一个数组中传播运算符访问数据。

扩展语法允许在需要零个或多个元素的地方扩展可迭代对象。这种高级解释可能令人困惑,因此 'real-world' 示例如下:

如果没有传播语法,您可以像这样多次更新对象:

//If I needed to change the speed or damage at any time of a race car

const raceCar = {name: 'Ferrari 250 GT'};
const stats = {speed: 66, damage: 1, lap: 2};

raceCar['speed'] = stats.speed;
raceCar['damage'] = stats.damage;

或者,更简洁的解决方案是使用扩展语法创建一个新对象:

//Creates a new object with priority from left to right 
const lap1 = { ...raceCar, ...stats }

//Or a specific variable:
const enterPitStop = {...raceCar, speed: 0 }

本质上,您将创建一个新的不可变对象,而不是改变 raceCar 的原始对象。

它在向数组添加新值时也很有用。使用传播,您可以通过复制前一个数组来 push/unshift 多个变量。传播之前,你会这样推:

var raceCars = ['Ferrari 250 GT', 'Le Mans Series', '24 Heures du Mans'];

//Sometimes, you will need to push multiple items to an array, which gets messy in large projects!
raceCars.push('Car 1');
raceCars.push('Car 2');
raceCars.push('Car 3');

相反,您可以复制数组并将其添加到一个新变量或为简单起见添加到同一变量。

//Push values to array
raceCars = [...raceCars, 'Car 1', 'Car 2', 'Car 3'];

//This is dynamic! Add the values anywhere in the array:

//Adds the values at the front as opposed to the end
raceCars = ['Car 1', 'Car 2', 'Car 3', ...raceCars];

//Another dynamic examples of adding not in the front or back:
raceCars = ['Car 1', 'Car 2', ...raceCars, 'Car 3'];

我鼓励您在 Mozilla Developer Website 上查看更详细的文档。