深入浅出 ES6+

一、导读

1 怎么读这篇笔记

定义: 用几句话说明「这东西到底解决啥问题」
代码块 常有 // 语法要点(提要)、//正确示例//错误示例,从上到下对着看
啥时候用 列几条「实际写业务时会在哪碰到」

二、变量绑定与函数写法

本章:变量怎么声明更省心、函数怎么写短、字符串和对象怎么「拆」着用。

1 letconst

定义:以前 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。适合写在 mapsetTimeout 里那种一两句就完事的回调。

// 语法要点:(参数) => 表达式  或  (参数) => { 语句; 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 封装signaltimeout 等可选配置。
  • 工具函数 — 边界值 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 解析

啥时候用

  • 并发 IOPromise.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...offor...in 辨析

定义:for...of 是「挨个拿值」,适合数组、MapSet、字符串。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 actiontype + payload 模式。
  • 事件表 / 配置表 — 方法简写。

五、安全访问与运算符扩展

本章:少写一堆 &&、默认值怎么给才不把 0 弄没、catch 怎么写干净。

1 可选链 ?. 与空值合并 ??

定义:

  • ?.:链式取属性时,中间一旦是 nullundefined,后面就不继续取了,整个表达式变成 undefined,少写一长串 &&
  • ??:只有左边是 nullundefined 才用右边的值;不会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 可选 catchnew.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 SymbolMap / 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

啥时候用

  • 大整数 IDBigInt
  • 严格数值判断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 WeakMapWeakSet

定义: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 ProxyReflect

定义: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.allSettledPromise.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 正则增强:uys、命名分组

定义:正则加 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 WeakRefFinalizationRegistry(提要)

定义: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 底层资源提示 慎用
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