ES6+ 必用新特性:箭头函数、解构、模板字符串、扩展运算符

写在前面

ES6(即 ES2015)是 JavaScript 历史上规模最大的一次升级,之后每年的新版本(ES2016~)统称 ES6+。它对现代 JS 的影响是全方位的:let/const 取代 var、类与模块、Promise 与 async/await......但真正让日常代码"脱胎换骨"的,是四个使用频率最高、组合起来威力巨大的语法特性:

  1. 箭头函数 ------ 更短的函数写法 + 词法 this
  2. 解构赋值 ------ 从数组/对象中"拆"出值;
  3. 模板字符串 ------ 字符串插值与多行;
  4. 扩展运算符 / 剩余参数 ------ 数组与对象的合并、复制、收集。

本文从语法讲到 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 收参数

相关推荐
梨想橙汁36 分钟前
DOM 与 JS 事件:页面元素操作、事件冒泡、事件委托实战
前端·javascript
Dreams°1231 小时前
【React+TS+ECharts可视化避坑:图表空白、不实时更新、数据池堆积卡顿完整复盘(附完整源码)】
前端·react.js·echarts
计算机魔术师1 小时前
三年前估值45亿,现在129亿——Hugging Face凭什么被老黄盯上?
前端
一个游离的指针1 小时前
浏览器的渲染原理
前端·javascript
zttbee1 小时前
vue2的API大白话讲解
前端·vue.js·前端框架
几何心凉1 小时前
ToDesk/向日葵/UU远程,谁才是真正的“口袋里的办公室”
前端
亿元程序员1 小时前
一个人,4个岗位,20天:我用Cursor+Codex上线了一款微信小游戏
前端
LabVIEW开发2 小时前
将自定义 DLL 与 INI 文件部署到 LabVIEW 实时目标
前端·labview·labview知识·labview功能·labview程序
IT_陈寒2 小时前
Redis Pipeline用错竟比不用还慢,这个坑我帮你踩过了
前端·人工智能·后端