深入浅出ES6(一)
深入浅出 ES6+
一、导读
1 怎么读这篇笔记
| 定义: | 用几句话说明「这东西到底解决啥问题」 |
| 代码块 | 常有 // 语法要点(提要)、//正确示例、//错误示例,从上到下对着看 |
| 啥时候用 | 列几条「实际写业务时会在哪碰到」 |
二、变量绑定与函数写法
本章:变量怎么声明更省心、函数怎么写短、字符串和对象怎么「拆」着用。
1 let 与 const
定义:以前 var 容易「漏」到整个函数里;let / const 只活在花括号 {} 这一块。let 可以改值,const 是「这个名字别换指向」——但对象里面该改还能改。必须先写好声明再用,不然就报错,大家常说的「暂时性死区」就是这个。
// 语法要点:let 变量名 = 初始值; const 常量名 = 初始值;(const 必须初始化)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0); // 0 1 2
}
//正确示例
const API_BASE = 'https://mp.csdn.net';
let retryCount = 0;
retryCount++;
const o = {};
o.a = 1; // 改对象属性合法,绑定仍是同一引用
//错误示例
console.log(x); // TDZ:在声明前访问
let x = 1;
const p = {};
p = {}; // TypeError:不能替换 const 绑定本身
啥时候用
- 循环与临时量 — 用
let,避免var泄漏到函数作用域。- 配置与句柄 — 用
const表达「不要换绑定」,减少误赋值。
2 箭头函数
定义:箭头函数写起来短,但它的 this 不「自己说了算」,而是跟外层的 this 走;也不能拿来 new。适合写在 map、setTimeout 里那种一两句就完事的回调。
// 语法要点:(参数) => 表达式 或 (参数) => { 语句; return 值; }
const nums = [1, 2, 3].map((n) => n * 2);
//正确示例
class Timer {
constructor() {
this.sec = 0;
}
start() {
this.id = setInterval(() => {
this.sec++; // 词法 this 指向 Timer 实例
}, 1000);
}
}
//错误示例
const Btn = () => {};
new Btn(); // TypeError:箭头函数不能作为构造函数
const obj = {
name: 'a',
fn: () => console.log(this.name), // 非「方法」this,常指向外层/global
};
啥时候用
- 数组高阶函数 —
map/filter等短逻辑。- 定时器与回调 — 在类或闭包内需要继承外层
this时。
3 模板字符串与标签模板
定义:键盘上数字 1 左边那个反引号成对包住字符串,里面可以写 ${变量} 插进去,也能直接换行,比一堆 + 好读。前面再挂个函数名的叫标签模板,适合做「安全拼接」一类工具。
// 语法要点:`文本 ${expr}` 与 tag`hello ${name}`
const msg = `第 ${page} 页,共 ${total} 条`;
//正确示例
function sql(strings, ...vals) {
return { strings, vals }; // 真实项目应对 vals 做参数化,避免裸拼接 SQL
}
//错误示例
const q = `SELECT * FROM users WHERE id = ${userId}`; // SQL 注入风险
啥时候用
- 动态文案 / URL — 替代大量
+拼接。- 标签模板 — 统一转义与占位策略。
4 解构赋值
定义:就是「左边长得像右边」,一口气把数组或对象里的值抠出来赋给变量;可以写默认值、嵌套,也能用 ... 把剩下的兜走。
// 语法要点:const [a, b = 2, ...rest] = arr; const { x, y: alias, z = 0 } = obj;
function draw({ x = 0, y = 0 } = {}) {
return [x, y];
}
//正确示例
const { data: { items = [] } = {} } = response;
//错误示例
let a;
{a} = { a: 1 }; // 语法错误:`{` 被解析为块语句
// 应写为:
({ a } = { a: 1 });
啥时候用
- 函数参数配置对象 — 一眼看出可用字段与默认值。
- 交换、丢弃字段 — 数组解构、省略占位。
5 展开与剩余运算符 ...
定义:三个点 ...:往展开用,就是把东西「摊开」抄一份;往剩余用,就是「前面分完了,剩下的都给我」。注意剩余只能写在最后,不然语法不过。
// 语法要点:fn(...iterable) { ...obj } const [first, ...rest] = arr;
const copy = [...original];
const cfg = { ...defaults, ...user };
//正确示例
function sum(...nums) {
return nums.reduce((a, b) => a + b, 0);
}
//错误示例
const [a, ...mid, b] = [1, 2, 3, 4]; // SyntaxError:剩余必须在末尾
啥时候用
- 浅拷贝与合并 — 数组、对象字面量。
- 不可变更新 — React/Redux 风格 state。
6 默认参数、剩余参数与解构参数
定义:函数参数可以带默认值,调用的人不传就用默认的;多出来的参数可以用 ...args 收成数组。别写「后面参数名字出现在前面默认值里」那种绕圈,容易把自己绕晕。
// 语法要点:function f(a = 1, b = a + 1, ...rest) {}
function fetchUser(id, { signal, timeout = 5000 } = {}) {}
//正确示例
function clamp(n, min = 0, max = 100) {
return Math.min(max, Math.max(min, n));
}
//错误示例
function g(a = b, b = 1) {} // 引用在后的参数,易 TDZ / 逻辑混乱,应避免
啥时候用
- HTTP 封装 —
signal、timeout等可选配置。- 工具函数 — 边界值
min/max默认。
三、异步流程与迭代
本章:异步怎么串起来、循环怎么不踩坑。
1 Promise 与组合
定义:可以把 Promise 理解成「过会儿才出结果的票」;.then 一串链下去。多个请求要一起等、要赛跑、要「每个结果都要记下来」时,用 all / race / allSettled / any 这些组合方法;其中后两个在后面也有一节提到。
// 语法要点:new Promise((resolve, reject) => {}) .then().catch().finally()
const data = await Promise.all([fetchA(), fetchB()]);
//正确示例
return fetch(url).then((r) => {
if (!r.ok) throw new Error(r.statusText);
return r.json();
});
//错误示例
fetch(url).then((r) => r.json()); // 未检查 r.ok,错误响应仍当 JSON 解析
啥时候用
- 并发 IO —
Promise.all聚合。- 与旧库衔接 — 无
async时的组合方式。
2 async / await
定义:在函数前加 async,里面就能写 await:读起来像同步,但并不会把整线程卡死。await 的是「没依赖就别一行一个排着等」,能并行就 Promise.all 一把梭。
// 语法要点:async function name() { const v = await promise; }
async function load() {
const user = await getUser();
const orders = await getOrders(user.id);
return { user, orders };
}
//正确示例
async function safe() {
try {
return await mayFail();
} catch (e) {
logger.error(e);
throw e;
}
}
//错误示例
async function serial() {
const a = await slowA(); // 与 b 无依赖却串行
const b = await slowB();
}
// 应改为:const [a, b] = await Promise.all([slowA(), slowB()]);
啥时候用
- 业务编排 — 路由、服务层顺序逻辑。
- 测试 —
setup/teardown异步初始化。
3 for...of 与 for...in 辨析
定义:for...of 是「挨个拿值」,适合数组、Map、Set、字符串。for...in 是「拿键名」,数组用它容易踩到原型链上的坑,正常遍历数组别用它。
// 语法要点:for (const item of iterable) {}
for (const ch of '你好') {
console.log(ch);
}
//正确示例
const map = new Map([
['a', 1],
['b', 2],
]);
for (const [k, v] of map) {
void k;
void v;
}
//错误示例
for (const k in [10, 20]) {
void k;
} // 键为字符串索引,且有继承属性风险;数组遍历勿用 for...in
啥时候用
- 数组 / Map / Set / 字符串 — 统一迭代语法。
- 自定义数据源 — 配合迭代器协议。
四、类、模块与对象字面量
本章:
class长啥样、文件之间怎么互相引用、对象字面量有啥偷懒写法。
1 class 语法糖
定义:class 写起来像 Java,底下还是原型那一套。继承时记得先在子类 constructor 里调用 super(),再动 this。带 # 的是「真藏起来」的私有字段。
// 语法要点:class Child extends Parent { constructor() { super(); } }
class Model {
#id;
constructor(id) {
this.#id = id;
}
get id() {
return this.#id;
}
}
//正确示例
class Service extends BaseService {
async run() {
await super.run();
}
}
//错误示例
class Bad extends BaseService {
constructor() {
this.x = 1; // ReferenceError:必须先 super()
super();
}
}
啥时候用
- 领域模型 / SDK — OOP 风格封装。
- 遗留类组件 — React/Vue 类组件(若仍维护)。
2 ES Module:import / export
定义:文件顶上的 import / export 是「写死」在结构里的,打包工具好做优化。要按条件再加载文件,用后面的 import() 动态加载。
// math.js
export const add = (a, b) => a + b;
export default function create() {}
// app.js
import create, { add } from './math.js';
//正确示例
export const VERSION = '1.0.0';
//错误示例
if (Math.random() > 0.5) {
import x from './a.js'; // 非法:import 须在顶层(动态 import 除外)
}
啥时候用
- 应用拆分 — 按功能域分文件。
- 库发布 — 明确导出面。
3 对象字面量增强
定义:变量名和属性名一样时可以只写一遍;属性名想算出来就用 [表达式]。{ ...a, ...b } 是浅拷贝合并,嵌套对象还是同一块肉,别当深拷贝。
// 语法要点:{ x, [key]: 1, method() {} }
const type = 'SUCCESS';
const action = { type, payload: {} };
//正确示例
const handlers = {
click() {},
keydown() {},
};
//错误示例
const bad = {
function() {}, // 非法:不能这样写方法简写
};
啥时候用
- Redux action —
type+payload模式。- 事件表 / 配置表 — 方法简写。
五、安全访问与运算符扩展
本章:少写一堆
&&、默认值怎么给才不把0弄没、catch 怎么写干净。
1 可选链 ?. 与空值合并 ??
定义:
?.:链式取属性时,中间一旦是null或undefined,后面就不继续取了,整个表达式变成undefined,少写一长串&&。??:只有左边是null或undefined才用右边的值;不会把0、''、false当成「空」给扔掉,这点跟||不一样。
// 语法要点:obj?.prop?.[key]?.(); const port = cfg.port ?? 3000;
const name = user?.profile?.displayName;
const n = count ?? 0;
//正确示例
const title = data?.meta?.title ?? '未命名';
//错误示例
const x = a?.b.c; // a 为 null 时 a?.b 为 undefined,再访问 .c 仍抛错
// 应写:a?.b?.c
啥时候用
- 深嵌套 API — 减少层层
&&。- 配置默认值 —
??避免吃掉合法0。
2 逻辑赋值 &&=、||=、??=
定义:??=、||=、&&= 这种,就是「条件对了才赋值」,少写重复的一行 obj.x = obj.x || 1。计数器可能是 0 时,别用 ||= 把 0 改成别的,改用 ??= 更稳。
// 语法要点:obj.x ||= 1; cache[k] ??= init(); flags.debug &&= isDev;
//正确示例
const config = { retries: undefined };
config.retries ??= 3;
//错误示例
let count = 0;
count ||= 1; // 合法 0 被错误改成 1;此处应使用 ??=
啥时候用
- 懒初始化缓存 —
cache[key] ??= expensive()。- 默认配置 — 与
??语义一致时优先??=。
3 可选 catch 与 new.target
定义:
catch { }:反正你也不读error对象,就可以不写catch (e),少一个没用变量。new.target:在函数体里看看这次是不是被new调用的,有人拿它写「忘了 new 也能用」的构造函数。
// 语法要点:try {} catch {} function F() { if (!new.target) return new F(); }
//正确示例
function parseJson(s) {
try {
return JSON.parse(s);
} catch {
return null;
}
}
//错误示例
try {
void 0;
} catch (e) {
void e;
} catch {
// 非法:只能有一个 catch
}
啥时候用
- 降级路径 — 不关心具体
Error类型时。- 可调用构造函数 — 漏写
new时自动补。
六、内置类型、数值与全局对象
本章:内置工具箱里多出来的顺手的家伙,以及「全局对象」在各环境叫啥。
1 Symbol 与 Map / Set
定义:
Symbol:搞一个几乎不会撞名的键,常用来挂「元数据」。Map:键啥类型都行,按插入顺序记着;Set:就是「不重复值的袋子」。
// 语法要点:Symbol('desc') new Map([[k,v]]) new Set([1,1,2])
const s = Symbol('meta');
const m = new Map([
[s, 42],
['k', 1],
]);
const st = new Set([1, 1, 2]);
//正确示例
const keyObj = {};
m.set(keyObj, 'attached');
//错误示例
const wm = new WeakMap();
wm.set('not-an-object', 1); // TypeError:WeakMap 的键必须是对象
啥时候用
- 去重 / 频次 —
Set/Map计数。- 元数据键 —
Symbol避免与字符串键冲突。
2 数组扩展 API
定义:数组上多了一堆「顺手」的方法:类数组转真数组、按条件找第一个、判断包不包含(includes 还能认出 NaN)、扁平化等,少自己手写轮子。
// 语法要点见注释
const arr = [1, 2, 3, NaN];
//正确示例
const doubled = arr.filter((x) => typeof x === 'number' && !Number.isNaN(x)).map((x) => x * 2);
const hasNaN = arr.includes(NaN);
const nodes = Array.from({ length: 2 }, (_, i) => i);
//错误示例
const idx = [1, 2, 3].findIndex((x) => x > 10);
if (idx) {
// 误判:findIndex 找不到返回 -1,为 truthy
}
啥时候用
- 类数组转数组 —
Array.from(nodeList)- 树扁平 —
flat/flatMap
3 字符串扩展 API
定义:字符串两头匹配、左右补字符、重复、matchAll 一把捞所有匹配(正则记得带 g)、replaceAll 批量替换——都是让字符串操作少写点样板代码。
const text = 'id: 12, 34';
//正确示例
for (const m of text.matchAll(/\d+/g)) {
void m.index;
void m[0];
}
//错误示例
[...'abc'.matchAll(/a/)]; // TypeError:matchAll 要求正则带 g 标志
啥时候用
- 定宽展示 —
padStart- 日志解析 —
matchAll惰性迭代
4 数值运算与 BigInt
定义:
**就是乘方,从右往右结合算。Number.isNaN这类带Number.前缀的,不会先把参数偷偷转成数字再判断,比全局那俩靠谱。BigInt是「大整数专用」,字面量带n,别和普通数字直接加减,要转就显式转。
// 语法要点:2 ** 3 ** 2 === 512
//正确示例
if (Number.isInteger(n) && n >= 0) {
void n;
}
const big = 10n + 1n;
//错误示例
const bad = 1n + 1; // TypeError
啥时候用
- 大整数 ID —
BigInt- 严格数值判断 —
Number.isNaN等
5 Object 工具方法
定义:对象和数组来回转、URL 查询串转对象,常用 entries / fromEntries 这对。Object.hasOwn 用来问「这个键是不是对象自己的」,比直接点 hasOwnProperty 省心,尤其对象没有原型的时候。
//正确示例
const search = Object.fromEntries(new URLSearchParams('a=1&b=2'));
Object.hasOwn({ a: 1 }, 'a'); // true
//错误示例
const obj = Object.create(null);
// 若后续代码写 obj.hasOwnProperty('x') 可能直接 TypeError
// 应使用:Object.hasOwn(obj, 'x')
啥时候用
- 查询串 → 对象 —
fromEntries+URLSearchParams- 安全
hasOwn— 替代obj.hasOwnProperty
6 WeakMap 与 WeakSet
定义:WeakMap / WeakSet 里挂的东西不会把对象「拽死」在内存里——没人引用那个对象了,条目自己就没了。键必须是对象,也不能遍历、没有 size。
//正确示例
const priv = new WeakMap();
class User {
constructor(id) {
priv.set(this, { id });
}
getId() {
return priv.get(this).id;
}
}
//错误示例
const wm = new WeakMap();
wm.set('k', 1); // TypeError:原始值不能作为 WeakMap 键
啥时候用
- 实例元数据 — 不延长实例生命周期。
- DOM 节点挂数据 — 节点移除后易回收。
7 globalThis
定义:不管你跑在浏览器、Worker 还是 Node,想指「最外面那个全局」就用 globalThis,少写一长串环境判断。
//正确示例
const root = globalThis;
root.__APP_VERSION__ = '1.0.0';
//错误示例
globalThis.hugeCache = new Array(1e7); // 全局污染,反模式
啥时候用
- 跨环境库 — 注册 polyfill。
- 测试 — mock 全局。
七、元编程、模块化进阶与边角特性
本章:偏进阶,日常可能用得少,读一遍知道有这回事就行。
1 生成器 function* 与 yield
定义:function* 这种函数可以「走一步停一下」,外面用 next() 往里推;适合惰性算一串东西。现在业务里更多用 async/await,生成器当背景知识就行。
//正确示例
function* idRange(start, end) {
for (let i = start; i <= end; i++) yield i;
}
const seq = [...idRange(1, 3)]; // [1,2,3]
//错误示例
const g = (function* () {
yield 1;
})();
g.next();
g.next(); // done 后仍 next,易忽略状态机边界
啥时候用
- 惰性序列 — 避免一次性分配巨型数组。
- 协同流程 — 旧式 async 方案(现多被 async/await 外层替代)。
2 迭代器协议与 Symbol.iterator
定义:想让 for...of 能遍历你自定义的东西,就给对象挂上 Symbol.iterator,返回一个带 next() 的小对象;next() 每次吐出 { value, done }。
//正确示例
class Queue {
#arr = [];
push(v) {
this.#arr.push(v);
}
*[Symbol.iterator]() {
while (this.#arr.length) yield this.#arr.shift();
}
}
//错误示例
const bad = {
[Symbol.iterator]() {
return {}; // 缺少 next,for...of 时报错
},
};
啥时候用
- 自定义集合 — 对外暴露统一
for...of。
3 Proxy 与 Reflect
定义:Proxy 像给对象包一层「拦截器」,读写都能管;Reflect 里是一堆「默认该怎么干」的函数,在拦截器里常常最后还是要调用它,省得自己模拟错语义。
//正确示例
const model = new Proxy(
{ count: 0 },
{
set(t, k, v) {
if (k === 'count' && typeof v !== 'number') return false;
return Reflect.set(t, k, v);
},
}
);
//错误示例
new Proxy(null, {}); // TypeError:target 须为 object
啥时候用
- 响应式底层 — Vue 3 等(概念层了解即可)。
- 校验 / 日志 — 中间层拦截。
4 动态 import()、import.meta 与顶层 await
定义:
import():跑起来再拉别的文件,适合按路由、按条件拆包。import.meta:告诉你当前模块在哪等信息(比如import.meta.url)。- 顶层
await:在模块最外层直接await,整个模块会等它完事再往下走(需要 ESM 环境支持)。
//正确示例
const locale = await import(`./locales/${lang}.js`);
console.log(import.meta.url);
//错误示例
import('./maybe-missing.js'); // 未 await / 未 catch,拒绝未处理
啥时候用
- 路由懒加载 —
import()+ 代码分割。- 构建脚本 —
import.meta.url定位当前文件。
5 Promise.allSettled 与 Promise.any
定义:
allSettled:几个 Promise 不管成功失败,每个结果都要,适合做报表、做统计。any:几个里只要有一个成就成;全挂了才报错;传空数组也会挂。
//正确示例
const settled = await Promise.allSettled([Promise.resolve(1), Promise.reject(new Error('x'))]);
const first = await Promise.any([Promise.reject(1), Promise.resolve(2)]);
//错误示例
await Promise.any([]); // AggregateError:Empty iterable
啥时候用
- 批量任务报表 —
allSettled- 多镜像竞速 —
any
6 正则增强:u、y、s、命名分组
定义:正则加 u 对 Unicode 更友好;y 从指定位置「粘着」匹配;s 让点号也能匹配换行;命名分组就是给捕获结果起个名字,后面好读。
//正确示例
const re = /(?<y>\d{4})-(?<m>\d{2})/u;
const m = re.exec('2026-05-12');
void m.groups.y;
//错误示例
/😀{2}/u.test('😀😀'); // 仍要注意码点与量词语义;复杂表情序列建议专用库
啥时候用
- 中文等脚本检测 —
\p{Script=Han}+u- 结构化解析 — 命名分组
7 类字段、私有成员 # 与静态块 static {}
定义:类里面直接写字段,相当于每个实例一份;# 开头的是运行时真看不见外面的私有;static {} 块在类加载时跑一次,用来写静态初始化里一堆啰嗦逻辑。
//正确示例
class Counter {
#n = 0;
inc() {
this.#n++;
}
get value() {
return this.#n;
}
}
//错误示例
class D {
#x;
}
new D().#x; // SyntaxError:类外不可访问私有名
啥时候用
- SDK 内部不变量 —
#隐藏实现细节。
8 WeakRef 与 FinalizationRegistry(提要)
定义:WeakRef 是「轻轻挂着」对象,deref() 有时已经拿不到东西了。FinalizationRegistry 是对象被垃圾回收后可能会叫你一声——时间点不靠谱,关文件、关句柄这种大事别指望它,该 dispose 还是 dispose。
//正确示例(仅示意,业务慎用)
const ref = new WeakRef({});
const v = ref.deref();
//错误示例
// 把「释放文件句柄」等关键清理完全依赖 FinalizationRegistry —— 不可靠
啥时候用
- WASM / 原生句柄 — 提示性清理,配合显式 API。
八、速查总表与结语
本章:一张表扫一遍,最后收个尾。
1 特性速查表
| 语法点 | 人话:干啥用 | 心里有个数 |
|---|---|---|
let / const |
替代 var |
可忽略 |
| 箭头函数 | 短回调、词法 this | 栈追踪略逊具名函数 |
| 模板字符串 | 插值、多行 | 可忽略 |
| 解构 | 参数与返回值 | 过深影响可读 |
| 展开 / 剩余 | 拷贝、合并、变参 | 大对象分配 |
| 默认参数 | API 默认值 | 避免重计算默认值 |
Promise |
组合异步 | 控制并发 |
async / await |
业务编排 | 避免无谓串行 await |
class |
模型、SDK | 与函数式混用需规范 |
| ESM | 工程模块化 | 摇树、拆包 |
?. / ?? |
安全访问与默认 | 短路要写完整 |
for...of |
迭代值 | 热路径可对比 for 索引 |
| 生成器 | 惰性序列 | 调度成本 |
| 迭代器协议 | 自定义遍历 | 间接一层 |
Proxy / Reflect |
拦截、响应式 | 热路径慎用 |
| 数组新方法 | 数据处理 | flat 深度 |
| 字符串新方法 | 解析、格式化 | 大文本迭代策略 |
数值 / BigInt |
精度整数 | BigInt 较慢 |
Object.hasOwn 等 |
序列化、检测 | 大对象 entries |
WeakMap / WeakSet |
元数据 | 读取略逊于 Map |
globalThis |
跨环境全局 | 可忽略 |
import() / import.meta / 顶层 await |
懒加载 | 首包与运行时权衡 |
| 逻辑赋值 | 默认与缓存 | 区分 || 与 ?? |
any / allSettled |
容错与竞速 | 注意空数组边界 |
正则 u / 命名组 |
i18n、解析 | Unicode 路径略慢 |
类字段 / # / static {} |
私有与静态初始化 | 依引擎演进 |
可选 catch / new.target |
降级与构造约束 | 可忽略 |
WeakRef / FinalizationRegistry |
底层资源提示 | 慎用 |
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)