写在前面
ES6(即 ES2015)是 JavaScript 历史上规模最大的一次升级,之后每年的新版本(ES2016~)统称 ES6+。它对现代 JS 的影响是全方位的:let/const 取代 var、类与模块、Promise 与 async/await......但真正让日常代码"脱胎换骨"的,是四个使用频率最高、组合起来威力巨大的语法特性:
- 箭头函数 ------ 更短的函数写法 + 词法
this; - 解构赋值 ------ 从数组/对象中"拆"出值;
- 模板字符串 ------ 字符串插值与多行;
- 扩展运算符 / 剩余参数 ------ 数组与对象的合并、复制、收集。
本文从语法讲到 this 陷阱、浅拷贝边界,再给 5 个实战,帮你把代码量砍掉三成的同时避开最常见的坑。
一、箭头函数
1.1 基本语法
箭头函数用 => 取代 function,写法更短:
js
// 普通函数
const add1 = function (a, b) { return a + b; };
// 箭头函数
const add2 = (a, b) => a + b; // 单个表达式:自动 return
// 只有一个参数:省略括号
const double = x => x * 2;
// 没有参数:括号不能省
const hi = () => "hello";
// 函数体多条语句:必须写 return
const calc = (a, b) => {
const sum = a + b;
return sum * 2;
};
规则速记:
| 情况 | 写法 |
|---|---|
| 一个参数 | x => x * 2(可省括号) |
| 多个 / 零个参数 | (a, b) => ...、() => ... |
| 单表达式函数体 | 省略 {} 和 return |
| 多语句函数体 | 必须写 {} 和 return |
1.2 核心区别:this 是词法绑定的
普通函数 的 this 由"调用方式"决定(谁调用指向谁);箭头函数 的 this 由"定义位置"决定(继承外层作用域),定义后不可改变。
js
const obj = {
name: "Tom",
sayLater: function () {
// 普通函数:this 是 window,找不到 name
setTimeout(function () {
console.log(this.name); // undefined
}, 100);
// 箭头函数:this 继承自 sayLater 的外层(obj)
setTimeout(() => {
console.log(this.name); // "Tom"
}, 100);
},
};
所以箭头函数天生适合回调 (定时器、事件、数组方法),不需要再写 var self = this 或 .bind(this)。
1.3 箭头函数的三个"不能"
js
// 1. 不能作为构造函数
const A = () => {};
new A(); // TypeError: A is not a constructor
// 2. 没有自己的 arguments
const f = () => console.log(arguments); // 报错/不是本函数参数
const g = (...args) => console.log(args); // 用 rest 代替
// 3. 没有 prototype
console.log(A.prototype); // undefined
因此需要动态
this的地方(如对象方法、addEventListener里要拿this指向元素)不适合用箭头函数。
二、解构赋值
解构让我们从数组或对象中按位置/按属性名取出值并赋给变量,是"赋值语法糖"。
2.1 数组解构
js
const arr = [1, 2, 3];
const [a, b, c] = arr; // a=1, b=2, c=3
const [first] = arr; // first=1
const [, , last] = arr; // last=3(用逗号跳过)
// 默认值:只在 undefined 时生效
const [x = 10, y = 20] = [undefined, 5]; // x=10, y=5
// 交换变量:经典应用
let m = 1, n = 2;
[m, n] = [n, m]; // m=2, n=1,无需临时变量
// 剩余收集
const [head, ...rest] = [1, 2, 3, 4]; // head=1, rest=[2,3,4]
2.2 对象解构
js
const user = { id: 1, name: "Tom", age: 18 };
const { name, age } = user; // 按属性名取值
const { id: userId } = user; // 重命名:id → userId
const { email = "无" } = user; // 默认值:无该属性时生效
// 嵌套解构
const data = { info: { city: "哈尔滨" } };
const { info: { city } } = data; // city = "哈尔滨"
注意:对象解构按属性名 匹配,数组解构按位置 匹配;重命名写法是
原属性名: 新变量名。
2.3 函数参数解构
把参数直接"拆开用",配合默认值非常优雅:
js
function showUser({ name, age = 18 }) {
console.log(`${name},${age}岁`);
}
showUser({ name: "Tom" }); // "Tom,18岁"
// 数组参数
function sum([a, b]) { return a + b; }
sum([3, 4]); // 7
这在 React 的 props、组件 options 参数里是标配写法。
三、模板字符串
用反引号 ````` 包裹,支持插值 与多行。
3.1 插值 ${}
js
const name = "Tom";
const age = 18;
// 传统:字符串拼接,易错
const old = "我叫 " + name + ",今年 " + age + " 岁";
// 模板字符串
const s = `我叫 ${name},今年 ${age} 岁`;
${} 里可以是任意表达式,甚至嵌套模板:
js
const total = 10;
const msg = `总计:${total * 2} 元`; // 表达式
const list = `${[1, 2, 3].map(n => n * 2).join(", ")}`; // 方法调用
3.2 多行字符串
js
// 传统:要写 \n 和续行
const html1 = "<div>\n <p>你好</p>\n</div>";
// 模板字符串:直接换行
const html2 = `
<div>
<p>你好</p>
</div>
`;
配合解构渲染 HTML 列表是最高频组合:
js
const users = [{ name: "Tom" }, { name: "Jerry" }];
const html = `
<ul>
${users.map(u => `<li>${u.name}</li>`).join("")}
</ul>
`;
3.3 标签模板(了解)
在模板字符串前加函数名,可对插值结果做预处理(常用于国际化、安全转义):
js
function tag(strings, ...values) {
return strings[0] + values.map(v => `[${v}]`).join("");
}
tag`你好 ${"Tom"}`; // "你好 [Tom]"
四、扩展运算符与剩余参数
... 同一个符号、两种用法:展开 (拆开)与收集(聚合)。
4.1 展开数组
js
// 合并数组
const a = [1, 2], b = [3, 4];
const merged = [...a, ...b]; // [1,2,3,4]
// 复制数组(浅拷贝)
const copy = [...a]; // 等价于 a.slice()
// 函数调用传参
const nums = [3, 5, 1];
Math.max(...nums); // 5(传统写法 Math.max.apply(null, nums))
// 字符串 → 数组
const chars = [..."abc"]; // ["a","b","c"]
4.2 展开对象
js
const base = { x: 1, y: 2 };
const copy = { ...base }; // 浅拷贝
// 合并对象(后面的覆盖前面的同名键)
const cfg = { ...base, y: 9, z: 3 }; // {x:1, y:9, z:3}
函数式更新状态的核心写法(React setState / Vue 不可变更新):
js
const state = { count: 0, name: "Tom" };
const next = { ...state, count: state.count + 1 };
// next 是全新对象,原 state 不变
4.3 剩余参数 rest(函数内收集)
js
function sum(...nums) {
return nums.reduce((s, n) => s + n, 0);
}
sum(1, 2, 3); // 6
// 与解构配合:首参固定,其余收集
const [head, ...rest] = [1, 2, 3]; // rest=[2,3]
箭头函数没有
arguments,用(...args)收集参数是最佳替代。
4.4 重要边界:展开是浅拷贝
... 只复制一层。嵌套的数组/对象仍是引用共享:
js
const original = { list: [1, 2, 3] };
const copy = { ...original };
copy.list.push(4);
console.log(original.list); // [1,2,3,4] ← 被改了!
// 深拷贝(一层的常用手段)
const deepCopy = { ...original, list: [...original.list] };
五、实战案例
实战 1:解构 + 模板字符串渲染列表
js
const users = [
{ id: 1, name: "Tom", city: "哈尔滨" },
{ id: 2, name: "Jerry", city: "北京" },
];
function render() {
const html = `
<ul>
${users.map(({ name, city }) => `
<li>${name} --- ${city}</li>
`).join("")}
</ul>
`;
document.querySelector("#list").innerHTML = html;
}
render();
实战 2:对象不可变更新(函数式)
js
let cart = { items: 1, total: 20 };
// 加一件:返回新对象,不改原对象
cart = { ...cart, items: cart.items + 1, total: cart.total + 20 };
// 数组形式:增删都用展开
let list = [1, 2, 3];
list = [...list, 4]; // 尾部追加
list = [0, ...list]; // 头部插入
list = list.filter(x => x !== 2); // 删除
实战 3:rest + 默认值 + 解构封装请求
js
function request(url, { method = "GET", timeout = 5000 } = {}, ...rest) {
console.log(`请求:${method} ${url},超时 ${timeout}ms`);
console.log("其余参数:", rest);
// ...真正的 fetch 逻辑
}
request("/api/user", { method: "POST" }, "extra1");
// 请求:POST /api/user,超时 5000ms
// 其余参数:["extra1"]
实战 4:数组操作组合拳
js
// 数组去重 + 合并 + 排序
const a = [3, 1, 4], b = [1, 5, 9];
const merged = [...new Set([...a, ...b])].sort((x, y) => x - y);
// [1,3,4,5,9]
// 键值对数组 → 对象(解构 + reduce)
const pairs = [["name", "Tom"], ["age", 18]];
const obj = Object.fromEntries(pairs); // {name:"Tom", age:18}
实战 5:购物车合并去重(实战优先)
js
const cart = [
{ id: 1, name: "苹果", price: 5, qty: 2 },
{ id: 2, name: "香蕉", price: 3, qty: 1 },
];
function addItem(newItem) {
const idx = cart.findIndex(i => i.id === newItem.id);
if (idx === -1) {
return [...cart, { ...newItem, qty: 1 }]; // 新增
}
// 数量累加:不可变更新
return cart.map((item, i) =>
i === idx ? { ...item, qty: item.qty + 1 } : item
);
}
六、高频误区清单
| 误区 | 正解 |
|---|---|
| 箭头函数就是短一点 | this 是词法绑定,且不能当构造函数、无 arguments |
| 对象方法用箭头函数安全 | 需要动态 this(如事件里取元素)时不能用箭头函数 |
| 解构默认值对所有"假值"生效 | 只在值为 undefined 时生效,null 不触发默认值 |
... 是深拷贝 |
只浅拷贝一层,嵌套引用仍共享 |
| 对象展开顺序无所谓 | 后面的同名属性会覆盖前面的 |
箭头函数可以 new |
不能作为构造函数,会抛 TypeError |
| 数组解构按属性名 | 数组按位置,对象才按属性名 |
arguments 在箭头函数里可用 |
箭头函数没有 arguments,用 ...args |
七、总结
一句话记住每个特性:
| 特性 | 一句话 |
|---|---|
| 箭头函数 | 写法更短 + this 继承外层,回调首选 |
| 解构 | 数组按位置、对象按名字,{} 拆出即用 |
| 模板字符串 | 反引号 + ${},告别 + 拼接 |
| 扩展运算符 | ... 合并/复制数组与对象,rest 收集参数 |
四个特性组合起来能解决绝大多数"数据转换 + 渲染"的场景:解构取数据 → 模板字符串渲染 → 展开做不可变更新 → rest 收参数。