...到底帮你做了什么,让你至今都忘不了TA

259 阅读6分钟

前言

聪明的你肯定知道我说的是拓展运算符...吧

Snipaste_2024-11-05_16-36-53.png

深入了解拓展运算符 ...

拓展运算符(spread operator)... 是 ECMAScript 6(ES6)引入的一种简洁语法,它使得操作数组、对象等变得更加方便和灵活。... 运算符有两个常见的使用场景:

  1. 展开(Spread) :将数组或对象的元素/属性展开。
  2. 剩余(Rest) :将函数参数收集成一个数组或对象。

下面将详细讲解拓展运算符的原理、用法、一些常见的应用场景,最后还会介绍一些需要注意的细节。

1学习.png


1. 拓展运算符的基础

1.1 展开数组

在数组中,... 运算符用于将数组的元素拆开,通常用于数组的合并或复制。

例子:

// 合并两个数组
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2];
console.log(combined); // 输出: [1, 2, 3, 4, 5, 6]

解析:

在上述代码中,...arr1...arr2 把数组 arr1arr2 展开成单独的元素,然后组成一个新的数组。这个操作非常直观地达到了合并两个数组的效果。

1.2 展开对象

... 运算符同样可以用于对象,将对象的属性复制到另一个对象中。

例子:

const obj1 = { name: "Alice", age: 25 };
const obj2 = { job: "Engineer", city: "New York" };

const combinedObj = { ...obj1, ...obj2 };
console.log(combinedObj); 
// 输出: { name: "Alice", age: 25, job: "Engineer", city: "New York" }

解析:

...obj1...obj2 展开了各自的属性,将它们合并成一个新的对象。如果两个对象中有相同的属性,后面的对象会覆盖前面的属性。


2. 拓展运算符的底层原理

... 运算符在 JavaScript 中并不是简单的语法糖,而是通过 迭代器原型链 的机制来实现的。在底层,... 进行的是以下操作:

2.1 展开数组

当我们使用拓展运算符 ... 展开数组时,JavaScript 会通过 迭代 数组的元素并将它们逐个添加到目标数组中。例如:

const arr1 = [1, 2, 3];
const arr2 = [...arr1];  // 通过拓展运算符复制数组

底层的工作原理是:

  • 先通过数组的内建迭代器(Symbol.iterator)获取数组的元素。
  • 然后将每个元素依次放入新的数组。

这样,...arr1 就会将数组 arr1 展开为一个个元素,然后创建一个新的数组。

2.2 展开对象

对象的展开原理也类似于数组展开。... 运算符会遍历源对象的所有属性并将其添加到目标对象中。对象的每个属性通过其 [[Enumerable]] 内部属性来决定是否可以被展开。这个过程通过 for...inObject.keys() 进行枚举,并且只会拷贝可枚举的属性。

const obj = { name: "Alice", age: 25 };
const newObj = { ...obj };

底层原理:

  • 遍历对象的每个可枚举属性(通常通过 Object.keys())。
  • 将每个属性值添加到新的对象中。

3. 拓展运算符的妙用

3.1 浅拷贝数组和对象

拓展运算符可以轻松创建数组和对象的浅拷贝。

数组浅拷贝:

const arr = [1, 2, 3];
const newArr = [...arr];  // 创建一个新数组

newArr.push(4);
console.log(arr);    // 输出: [1, 2, 3]
console.log(newArr); // 输出: [1, 2, 3, 4]

对象浅拷贝:

const obj = { name: "Alice", age: 25 };
const newObj = { ...obj };  // 创建一个新对象

newObj.age = 30;
console.log(obj);    // 输出: { name: "Alice", age: 25 }
console.log(newObj); // 输出: { name: "Alice", age: 30 }

解析:

在这两个例子中,使用 ... 运算符创建了原数组和原对象的浅拷贝。原始数组和对象不受修改影响(ps:修改的是基本数据的情况),因此它们的内容没有被改变。

3.2 合并数组或对象

如前所述,拓展运算符非常适合将多个数组或对象合并。

合并数组:

const arr1 = [1, 2];
const arr2 = [3, 4];
const arr3 = [5, 6];

const mergedArr = [...arr1, ...arr2, ...arr3];
console.log(mergedArr); // 输出: [1, 2, 3, 4, 5, 6]

合并对象:

const obj1 = { name: "Alice" };
const obj2 = { age: 25 };
const obj3 = { job: "Engineer" };

const mergedObj = { ...obj1, ...obj2, ...obj3 };
console.log(mergedObj); // 输出: { name: "Alice", age: 25, job: "Engineer" }

解析:

通过 ... 运算符,多个数组或对象可以非常方便地合并成一个新的数组或对象。如果属性或元素重复,后面的会覆盖前面的。

3.3 解构数组和对象

... 运算符还可以在解构时使用,将剩余的元素收集到一个新数组或对象中。

数组解构:

const arr = [1, 2, 3, 4, 5];
const [first, second, ...rest] = arr;

console.log(first);  // 输出: 1
console.log(second); // 输出: 2
console.log(rest);   // 输出: [3, 4, 5]

对象解构:

const obj = { name: "Alice", age: 25, job: "Engineer" };
const { name, ...rest } = obj;

console.log(name);  // 输出: Alice
console.log(rest);  // 输出: { age: 25, job: "Engineer" }

解析:

在数组解构中,...rest 用来收集剩余的元素。在对象解构中,...rest 收集除了 name 之外的所有属性,形成一个新对象。

3.4 函数参数的剩余参数

... 也可以用作剩余参数(Rest Parameters),将函数的多余参数收集为一个数组。

function sum(...numbers) {
  return numbers.reduce((total, num) => total + num, 0);
}

console.log(sum(1, 2, 3, 4, 5)); // 输出: 15

解析:

这里的 ...numbers 语法将所有传入的参数收集成一个数组 numbers,并可以在函数内部进行操作。这种方式比传统的 arguments 更简洁直观。


4. 使用拓展运算符时需要注意的事项

  • 浅拷贝... 只进行浅拷贝,对于嵌套的对象或数组,内部的引用类型不会被深拷贝。即,嵌套的对象或数组仍然是原始数据的引用。

    const obj = { name: "Alice", address: { city: "New York" } };
    const copyObj = { ...obj };
    
    copyObj.address.city = "Los Angeles";
    console.log(obj.address.city);  // 输出: "Los Angeles"  (浅拷贝)
    
  • 不可遍历的对象:一些内建对象,如 MapSet,无法直接通过 ... 扩展。对于这类对象,需要使用相应的方法(如 Array.from())转换成数组后再进行操作。

  • 不适用于 nullundefined:使用 ... 运算符时,传入的值不能是 nullundefined,否则会抛出错误。


总结

拓展运算符 ... 是 JavaScript 中一个强大的工具,它可以使数组和对象的操作更加简洁、高效。无论是数组的合并、拷贝、解构,还是函数参数的处理,... 都提供了灵活的解决方案。

在实际开发中,... 运算符常用于:

  • 合并多个数组或对象;
  • 创建浅拷贝;
  • 解构数组和对象;
  • 处理函数的剩余参数。

通过对拓展运算符底层原理的理解,你能够更好地掌握它的使用,并避免一些常见的陷阱。了解并熟练掌握拓展运算符的使用,将极大提升你在日常开发中的效率和代码可读性!

6164e8e1f9364157ba566bea33600793~tplv-73owjymdk6-jj-mark-v1_0_0_0_0_5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5LiN54ix6K-06K-d6YOt5b6357qy_q75.webp