ES6 展开语法

67 阅读4分钟

JavaScript 中的展开语法(Spread Syntax) ,它也使用三个点(...)作为符号,但其作用与**剩余参数(Rest Parameters)**正好相反。

什么是展开语法?

展开语法允许一个可迭代对象(如数组、字符串、Set、Map 等)或者一个对象(自 ES2018 起)在期望零个或多个参数(用于函数调用)或元素(用于数组字面量)或键值对(用于对象字面量)的位置进行展开

简单来说,它就是把一个集合(数组、对象等)的内容“摊开”或“铺平”,将其中的元素或属性一个个取出来,放到当前的位置。

语法:

  • 对于可迭代对象(数组、字符串等):...iterableExpression
  • 对于对象(ES2018+):...objectExpression

主要应用场景:

1. 在数组字面量中 (Array Literals):

  • 合并数组 (Concatenating Arrays):

    const arr1 = [1, 2, 3];
    const arr2 = [4, 5, 6];
    const arr3 = [0, ...arr1, ...arr2, 7]; // 展开 arr1 和 arr2 的元素
    
    console.log(arr3); // 输出: [0, 1, 2, 3, 4, 5, 6, 7]
    
    // 传统方式:
    // const arr3_old = [0].concat(arr1, arr2, [7]);
        
    
  • 复制数组 (Shallow Copying Arrays):
    创建一个数组的浅拷贝

    const originalArray = [1, 2, 3];
    const copiedArray = [...originalArray];
    
    console.log(copiedArray); // 输出: [1, 2, 3]
    console.log(copiedArray === originalArray); // 输出: false (它们是不同的数组实例)
    
    copiedArray.push(4);
    console.log(originalArray); // 输出: [1, 2, 3] (原数组不受影响)
        
    

    注意: 这是浅拷贝。如果数组元素是对象或其他数组,拷贝的是引用,修改嵌套内容会影响原数组。

    const originalNested = [{ a: 1 }, { b: 2 }];
    const copiedNested = [...originalNested];
    copiedNested[0].a = 99;
    console.log(originalNested[0].a); // 输出: 99 (原数组的嵌套对象被修改了)
        
    
  • 向数组中插入元素 (Immutably):

    const base = ['a', 'b', 'c'];
    const itemToAdd = 'x';
    
    // 在末尾添加
    const addedEnd = [...base, itemToAdd];
    console.log(addedEnd); // 输出: ['a', 'b', 'c', 'x']
    
    // 在开头添加
    const addedStart = [itemToAdd, ...base];
    console.log(addedStart); // 输出: ['a', 'x', 'b', 'c'] (修正:应为 ['x', 'a', 'b', 'c']) -> Let's correct the thinking, it should be ['x', 'a', 'b', 'c']
    
    console.log(base); // 输出: ['a', 'b', 'c'] (原数组不变)
        
    
  • 将类数组对象或可迭代对象转换为真数组:

    // NodeList (来自 DOM 查询)
    // const divs = document.querySelectorAll('div');
    // const divArray = [...divs]; // 将 NodeList 转换为 Array
    
    // arguments 对象 (函数内部,ES6 之前常用)
    function exampleFunc() {
      const argsArray = [...arguments];
      console.log(argsArray);
    }
    exampleFunc(1, 'a', true); // 输出: [1, 'a', true]
    
    // Set
    const mySet = new Set([1, 2, 2, 3]);
    const setArray = [...mySet];
    console.log(setArray); // 输出: [1, 2, 3]
    
    // String
    const str = "hello";
    const charArray = [...str];
    console.log(charArray); // 输出: ['h', 'e', 'l', 'l', 'o']
        
    

2. 在函数调用中 (Function Calls):

