ES6 到 ES11 核心特性精要:只讲对你现代 JavaScript 开发真正常用的那部分
前言:
从 ES6(ES2015)到 ES11(ES2020)是 JavaScript 语言现代化的关键阶段,持续引入精巧而实用的新特性,使开发者的开发效率、代码可读性与运行时安全性都大大提升了。
而本文旨在系统梳理 ES6 至 ES11 中最具影响力与实用价值的核心特性,聚焦那些被广泛采纳、深刻改变日常编码习惯的语言革新。理解这些特性背后的动机与应用场景,都将帮助你写出更清晰、健壮且富有表现力的现代 JavaScript 代码
ES6(ES2015)—— 革命性更新
几乎所有现代 JavaScript 开发都建立在 ES6 及其后续版本之上。ES6 不仅是一次语法升级,更是对语言设计哲学的重构,使 JavaScript 更适合大型应用开发。
1. let / const:块级作用域变量
函数作用域:
在 ES6 之前,JavaScript 只有函数作用域,没有块级作用域
什么是函数作用域?
变量的作用域仅由 函数边界 决定。在函数内部声明的变量,在整个函数体内都可见;在函数外部则不可见。
例如:
function foo() {
var x = 1;
if (true) {
var x = 2; // 重新赋值,不是新变量!
console.log(x); // 2
}
console.log(x); // 2
}
foo();
即使 var x = 2写在if块内部,但是其仍然属于foo函数作用域(if、for、while 等语句块不会创建新的作用域)
常见陷阱:循环中的闭包问题
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 输出 3, 3, 3(而不是 0,1,2)
}, 100);
}
i 是函数作用域变量,三个 setTimeout 回调共享同一个 i,当它们执行时,循环早已结束,i 的值已是 3
ES5 时代解决方案:用 立即执行函数(IIFE) 创建新作用域
变量提升:
变量提升 是 JavaScript 引擎在代码执行前的一个“预处理”行为。
什么是变量提升?
使用
var声明的变量和function声明的函数,会被自动移动到其所在作用域的顶部(仅声明被提升,赋值不会)。
示例1:变量提升
console.log(a); // undefined(不是 ReferenceError!)
var a = 10;
console.log(a); // 10
就等价于:
var a; // 声明被提升到顶部
console.log(a); // undefined(此时未赋值)
a = 10; // 赋值留在原地
console.log(a); // 10
示例2:函数表达式 vs 函数声明
// 函数声明 → 完整提升
foo(); // 正常执行
function foo() { }
// 函数表达式 → 只有变量名提升
bar(); // TypeError: bar is not a function
var bar = function() { };
常见陷阱:遮蔽
var name = 'xxl';
function test() {
console.log(name); // undefined(不是 'xxl'!)
var name = 'Bob';
console.log(name); // 'Bob'
}
test();
包裹在函数test内部的变量 var name变量提升了导致屏蔽了外部的var name = 'xxl'
🔍 新特性 :
-
let:声明可变的块级作用域变量。 -
const:声明不可重新赋值的块级作用域常量(注意:对象/数组内容仍可修改)。
虽然会声明提升,但不初始化(存在“暂时性死区”),并且不允许重复声明
{
console.log(x); // ReferenceError!
let x = 1;
const y = 2;
x = 3; // 允许
// y = 4; // 报错:Assignment to constant variable
}
console.log(x); // ReferenceError: x is not defined
在 let x执行之前,x处于 “死区”,访问会报错ReferenceError(而不是undefined)
⚠️ 关键点
-
块级作用域:
{}内部即为一个作用域(如if、for、函数体)。 -
const并非“完全不可变”:
const obj = { a: 1 };
obj.a = 2; // 允许(修改属性)
// obj = {}; // 不允许(重新赋值)
使用建议
- 默认使用
const,只有需要重新赋值时才用let。 - 完全避免使用
var。
2. 箭头函数(Arrow Functions)
🔍 语法简化
// 普通函数
const add1 = function(a, b) {
return a + b;
};
// 箭头函数
const add2 = (a, b) => a + b;
// 单参数可省略括号
const square = x => x * x;
// 无参数
const sayHi = () => console.log('Hi');
核心优势:不绑定自己的 this
箭头函数的 this 继承自外层作用域(词法作用域),解决了回调中 this 指向丢失的问题:
class Timer {
constructor() {
this.seconds = 0;
}
start() {
// 普通函数:this 指向 window(非严格模式)或 undefined
// setInterval(function() { this.seconds++; }, 1000); // 不好
// 箭头函数:this 指向 Timer 实例
setInterval(() => {
this.seconds++; // 正确
}, 1000);
}
}
⚠️ 注意事项
- 不能用作构造函数(无
new.target,无prototype)。 - 没有
arguments对象。 - 不适合需要动态
this的场景(如事件处理器需访问 DOM 元素时)。
3. 模板字符串(Template Literals)
🔍 多行字符串 + 字符串插值
使用反引号 ` 包裹,支持:
- 换行(保留格式)
-
${expression}插入变量或表达式
const name = 'Alice';
const age = 30;
// 传统方式(繁琐且易错)
const msg1 = 'Hello ' + name + '!\nYou are ' + age + ' years old.';
// 模板字符串
const msg2 = `
Hello ${name}!
You are ${age} years old.
`;
console.log(msg2);
// 输出:
// Hello Alice!
// You are 30 years old.
4. 解构赋值(Destructuring Assignment)
🔍 从 数组或对象 中提取数据并赋值
数组解构:
const [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1 2 3
// 跳过元素
const [x, , z] = [1, 2, 3]; // x=1, z=3
// 默认值
const [p = 10, q = 20] = [5]; // p=5, q=20
对象解构
const user = { name: 'Alice', age: 30 };
// 基本解构
const { name, age } = user;
// 重命名
const { name: userName, age: userAge } = user;
// 默认值
const { email = 'unknown@qq.com' } = user; // 如果user中包含email,那么这里的默认值就会被忽略
// 即 user.email === undefined ? 'unknown@qq.com' : user.email
常见应用场景
-
函数参数解构:
function createUser({ name, age }) { return { name, age }; } createUser({ name: 'Bob', age: 25 }); -
交换变量:
[a, b] = [b, a];
5. 模块化(Modules)
🔍 使用 import / export 实现代码分割与复用
导出(export)
// utils.js
export const PI = 3.14;
export function add(a, b) {
return a + b;
}
export default class Calculator { } // 默认导出(一个文件只能有一个)
导入(import)
// main.js
import Calculator, { PI, add } from './utils.js';
// 重命名
import { add as sum } from './utils.js';
// 导入所有
import * as utils from './utils.js';
优势
- 明确依赖关系
- 避免全局污染
6. Promise:统一异步处理
🔍 解决“回调地狱”(Callback Hell)
// 回调地狱(难以维护)
getData(function(a) {
getMoreData(a, function(b) {
getEvenMoreData(b, function(c) {
// ...
});
});
});
// Promise 链式调用
getData()
.then(a => getMoreData(a))
.then(b => getEvenMoreData(b))
.then(c => { /* ... */ })
.catch(err => console.error(err));
核心方法
-
.then():处理成功结果 -
.catch():捕获错误 -
.finally()(ES2018):无论成功/失败都执行
7. 类(Class)
🔍 语法糖,更清晰的面向对象写法
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal { // extends 负责继承
constructor(name, breed) {
super(name); // 调用父类构造函数
this.breed = breed;
}
speak() {
console.log(`${this.name} barks.`); // 方法重写
}
}
优势
- 代码更易读、更接近其他 OOP 语言。
- 避免手动操作
prototype的复杂性。
8. 默认参数 & 剩余/展开运算符(Rest/Spread)
默认参数
function greet(name = 'Guest', greeting = 'Hello') {
return `${greeting}, ${name}!`;
}
greet(); // "Hello, Guest!"
greet('Alice'); // "Hello, Alice!"
🔍 剩余参数(Rest Parameters)
将多个参数收集为数组:
function sum(...numbers) {
return numbers.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4); // numbers = [1, 2, 3, 4]
🔍 展开运算符(Spread Syntax)
将数组/对象展开:
// 数组
const arr1 = [1, 2];
const arr2 = [...arr1, 3, 4]; // [1, 2, 3, 4]
// 函数调用
Math.max(...arr1); // 等价于 Math.max(1, 2)
应用场景
- 浅拷贝数组/对象
- 合并数据
- 灵活处理函数参数
ES7(ES2016)—— 精简而实用的增强
ES7 并没有像 ES6 那样带来革命性变化,而是聚焦于解决日常开发中的小痛点,提升代码可读性与表达力。
1. Array.prototype.includes():判断数组是否包含某个值
背景:为什么需要它?
在 ES7 之前,我们通常用 indexOf 判断元素是否存在:
if ([1, 2, 3].indexOf(2) !== -1) {
// 存在
}
但是这种方式:
-
语义不清晰:
indexOf返回索引,需额外判断是否为-1 -
无法正确处理
NaN:[NaN].indexOf(NaN); // -1(错误!)
🔍 includes() 的优势:
-
语义明确:直接返回
true/false - 能正确识别
NaN -
支持
fromIndex参数(从指定位置开始搜索)
// 基本用法
[1, 2, 3].includes(2); // true
['a', 'b', 'c'].includes('d'); // false
// 处理 NaN(这是 includes() 最大的亮点)
[NaN].includes(NaN); // true
[1, NaN, 3].includes(NaN); // true
// fromIndex:从索引 2 开始查找
[1, 2, 3, 4].includes(3, 2); // true
[1, 2, 3, 4].includes(3, 3); // false(索引 3 是 4)
// 类型严格相等(===),不会类型转换
[1, 2, 3].includes('2'); // false(字符串 '2' ≠ 数字 2)
⚠️ 注意事项
- 使用 SameValueZero 算法 比较(与
===几乎相同,唯一例外是NaN === NaN成立) - 不会跳过空槽(empty slots):
const arr = [, ,]; arr.includes(undefined); // true(空槽被视为 undefined)
2. 指数运算符 **:用于计算幂运算(x 的 y 次方)
背景:之前的写法
在 ES7 之前,只能使用 Math.pow():
Math.pow(2, 3); // 8
但这种方式:
-
不够直观:数学表达式
2³无法直接书写 - 可读性差:嵌套复杂表达式时难以理解
🔍 ** 的优势
-
符合数学直觉:
2 ** 3就是 “2 的 3 次方” - 支持赋值运算符
**= -
右结合性(符合数学规则):
2 ** 3 ** 2=2 ** (3 ** 2)=2 ** 9
// 基本幂运算
2 ** 3; // 8
5 ** 2; // 25
2 ** -1; // 0.5(负指数)
// 结合变量
const x = 3;
x ** 2; // 9
// 赋值运算符
let a = 2;
a **= 3; // a = a ** 3 → a = 8
// 右结合性(重要!)
2 ** 3 ** 2; // 512(因为 3**2=9,再 2**9=512)
// 等价于:
2 ** (3 ** 2); // 512
// 而不是:
(2 ** 3) ** 2; // 64
⚠️ 注意事项
-
不能有空格:
2 * * 3会报错(必须写成2**3) -
优先级高于一元运算符:
-2 ** 2; // -4(等价于 -(2 ** 2)) // 若想计算 (-2)²,需加括号: (-2) ** 2; // 4
ES8(ES2017)—— 异步编程的里程碑
ES8 聚焦于提升开发体验:让异步代码更清晰、对象遍历更直观、字符串处理更便捷。
1. async / await:让异步代码像同步一样书写
背景:回调地狱与 Promise 链
在 async/await 出现前,处理多个异步操作非常繁琐:
// 回调地狱(难以维护)
getData((err, data) => {
getMoreData(data, (err, more) => {
getEvenMore(more, (err, final) => { /* ... */ });
});
});
// Promise 链(稍好,但嵌套逻辑仍复杂)
fetch(url)
.then(res => res.json())
.then(data => fetch(data.url))
.then(res => res.json())
.catch(err => console.error(err));
🔍 async/await 的核心优势
- 语法简洁:用同步风格写异步逻辑
-
错误处理统一:可用
try/catch -
调试友好:断点可正常步入
await行 -
基于 Promise:
await后面必须是 Promise
// 1. 基本用法
async function fetchUser(id) {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
return user;
}
// 2. 错误处理
async function safeFetch() {
try {
const res = await fetch('/api/data');
if (!res.ok) throw new Error('Network error');
return await res.json();
} catch (err) {
console.error('Failed to fetch:', err);
return null;
}
}
// 3. 并行请求(避免串行等待)
async function fetchAll() {
const [user, posts] = await Promise.all([
fetchUser(1),
fetchPosts()
]);
return { user, posts };
}
⚠️ 注意事项
await只能在async函数内使用-
顶层 await :仅在模块(
type="module")或现代 Node.js 中支持 -
不要滥用串行:无关的异步操作应并行(用
Promise.all)
最佳实践
- 用
async/await替代.then()链 - 总是配合
try/catch处理错误 - 对独立请求使用
Promise.all提升性能
2. Object.values() / Object.entries():对象遍历像数组一样简单
背景:传统对象遍历的痛点
过去遍历对象需结合 for...in + hasOwnProperty,或先取键再映射:
const obj = { a: 1, b: 2 };
// 旧方式:获取值
const values = [];
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
values.push(obj[key]);
}
}
// 或
const values = Object.keys(obj).map(key => obj[key]);
🔍 新方法的优势
| 方法 | 返回值 | 用途 |
|---|---|---|
Object.keys(obj) |
['a', 'b'] |
获取所有可枚举属性名 |
Object.values(obj) |
[1, 2] |
直接获取所有值 |
Object.entries(obj) |
[['a',1], ['b',2]] |
获取键值对数组 |
const user = { name: 'Alice', age: 30, city: 'Paris' };
// 获取所有值
console.log(Object.values(user));
// ['Alice', 30, 'Paris']
// 获取键值对(常用于 map 渲染)
console.log(Object.entries(user));
// [['name', 'Alice'], ['age', 30], ['city', 'Paris']]
// 在 React/Vue 中遍历对象
Object.entries(user).map(([key, value]) => (
// 方法
));
// 计算对象总和(如统计)
const scores = { math: 90, english: 85 };
const total = Object.values(scores).reduce((a, b) => a + b, 0); // 175
⚠️ 注意事项
-
只包含可枚举属性(不包括
Symbol键,除非用Object.getOwnPropertySymbols) - 不保证顺序(但现代引擎通常按创建顺序)
3. String.prototype.padStart() / padEnd():字符串补全(左填充 / 右填充)
背景:格式化需求常见但实现繁琐
格式化时间(5 → 05)、对齐文本、生成固定长度 ID 等,过去需手动拼接或正则处理。
🔍 新方法的优势
-
语义清晰:
padStart(左补)、padEnd(右补) - 自动截断:若原字符串长度 ≥ 目标长度,直接返回原字符串
- 支持多字符填充
// 时间格式化
'7'.padStart(2, '0'); // '07'
'12'.padStart(2, '0'); // '12'(长度已够,不变)
// 生成固定长度编号
'123'.padStart(6, '0'); // '000123'
// 右对齐文本(控制台输出)默认填充空
'Price'.padEnd(10) + '$10'; // 'Price $10'
// 多字符填充
'abc'.padStart(8, 'xy'); // 'xyxyxabc'(重复填充 'xy')
// 安全处理:目标长度小于原长
'hello'.padStart(3, '0'); // 'hello'(不会截断!)
⚠️ 注意事项
padString默认是空格' '-
填充字符串会被截断以适应目标长度:
'a'.padStart(5, 'xyz'); // 'xyzxa'(不是 'xyzxy')
ES9(ES2018)—— 对象操作与异步收尾的优雅升级
ES9 延续了“小而美”的更新风格,聚焦开发者日常痛点,让对象处理更灵活,异步逻辑更完整。
1. 对象展开/剩余运算符(ES6中 数组展开/剩余语法的延展)
🔍 特性一:对象展开(Object Spread)—— 合并/克隆对象
// 1. 浅拷贝对象
const user = { name: 'Alice', age: 30 };
const clone = { ...user }; // { name: 'Alice', age: 30 }
// 2. 合并多个对象(后覆盖前)
const defaults = { theme: 'light', lang: 'en' };
const settings = { lang: 'zh', fontSize: 14 };
const config = { ...defaults, ...settings };
// { theme: 'light', lang: 'zh', fontSize: 14 }
// 3. 添加新属性
const person = { name: 'Bob' };
const employee = { ...person, role: 'developer', id: 101 };
// { name: 'Bob', role: 'developer', id: 101 }
⚠️ 注意事项:
-
浅拷贝:嵌套对象仍共享引用(引用式赋值)
const original = { a: { b: 1 } }; const copy = { ...original }; copy.a.b = 2; console.log(original.a.b); // 2(被修改了!) - 属性覆盖顺序:后者属性会覆盖前者同名属性
-
非对象值会被忽略(但不会报错):
{ ...null, ...undefined, x: 1 } // { x: 1 }
🔍 特性二:对象剩余(Object Rest)—— 提取“其余属性”
// 1. 提取部分属性,其余打包
const user = { id: 1, name: 'Alice', email: 'a@example.com', role: 'admin' };
const { id, ...userInfo } = user;
// id = 1
// userInfo = { name: 'Alice', email: 'a@example.com', role: 'admin' }
// 2. 函数参数中移除特定字段
function omit(obj, key) {
const { [key]: _, ...rest } = obj; // 使用计算属性名 + 忽略变量 _
return rest;
}
omit({ a: 1, b: 2, c: 3 }, 'b'); // { a: 1, c: 3 }
// 3. React 中分离 props
function MyComponent({ className, children, ...restProps }) {
return <div className={className} {...restProps}>{children}</div>;
}
⚠️ 注意事项:
-
只能用在解构的最后:
const { ...rest, name } = user; // SyntaxError - 不会包含原型链上的属性(只处理自身可枚举属性)
2. Promise.prototype.finally():Promise 都执行的回调
背景:为什么需要 finally?
在 ES9 之前,要实现“无论成功失败都执行”的逻辑,必须在 .then() 和 .catch() 中重复写代码
// ES8 及之前(冗余)
showLoading();
fetch('/api/data')
.then(data => {
hideLoading(); // ← 重复
return processData(data);
})
.catch(err => {
hideLoading(); // ← 重复
showError(err);
});
🔍 finally() 的优势
- 消除重复代码
- 语义清晰:明确表示“收尾操作”
-
不改变 Promise 状态:
finally中的返回值会被忽略,错误会继续抛出
// 1. 隐藏 loading 指示器
showLoading();
fetchUserData()
.then(user => renderUser(user))
.catch(err => showErrorMessage(err))
.finally(() => hideLoading()); // 无论成功失败都隐藏
// 2. 清理资源
let dbConnection;
openDB()
.then(conn => {
dbConnection = conn;
return conn.query('SELECT * FROM users');
})
.finally(() => {
if (dbConnection) dbConnection.close(); // 安全关闭
});
// 3. finally 中抛出错误会传递下去
Promise.resolve(1)
.finally(() => {
throw new Error('Oops!');
})
.catch(err => console.log(err.message)); // "Oops!"
⚠️ 关键行为:
| 场景 |
finally回调是否执行? |
|---|---|
| Promise 成功 | 是 |
| Promise 失败 | 是 |
finally 中返回值 |
被忽略(不影响链式结果) |
finally 中抛出错误 |
会中断后续 .then(),进入 .catch()
|
💡
finally不接收参数(因为它不知道 Promise 是成功还是失败):
promise.finally(result => { /* result 是 undefined! */ });
ES10(ES2019)—— 精巧实用的工具方法
ES10 聚焦于“补齐生态”,提供对称、一致、标准化的工具函数,让开发者告别手动实现或依赖第三方库。
1. Array.prototype.flat() / flatMap():轻松处理嵌套数组
背景:手动扁平化很麻烦
过去要展平嵌套数组,需用 reduce + 递归或 concat.apply:
// 手动展平一层
const arr = [[1, 2], [3, 4]];
const flat = [].concat(...arr); // [1, 2, 3, 4]
// 展平多层?更复杂!
🔍 flat():按需展平指定层数
// 展平一层(默认)
[[1, 2], [3, [4]]].flat();
// → [1, 2, 3, [4]]
// 展平两层
[[1, 2], [3, [4]]].flat(2);
// → [1, 2, 3, 4]
// 展平任意深度
[1, [2, [3, [4, [5]]]]].flat(Infinity);
// → [1, 2, 3, 4, 5]
// 自动跳过空槽(empty slots)
[1, , [2, 3]].flat();
// → [1, 2, 3](中间的空位被忽略)
🔍 flatMap():先 map 再 flat(一层)
// 场景:将句子拆分为单词数组并合并
const sentences = ['Hello world', 'JS is fun'];
const words = sentences.flatMap(sentence => sentence.split(' '));
// → ['Hello', 'world', 'JS', 'is', 'fun']
// 对比传统写法:
sentences.map(s => s.split(' ')).flat(); // 效果相同,但 flatMap 更高效(只遍历一次)
// 过滤 + 映射(返回空数组可过滤掉元素)
[1, 2, 3, 4].flatMap(n =>
n % 2 === 0 ? [n * 2] : []
);
// → [4, 8](奇数被过滤)
⚠️ 注意事项:
-
flatMap只能展平一层(内部调用flat(1)) - 回调函数必须返回数组(否则会报错或行为异常)
2. Object.fromEntries():将键值对列表转回对象
背景:对象 ↔ 键值对 的转换不对称
ES8 引入了 Object.entries(obj),但没有对应的反向方法,导致链式操作中断:
// 想过滤对象属性?需手动重建对象
const obj = { a: 1, b: 2, c: 3 };
const filtered = Object.keys(obj)
.filter(k => obj[k] > 1)
.reduce((acc, k) => ({ ...acc, [k]: obj[k] }), {});
// 复杂且低效!
🔍 fromEntries() 的优势
-
完美对称:
Object.fromEntries(Object.entries(obj)) === obj(浅拷贝) -
支持链式操作:结合
entries()实现函数式对象变换
// 1. 过滤对象属性(函数式风格)
const obj = { a: 1, b: 2, c: 3 };
const filtered = Object.fromEntries(
Object.entries(obj).filter(([key, val]) => val > 1)
);
// → { b: 2, c: 3 }
// 2. 转换 Map 到普通对象
const map = new Map([['name', 'Alice'], ['age', 30]]);
const user = Object.fromEntries(map);
// → { name: 'Alice', age: 30 }
// 3. 修改键名(如 API 字段重命名)
const apiData = { user_id: 123, user_name: 'Bob' };
const cleanData = Object.fromEntries(
Object.entries(apiData).map(([key, val]) =>
[key.replace('user_', ''), val]
)
);
// → { id: 123, name: 'Bob' }
⚠️ 注意事项:
- 输入必须是可迭代对象(如
Array,Map) - 每个元素必须是长度 ≥2 的数组或类似结构(取前两个值作为
[key, value])
3. trimStart() / trimEnd():标准化字符串首尾空白清理
背景:浏览器兼容性问题
早期浏览器提供了非标准方法:
-
trimLeft()/trimRight()(Chrome、Firefox 支持) - 但 Safari 和 IE 不一致
ES10 引入了标准化名称,并让旧方法成为别名:
| 标准方法(ES10+) | 非标准别名(仍可用) |
|---|---|
str.trimStart() |
str.trimLeft() |
str.trimEnd() |
str.trimRight() |
所有现代浏览器现在都同时支持新旧两种写法,但推荐使用标准名称。
const str = ' Hello World! ';
console.log(str.trimStart()); // 'Hello World! '
console.log(str.trimEnd()); // ' Hello World!'
console.log(str.trim()); // 'Hello World!'(移除两端)
// 别名仍然有效(但不推荐)
console.log(str.trimLeft() === str.trimStart()); // true
console.log(str.trimRight() === str.trimEnd()); // true
ES11(ES2020)—— 安全、精确与跨环境统一
ES11 聚焦于 空值安全处理、大整数支持 和 跨平台一致性,极大提升了代码的健壮性与可移植性。
1. 空值合并运算符 ??:仅在值为 null 或 undefined 时提供默认值
背景:|| 的陷阱
传统使用逻辑或 || 设置默认值存在严重问题:
const name = input || 'default';
但 || 会将所有 falsy 值(如 0, '', false, NaN)都视为“无效”,导致意外覆盖:
// 问题示例
const count = 0;
console.log(count || 10); // 10(但 0 是有效值!)
const title = '';
console.log(title || 'Untitled'); // 'Untitled'(但空字符串可能是用户故意输入的)
🔍 ?? 的核心优势
- 只响应
null和undefined -
保留其他 falsy 值(
0,'',false,NaN等)
// 正确处理边界值
0 ?? 10; // 0(保留 0)
'' ?? 'text'; // ''(保留空字符串)
false ?? true; // false(保留布尔 false)
NaN ?? 0; // NaN(保留 NaN)
// 仅当真正“无值”时用默认值
null ?? 'default'; // 'default'
undefined ?? 'default'; // 'default'
// 实际场景:配置项默认值
const config = {
timeout: userConfig.timeout ?? 5000, // 若用户没设 timeout 才用默认
retries: userConfig.retries ?? 3
};
⚠️ 注意事项
-
不能与
&&或||混用(需加括号):// SyntaxError a || b ?? c; // 必须加括号 (a || b) ?? c; a || (b ?? c); -
与可选链
?.天然搭配(见下文)
2. 可选链操作符 ?.:安全访问嵌套属性
背景:深层属性访问风险高
传统写法需层层检查:
// 冗长且易错
const city = user && user.address && user.address.city;
// 或使用 try/catch(过度)
🔍 ?. 的核心优势
-
自动短路:若左侧为
null/undefined,立即返回undefined而不报错 - 支持多种访问形式:属性、方法调用、动态属性
const user = {
name: 'Alice',
address: { city: 'Paris' },
getAge: () => 30
};
// 安全读取属性
user?.address?.city; // 'Paris'
user?.profile?.email; // undefined(不报错)
// 安全调用方法
user?.getAge?.(); // 30
user?.getProfile?.(); // undefined(方法不存在)
// 动态属性
const key = 'city';
user?.address?.[key]; // 'Paris'
// 与空值合并搭配(完美组合)
const email = user?.contact?.email ?? 'no-email@example.com';
⚠️ 注意事项
-
不会跳过 falsy 值(如
0,''):const obj = { a: { b: 0 } }; obj.a?.b; // 0(正常返回,因为 a 存在) -
不能用于赋值左侧:
user?.address = {}; // SyntaxError
3. BigInt:表示任意精度的整数(突破 Number.MAX_SAFE_INTEGER 限制)
背景:JavaScript 数字的精度限制
- JavaScript 的
Number类型基于 IEEE 754 双精度浮点数 -
安全整数范围:
-(2^53 - 1)到2^53 - 1(即±9007199254740991) - 超出此范围会丢失精度:
9007199254740992 === 9007199254740993; // true!(精度丢失)
🔍 BigInt 的核心优势
- 任意精度整数(仅受内存限制)
- 原生支持大数运算
// 超大整数运算
const maxSafe = BigInt(Number.MAX_SAFE_INTEGER);
console.log(maxSafe + 1n); // 9007199254740992n(精确!)
// 比较
10n === 10; // false(类型不同)
10n == 10; // true(类型转换)
// 不支持与 Number 混合运算
// 10n + 10; // TypeError
10n + BigInt(10); // 20n
// 常见用途:加密、ID(如 Twitter Snowflake ID)、金融计算
⚠️ 注意事项
- 不能与
Number直接运算或比较(严格相等) -
不支持
Math对象方法(如Math.sqrt(16n)会报错) -
JSON 不支持序列化 BigInt(需自定义 replacer):
JSON.stringify({ id: 123n }); // TypeError // 解决方案: JSON.stringify({ id: 123n }, (k, v) => typeof v === 'bigint' ? v.toString() : v);
4. globalThis:统一获取全局对象(跨环境兼容)
背景:不同环境全局对象名称不同
| 环境 | 全局对象 |
|---|---|
| 浏览器 | window |
| Web Worker | self |
| Node.js | global |
| Deno | globalThis |
过去需写兼容代码:
// 丑陋的兼容写法
const getGlobal = () => {
if (typeof window !== 'undefined') return window;
if (typeof global !== 'undefined') return global;
if (typeof self !== 'undefined') return self;
};
🔍 globalThis 的核心优势
- 单一标准属性,在所有 JavaScript 环境中可用
- 指向当前环境的全局对象
// 任何环境下都安全
globalThis.myApp = { version: '1.0' };
// 在浏览器中:
console.log(globalThis === window); // true
// 在 Node.js 中:
console.log(globalThis === global); // true
// 用于 polyfill 或库开发
if (!globalThis.Promise) {
// 提供 Promise polyfill
}
💡 使用场景
- 编写跨平台库(如 Lodash、Axios)
- 全局状态管理(谨慎使用)
- 检测环境特性(如
globalThis.crypto)
ES6–ES11 极简总结
-
ES6(2015) :现代化基石
let/const、箭头函数、模块、Promise、Class、解构、模板字符串 —— 奠定现代 JS 开发范式。 -
ES7(2016) :小而实用
includes()(安全查数组)、**(幂运算)—— 提升语义清晰度。 -
ES8(2017) :异步革命
async/await让异步如同步;Object.entries/values、padStart简化对象与字符串操作。 -
ES9(2018) :对象与 Promise 完善
对象展开/剩余({...obj})、Promise.finally()—— 操作更对称,收尾更统一。 -
ES10(2019) :工具补齐
flat()/flatMap()扁平数组,Object.fromEntries()逆转 entries,trimStart/End标准化。 -
ES11(2020) :安全与跨平台
?.(可选链)、??(空值合并)防崩溃;BigInt支持大整数;globalThis统一全局对象。