用 ... 把数组或对象"炸开"成一个个单独的值——像把一整袋零食撕开倒在桌上。它是 rest(收)的逆运算:rest 把剩下的东西收进袋子,spread 把袋子撒出来。用于合并、克隆、传参、转数组。
把两袋零食倒在一个新袋子里,[...a, ...b] 比 a.concat(b) 更直观。
let a = ["王太利", "肖央"]; // 第一袋 let b = ["曾毅", "玲花"]; // 第二袋 // 以前:concat 方法 let c = a.concat(b); // 把 b 拼到 a 后面 // 现在:... 把两袋都炸开,倒在新数组里 let c = [...a, ...b]; // ...炸开 a 和 b,装进新 [] // ^^^^ ^^^^ // 倒a 倒b console.log(c); // 打印合并后的新数组 // ["王太利","肖央","曾毅","玲花"]
复制一份新数组,和原数组互不影响。
let arr = ["E", "G", "M"]; // 原数组 // 以前:slice 克隆 let copy = arr.slice(); // slice() 不带参数就是复制一份 // 现在:...arr 炸开再装回新 [] let copy = [...arr]; // 炸开 arr 装进新 [],得到副本 console.log(copy); // 打印副本 // ["E","G","M"]
克隆的意义:copy 和 arr 是两个独立数组,改 copy 不影响 arr。注意这是"浅克隆"——数组里如果是对象,对象还是同一个引用,改对象内容两边都会变。
Math.max 本来要你一个个写参数 Math.max(3, 9, 5, 1),现在数组一炸直接喂进去。
let nums = [3, 9, 5, 1]; // 一组数 // 以前:一个个下标传 console.log(Math.max(nums[0], nums[1], nums[2], nums[3])); // 手写四个下标 // 9 // 现在:...nums 炸成 3, 9, 5, 1 喂进去 console.log(Math.max(...nums)); // ... 把数组炸成一串参数 // 9
document.querySelectorAll 返回的 NodeList、函数的 arguments 都是"类数组"——没有 map/filter。[...类数组] 一行转成真数组。
let divs = document.querySelectorAll("div"); // 拿到的是 NodeList 类数组
// NodeList 是类数组,没有 map / filter
let arr = [...divs]; // 一行转真数组
arr.map(d => d.textContent); // 现在数组方法随便用
如果你直接对 querySelectorAll 的结果调 .map,会报错"map is not a function"。因为 NodeList 不是真数组——先用 [...divs] 展开转成真数组,数组方法才能用。
ES2018 起 ... 也能用在对象上:复制、合并、加字段。
let user = { name: "大喇叭", age: 22 }; // 原对象
let copy = { ...user }; // 复制整个对象
let withId = { ...user, id: 100 }; // 复制并加一个新字段
console.log(withId); // 打印合并后的新对象
// { name: "大喇叭", age: 22, id: 100 }
对象展开里后面的同名属性会覆盖前面的——所以写 { ...defaultConfig, ...userConfig } 时,用户配置永远盖在默认配置后面。React 里 {...props} 传属性、合并 state 全是这个套路。
① 可能在什么地方用:合并两个数组/对象、复制数组不改原数据、Math.max(...arr) 求最大、把 DOM 集合转成真数组、React 组件展开 props。
② 常见的问题:把 ... 用在等号左边(那是 rest)以为在复制;以为展开是深克隆(其实是浅克隆,嵌套对象还是共享);直接对 NodeList 调数组方法报错。
③ 解决思路:合并/克隆就用 [...arr] / {...obj};要深克隆嵌套对象用 JSON.parse(JSON.stringify(obj));DOM 结果先 [...divs] 再用数组方法。