将数组的元素作为独立的参数传递给函数。这通常用来替代 Function.prototype.apply()

 function sum(x, y, z) {
   return x + y + z;
 }

 const numbers = [1, 2, 3];

 // 使用展开语法
 const result = sum(...numbers); // 等价于 sum(1, 2, 3)
 console.log(result); // 输出: 6

 // 传统方式 (使用 apply)
 // const resultApply = sum.apply(null, numbers);
 // console.log(resultApply); // 输出: 6

 // 结合其他参数
 function logArgs(arg1, arg2, ...rest) { // 这里 ...rest 是剩余参数
     console.log("Arg1:", arg1);
     console.log("Arg2:", arg2);
     console.log("Rest:", rest);
 }
 const data = ['a', 'b', 'c', 'd'];
 logArgs('fixed1', ...data, 'fixed2'); // 等价于 logArgs('fixed1', 'a', 'b', 'c', 'd', 'fixed2')
 // 输出:
 // Arg1: fixed1
 // Arg2: a
 // Rest: [ 'b', 'c', 'd', 'fixed2' ]
     
  1. 在对象字面量中 (Object Literals - ES2018+):
    将一个对象的自有(own)可枚举(enumerable)属性复制到新的对象字面量中。

    • 合并对象 (Merging Objects):

      const obj1 = { a: 1, b: 2 };
      const obj2 = { b: 3, c: 4 }; // obj2 的 b 会覆盖 obj1 的 b
      const mergedObj = { ...obj1, ...obj2, d: 5 };
      
      console.log(mergedObj); // 输出: { a: 1, b: 3, c: 4, d: 5 }
      
      // 顺序很重要,后面的属性会覆盖前面的同名属性
      const mergedObjReverse = { ...obj2, ...obj1 };
      console.log(mergedObjReverse); // 输出: { b: 2, c: 4, a: 1 }
          
      
    • 复制对象 (Shallow Copying Objects):
      创建一个对象的浅拷贝

      const originalObject = { name: "Alice", age: 30 };
      const copiedObject = { ...originalObject };
      
      console.log(copiedObject); // 输出: { name: 'Alice', age: 30 }
      console.log(copiedObject === originalObject); // 输出: false
      
      copiedObject.age = 31;
      console.log(originalObject.age); // 输出: 30 (原对象不受影响)
          
      

      注意: 同样是浅拷贝。嵌套对象属性依然是引用。

    • 向对象中添加或更新属性 (Immutably):

      const user = { id: 101, name: "Bob" };
      const updatedUser = { ...user, name: "Charlie", city: "New York" }; // 更新 name,添加 city
      
      console.log(updatedUser); // 输出: { id: 101, name: 'Charlie', city: 'New York' }
      console.log(user); // 输出: { id: 101, name: 'Bob' } (原对象不变)
          
      

展开语法 vs 剩余参数 (Spread vs Rest):

虽然它们都使用 ...,但用途完全不同:

  • 展开语法 (Spread):

    • 作用: 将一个集合(数组、对象等)展开成独立的元素/属性。
    • 位置: 用在数组字面量 [...]、函数调用 func(...)、对象字面量 {...} 中。
    • 目的: “拿出”集合的内容。
  • 剩余参数 (Rest):

    • 作用:多个独立的参数收集到一个数组中。
    • 位置: 用在函数定义的参数列表 function(...rest) 或解构赋值 const [first, ...rest] = arr; 中。
    • 目的: “聚合”多个项。

示例对比:

// 展开语法 (Spread)
const nums = [1, 2, 3];
console.log(...nums); // 输出: 1 2 3 (在 console.log 调用中展开)
const moreNums = [0, ...nums, 4]; // 输出: [0, 1, 2, 3, 4] (在数组字面量中展开)

// 剩余参数 (Rest)
function collectArgs(...args) { // 收集所有传入的参数到一个名为 args 的数组中
  console.log(args);
}
collectArgs(1, 2, 3); // 输出: [1, 2, 3]

const [first, ...remaining] = [10, 20, 30, 40]; // 解构赋值中的剩余参数
console.log(first); // 输出: 10
console.log(remaining); // 输出: [20, 30, 40]
    

总结:

展开语法是 ES6 (对象展开是 ES2018) 引入的一个非常实用和简洁的特性,它使得数组和对象的复制、合并以及函数参数传递变得更加直观和方便,是现代 JavaScript 开发中不可或缺的一部分。