什么是扩展运算符号?
扩展运算符是一种操作符,它可以将我们可迭代对象扩展为单独的元素。常用于在函数调用、数组构造、构造字面量对象等情况下,将数组、类数组对象或字符串展开为单独的元素,或将对象展开为键值对。在语法层面展开数据
扩展运算符特性
- 扩展运算符允许在其后接受表达式。
- 在数组或函数参数中使用展开语法(…)时,该语法只能用来将可迭代对象(即部署了 Iterator 遍历器接口的对象)的数组表达式或字符串在语法层面进行展开操作。
- 扩展运算符可用于展开/合并数组,将类数组对象转换为数组,并且可以用于接收参数。
- 扩展运算符仅执行浅拷贝,这意味着它只会复制对象的引用,而不是对象本身的内容。
- 在使用扩展运算符时,需要记住扩展运算符并不会克隆相同的属性
结合代码进行说明
扩展运算符允许在其后接受表达式
// 设定变量 x 的值为 10
const x = 10;// 构造数组 arr
const arr = [// 判断 x 是否大于 0,如果是则返回包含 'a' 的数组,否则返回空数组,然后使用扩展运算符将其展开...(x > 0 ? ['a'] : []),// 将 'b' 加入数组 arr'b',
];// 构造对象 obj
const obj = {// 判断 x 是否大于 1,如果是则返回包含属性 'a' 的对象,否则返回空对象,然后使用扩展运算符将其展开...(x > 1 ? { a: 1 } : {}),// 添加属性 'b',值为 2b: 2,
};// 输出数组 arr
console.log(arr); // ["a", "b"]// 输出对象 obj
console.log(obj); // { a: 1, b: 2 }
扩展运算符可用于展开/合并数组,将类数组对象转换为数组,并且可以用于接收参数。
使用运算符展开数组
const array1 = [1, 2, 3];
const array2 = [4, 5, ...array1, 6];console.log(array2); // 输出:[4, 5, 1, 2, 3, 6]
使用运算符合并数组
示例一
const coffee = ['coffee', 'water'];
const spices = ['cinnamon', 'nutmeg', 'cardamom'];const coffeeReady = [...coffee, ...spices];console.log(coffeeReady)
// 输出:['coffee', 'water', 'cinnamon', 'nutmeg', 'cardamom'];
示例二
const mySiblings = { brothersName: 'Philip', sistersName: 'Lara'
};const myFamily = { fathersName: 'Michael', mothersName: 'Louise',...mySiblings
};
// Now we can treat brothersName and sistersName as
// a property of myFamily:
console.log(myFamily.brothersName)
将类数组对象转换为数组
// 类数组对象
const arrayLikeObject = { 0: 'a', 1: 'b', 2: 'c', length: 3 };// 将类数组对象转换为数组
const array = [...arrayLikeObject];console.log(array); // 输出:['a', 'b', 'c']
将字符串转换成数组
const str = 'coffee';
const letters = [...str, 's.', '☕️'];
console.log(letters);// ["c", "o", "f", "f", "e", "e", "s.", "☕️"]
使用运算符接收参数
function myFunction(x, y, z) {console.log(x, y, z);
}const args = [1, 2, 3];
myFunction(...args); // 输出:1 2 3
扩展运算符并不会克隆相同的属性
在下面的例子中,我们的vegetable1对象和vegetable2对象里都有一个相同的属性potato,但是跳转的值不同,在使用扩展运算符时,它并不会克隆相同的属性,而是会将后面对象中的属性值覆盖前面对象中的属性值。
const vegetable1 = { potato: '土豆', tomato: '番茄',cucumber: '青瓜'
};
const vegetable2 = { potato: '马铃薯', cucumber: '黄瓜', pumpkin: '南瓜',
};const allVegetable = { ...vegetable1, ...vegetable2 };
console.log(allVegetable);
/* 输出
{ tomato: '番茄',cucumber: '青瓜'potato: '马铃薯', cucumber: '黄瓜', pumpkin: '南瓜',
}
*/
扩展运算符仅执行浅拷贝
let a = [1, [2, 3]]; // 定义数组 a,其中包含一个嵌套的数组 [2, 3]
const b = [4, 5, 6];let c = [...a, ...b]; // 将数组 a 和数组 b 展开,并将它们的元素合并到数组 c 中
console.log(c);
// 输出: [1, [2, 3], 4, 5, 6]a[0] = 11; // 修改数组 a 的第一个元素为 11
a[1][0] = 22; // 修改数组 a 中嵌套数组的第一个元素为 22console.log(c);
// 输出: [1, [22, 3], 4, 5, 6]
- 在上面的代码中,当我们使用扩展运算符
...
将数组a
和数组b
展开到数组c
中时,c
中的[2, 3]
实际上是对数组a
中嵌套数组的引用。因此,c
中的第二个元素[2, 3]
并不是复制了数组a
中的[2, 3]
,而是引用了相同的嵌套数组。 - 当我们修改数组
a
中的元素时,由于c
中的嵌套数组和数组a
中的嵌套数组是同一个引用,所以c
中的嵌套数组也会被修改。 - 因此,尽管我们只修改了数组
a
,但数组c
也受到了影响,其中嵌套数组的元素被修改为了[22, 3]
。
注意:浅拷贝只能复制对象或数组的顶层结构。如果原始对象或数组中包含嵌套的对象或数组,浅拷贝后的对象或数组仍然会与原始对象或数组共享相同的嵌套对象或数组。这意味着在浅拷贝中修改嵌套对象或数组会影响到所有相关的对象或数组。
讲到这里肯定很多人会一想到,Object.assign(),它同样也能将另一个对象里的值拷贝到目标对象中,那他们之间有什么区别呢?
Object.assign()
和扩展运算的区别
特性 | 扩展运算符 (... ) | Object.assign() |
---|---|---|
语法 | 更简洁,直观 | 语法稍微复杂,接受目标对象和一个或多个源对象作为参数 |
返回值 | 返回一个新的对象或数组,其中包含了源对象或数组的属性或元素 | 返回目标对象本身,并且修改后的对象会直接反映在目标对象上 |
用途 | 用于对象和数组的浅拷贝,以及数组的合并 | 用于将一个或多个源对象的属性复制到目标对象中,也可以用于对象的浅拷贝和属性的合并 |
处理非对象类型 | 仅能处理对象和数组 | 能处理非对象类型的值,如字符串、数字、布尔值等,并将它们包装成对象 |
属性的设置方式 | 忽略源对象中的非自身可枚举属性 | 复制所有可枚举属性,包括原型链上的属性 |