ES6 及之后的版本有哪些常用新特性?
ES2015 带来了 let/const、解构、展开、箭头函数、模板字符串、class、模块、Promise、Map/Set、Symbol 和迭代器,之后每年一版,常用的有 async/await、对象展开、可选链、空值合并、BigInt、Array.prototype.at、toSorted 等。回答时按版本说用途,再挑面试官会追问的点讲深。
简答
ES6 也叫 ES2015,是 2015 年发布的那一版。它一次加了很多东西:
let、const有了块级作用域;解构和展开让取值、合并数组更方便;箭头函数写回调更短;模板字符串可以直接插变量;class让继承写法更清楚;import、export有了语言自带的模块;Promise统一了异步写法;Map、Set、Symbol和迭代器也是这一版加的。从 ES2016 开始,规范改成每年一版,每版只加少量功能。常用的有 ES2017 的
async/await,ES2018 的对象展开和Promise.prototype.finally,ES2020 的可选链?.、空值合并??和BigInt,ES2022 的at()、类的私有字段和顶层await,ES2023 的toSorted这类不改原数组的方法。这些特性的用处很具体:
const防止变量被重新赋值,解构配默认值让函数参数更好读,?.和??让读取深层属性、给默认值不再写一长串判断。要注意几个容易问到的细节:
const只是变量不能重新赋值,对象内容照样能改;展开只拷贝一层;箭头函数没有自己的this;??只在null和undefined时取默认值,和||不一样。
按版本看主要特性
ECMAScript 是 JavaScript 的语言规范。TC39 委员会在“已完成提案”列表里给每个特性标了发布年份,下面按这个年份列出面试常问的部分。
| 版本 | 特性 | 用来做什么 |
|---|---|---|
| ES2015 | let、const | 块级作用域,const 不能重新赋值 |
| ES2015 | 解构、默认参数、剩余参数 | 从数组和对象里按位置或名字取值 |
| ES2015 | 数组展开 ... | 复制、合并数组,把数组摊开传参 |
| ES2015 | 箭头函数 | 更短的回调,this 取外层的 |
| ES2015 | 模板字符串 | 插值和多行字符串 |
| ES2015 | class | 构造函数加原型的另一种写法 |
| ES2015 | import / export | 语言自带的模块系统 |
| ES2015 | Promise | 统一的异步结果对象 |
| ES2015 | Map、Set、WeakMap、WeakSet | 任意类型做键的映射、去重集合 |
| ES2015 | Symbol、迭代器、Generator | 唯一键、for...of 和可暂停的函数 |
| ES2016 | Array.prototype.includes、** | 判断包含、求幂 |
| ES2017 | async / await | 用同步的写法写异步流程 |
| ES2018 | 对象展开和剩余属性、Promise.prototype.finally | 浅拷贝合并对象、成功失败都执行的收尾 |
| ES2019 | flat、flatMap、Object.fromEntries | 扁平化数组、把键值对转回对象 |
| ES2020 | ?.、??、BigInt、Promise.allSettled、globalThis | 安全读取、默认值、大整数 |
| ES2021 | replaceAll、Promise.any、??= 等逻辑赋值、数字分隔符 1_000 | 字符串替换、取第一个成功的 Promise |
| ES2022 | at()、类字段和 #私有 成员、顶层 await、Object.hasOwn | 负数下标取值、真正的私有属性 |
| ES2023 | toSorted、toReversed、with、findLast | 不改原数组的排序和替换 |
| ES2024 | Object.groupBy、Promise.withResolvers | 分组、在外面拿到 resolve |
| ES2025 | 迭代器辅助方法、Set 的集合运算、Promise.try | 对迭代器链式 map、filter,求交集并集 |
面试里说“ES6 新特性”,大多数时候指的是 ES2015 这一大版,所以先把第一部分讲清楚,再提几个后续版本里常用的就够了。
一段代码里的常用写法
下面这段把解构默认值、展开、模板字符串、可选链和空值合并放在一起,注意每一行用的是哪个特性。
const user = { name: '小明', profile: { age: 0 } };
const { name, role = '访客' } = user;
const merged = { ...user, role };
console.log(`${name}-${role}`); // 小明-访客
console.log(String(merged.profile === user.profile)); // true
console.log(user.profile?.age ?? 18); // 0
console.log(user.address?.city ?? '未知'); // 未知
role 在 user 里不存在,解构时就用了默认值“访客”。解构默认值只在取到 undefined 时生效,取到 null 不会用默认值。
为什么 merged.profile === user.profile 是 true?因为对象展开只复制第一层属性,profile 是对象,复制过去的是同一个引用,改 merged.profile.age 也会改到 user。要完整复制,见《浅拷贝、深拷贝和 structuredClone 如何选择?》。
user.profile?.age ?? 18 输出 0,没有变成 18。因为 ?? 只把 null 和 undefined 当作“没有值”,0 是有效的值。换成 || 就会得到 18。user.address 不存在,?. 让整个表达式直接返回 undefined,不会报错,再由 ?? 补上“未知”。两者的细节见《可选链和空值合并为什么不能简单替换为 && 和 ||?》。
面试官会接着问什么
let、const 和 var 的区别,最常追问的是暂时性死区和 for 循环里的闭包,见《var、let、const 和暂时性死区有什么关系?》。这里只记一条:const 保证的是变量不能重新赋值,const obj = {} 以后照样能写 obj.a = 1。
箭头函数常被问 this、arguments 和能不能 new,见《箭头函数和普通函数有哪些区别?》。class 常被问它和原型的关系,见《原型链、class 和继承之间是什么关系?》。
模块常被问和 CommonJS 的区别,比如 ES 模块的导入是静态的、导出的是活的绑定,见《ES Modules 和 CommonJS 有哪些关键区别?》。Promise 和 async/await 常连着问事件循环,见《回调地狱是什么?Promise 链式调用怎样解决它?then 的返回值有哪些规则?》和《事件循环中,任务和微任务按什么顺序执行?》。Map、Set 常被问和普通对象的区别,见《Map、Set、WeakMap 与普通对象各有什么用途?》。
常见追问
ES6 和 ES2015 是什么关系?
是同一版。ES6 指第 6 版规范,2015 年 6 月通过,正式名字是 ECMAScript 2015。规范前言写明,从 ECMAScript 2016 开始改为每年发布一版,所以现在一般直接说年份。
新特性在旧浏览器上不能用怎么办?
分两种情况。新语法,比如可选链、类字段,要靠 Babel、TypeScript 这类工具在构建时转成旧语法。新的内置方法,比如
Array.prototype.at,转换语法没用,要引入 polyfill 补上实现。