展开运算符...仅将可迭代结构摊开为独立值,不可用于复制对象或深拷贝数组;在数组中用于合并、传参、浅拷贝,但仅限第一层;{...obj}是对象展开语法,非可迭代对象不能直接用...展开。

JavaScript 的展开运算符 ... 不是用来“复制对象”或“深拷贝数组”的万能工具,它只做一件事:把可迭代(iterable)结构“摊开”成一串独立的值。用错场景、误判行为,是绝大多数问题的根源。
展开运算符在数组中怎么用?
它把数组“打散”成逗号分隔的值,只能出现在函数调用、数组字面量或解构赋值的右侧;不能单独写 ...[1, 2],会报语法错误。
常见用途:
- 合并数组:
[...arr1, ...arr2]—— 创建新数组,不修改原数组 - 函数传参:
Math.max(...numbers)—— 替代apply,避免Math.max.apply(null, numbers) - 浅拷贝:
[...arr]—— 仅第一层元素被复制,嵌套数组/对象仍共享引用
注意:[...arr, 3] 合法,[3, ...arr] 也合法,但 [...arr, ...obj] 会报错,因为 obj 默认不可迭代。
立即学习“Java免费学习笔记(深入)”;
为什么对普通对象展开会报错?
对象本身不是可迭代类型,所以直接写 {...obj} 在语法上是允许的,但这是「对象展开」(object spread),属于对象字面量语法扩展,和数组的展开运算符同符号、不同语义。
关键区别:
-
[...obj]→ 报错:obj is not iterable -
{...obj}→ 合法,等价于Object.assign({}, obj),只遍历对象自身的可枚举属性(不含原型链、Symbol 键)
如果想让对象变成可迭代的,得手动加 [Symbol.iterator] 方法,否则别指望 ... 能“自动展开对象”。
展开运算符和 rest 参数是一回事吗?
符号相同,作用相反:... 在函数参数位置叫 rest 参数(收集剩余参数),在调用/字面量位置叫展开运算符(分发已有值)。
示例对比:
function foo(a, ...rest) {
console.log(a); // 1
console.log(rest); // [2, 3]
}
foo(1, 2, 3);
const arr = [1, 2, 3];
console.log(...arr); // 1 2 3(不是 [1, 2, 3])
rest 参数必须是最后一个形参;展开运算符可以多次出现,也可以和其他元素混用,但必须在支持它的上下文中(如数组字面量、函数调用、对象字面量)。
哪些地方不能用展开运算符?
它不是语法糖,而是有明确运行时约束的特性:
- 不能在对象解构左侧使用:
const {...keys} = obj→ 语法错误 - 不能展开
null或undefined:[...null]→TypeError: null is not iterable - 不能展开非可迭代的类数组(如
document.querySelectorAll('div')返回的是NodeList,现代浏览器中它是可迭代的;但老式HTMLCollection不是,需先转Array.from) - 性能敏感场景慎用:展开大数组(如 10 万项)可能触发栈溢出或明显卡顿,此时应考虑
push.apply或循环
真正容易被忽略的是:展开运算符不会触发 getter,也不会处理 Map/Set 的键值对结构——[...myMap] 得到的是 [key, value] 数组,不是纯 key 或纯 value。











