【问题标题】:Spread Syntax ES6扩展语法 ES6
【发布时间】:2020-02-24 07:38:10
【问题描述】:

考虑以下示例代码

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. 展开语法的具体用途是什么?

您不认为在正式的庞大语言中引入这些小快捷方式可能会留下一些未被注意到的错误,我的意思是要么完全没有必要,要么我没有意识到它的必要性。

【问题讨论】:

  • 虽然这个问题的答案:“你不认为在正式的庞大语言中引入这些小快捷方式可能会留下一些未被注意到的错误”将基于意见,但我的意见是,是的,大多数 ES6 将生成大量错误代码,因为草率和/或初级开发人员无法准确理解他们在做什么。
  • @rockerest 这正是我的想法。
  • 好的,我刚刚做了一个快速测速,concat 更快。
  • @void 主要用于函数调用,即如果myFunc 接受未知数量的参数,我们可以将参数作为带有展开的数组提供。像这样:myFunc(...dynamicallyGeneratedArgs)
  • 如果您想将z 附加到x 而不创建另一个数组,您将获得真正的好处。 x.push(...z);

标签: javascript ecmascript-6 spread-syntax


【解决方案1】:
  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] );
}

这是另一个随意的例子,但它更清楚为什么扩展运算符可以很好地用于一些其他冗长和笨重的情况。

作为@loganfsmythpoints out

Spread 也适用于任意可迭代对象,这意味着它不仅适用于 Arrays,还适用于 MapSet 等。

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


有关此特定上下文中扩展运算符的实际底层语法(因为... 也可以是“rest”参数),请参阅the specification。正如我在上面所写的,“显式迭代索引以扩展数组的更基本的底层语法”足以理解这一点,但实际定义使用GetValueGetIterator 来表示后面的变量。

【讨论】:

  • 实际使用示例:$.when 不允许将 promise 数组作为参数,所以 $.when(...args) 很酷 :)
  • Spread 也适用于任意可迭代对象,这意味着它不仅适用于Arrays,还适用于MapSet 等。
  • 自 Chrome 67(2018 年 5 月 29 日)以来,传播语法比 concat 快(至少两倍)
  • otherFunc.call( myObj, args[0], args[1], args[2], args[3], args[4] );???这似乎是一个非常糟糕的例子。这不仅仅是人为的,只是简单的误导。大多数编写 ES6 之前的代码的人都会使用otherFunc.apply( myObj, args );,它具有基本相同的语义而不会失去清晰度。当this 无关紧要时,func( ...args ) 与更冗长和更不必要的func.apply( null, args ) 相比是一个很好的观点。
【解决方案2】:

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

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

Spread 语法允许一个可迭代对象,例如数组表达式或字符串 在零个或多个参数的地方扩展(对于函数 调用)或元素(对于数组文字)是预期的,或者是一个对象 在零个或多个键值对的地方扩展表达式 (对于对象文字)是预期的。

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

函数调用:

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参数语法与spread语法不同:

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]

【讨论】:

【解决方案3】:

这个例子的输出是一样的,但在底层的行为是不一样的,

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

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 将保留数组中的empty slots,而Spread 将用undefined 值替换它们。

输入Symbol.iteratorSymbol.isConcatSpreadable

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

  • Array.prototype
  • TypedArray.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

一个布尔值属性,如果为 true,则表示对象应通过 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]]

但是,spread behaves differentlyObjects 一样,因为它们是 not iterable

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

它将自己的可枚举属性从提供的对象复制到新对象。

扩展运算符更快,检查spread-into-array-vs-concat(至少从 Chrome 67 开始)

查看how three dots changed javascript 的一些用例,其中有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" ]

【讨论】:

    【解决方案4】:

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

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

    1. 带数组的扩展运算符

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

      • 对象的串联

    要查看所有这些用途的演示并尝试代码,请点击以下链接 (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};
    

    【讨论】:

      【解决方案5】:

      常量颜色 = ['蓝色','红色','黑色']; // 简单数组。

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-12-24
        • 1970-01-01
        • 2018-08-01
        • 2023-03-06
        • 2019-10-02
        • 2017-07-27
        相关资源
        最近更新 更多