6.13 新语法特性:可选链、空值合并、逻辑赋值、Pipeline Operator、Decorators
可选链、空值合并、逻辑赋值、Pipeline Operator、Decorators
原理
JavaScript 语法持续演进,TC39 每年引入的新特性旨在提升代码安全性、可读性和表达能力。本节聚焦近年来对工程实践影响最深远的语法特性。
可选链(Optional Chaining,ES2020)
可选链操作符 ?. 允许安全地访问嵌套属性,当引用为 null 或 undefined 时短路返回 undefined,而非抛出 TypeError。
const street = user?.address?.street;
// 等价于:user == null ? undefined : user.address == null ? undefined : user.address.street
可选链支持三种使用形式:
obj?.prop:可选属性访问。obj?.[expr]:可选动态属性访问。func?.(...args):可选函数调用。
可选链不能用于赋值左侧
user?.name = 'Tom' 是语法错误。可选链只能用于读取,不能用于赋值。
空值合并(Nullish Coalescing,ES2020)
空值合并操作符 ?? 仅在左侧为 null 或 undefined 时返回右侧值,与 || 不同,它不会将 0、""、false 视为 falsy 而跳过。
const value = input ?? 'default'; // input 为 null/undefined 时才使用默认值
const count = response.count ?? 10; // count 为 0 时仍使用 0,不会回退到 10
?? 与 || 不能混用而不加括号,因为它们的优先级和结合性可能导致意外行为:
const x = 0 || 1 ?? 2; // SyntaxError
const y = (0 || 1) ?? 2; // OK
逻辑赋值(Logical Assignment,ES2021)
逻辑赋值运算符将逻辑运算与赋值结合:
a ||= b:等价于a || (a = b),仅当a为 falsy 时赋值。a &&= b:等价于a && (a = b),仅当a为 truthy 时赋值。a ??= b:等价于a ?? (a = b),仅当a为 nullish 时赋值。
// 初始化默认值
config.timeout ??= 5000;
// 条件性启用
feature.enabled &&= user.hasPermission;
注意:逻辑赋值的右侧仅在条件满足时求值,具有短路特性。
Pipeline Operator(提案中)
Pipeline Operator |> 允许将表达式的结果作为参数传递给下一个函数,改善函数调用的可读性:
// 当前(Stage 2)提案语法
const result = value
|>> double
|>> add(10)
|>> format;
目前 TC39 对具体语法(Hack-style vs F#-style)仍有争议,尚未进入 Stage 3。生产环境建议使用 lodash/fp、ramda 等函数式工具库,或简单的中间变量。
Decorators(ES2023/ES2024)
Decorators(装饰器)是一种用于修改类和类成员的声明式语法。TC39 的 Decorators 提案经历了多次重大修订,当前 Stage 3 版本(预计 ES2024 纳入)的语法如下:
function logged(value, { kind, name }) {
if (kind === 'method') {
return function(...args) {
console.log(`Calling ${name}`);
return value.apply(this, args);
};
}
}
class Example {
@logged
sum(a, b) {
return a + b;
}
}
装饰器函数接收被装饰的值和上下文对象(包含 kind、name、access、static、private 等元数据),可返回新的值替换原值。
Babel 的 @babel/plugin-proposal-decorators 和 TypeScript 的 experimentalDecorators 实现的是旧版(Legacy)装饰器语法,与最新提案不兼容。新项目应关注标准提案进展。
用法
// 可选链与空值合并的组合
const userCity = user?.address?.city ?? 'Unknown';
// 安全访问深层配置
const dbPort = config?.databases?.primary?.port ?? 5432;
// 逻辑赋值简化事件处理器注册
class Emitter {
listeners ??= new Map();
on(event, handler) {
this.listeners[event] ??= [];
this.listeners[event].push(handler);
}
}
// 可选链调用回调
onSuccess?.(result);
实践
迁移旧代码到现代语法
// 旧代码
const street = user && user.address && user.address.street;
const name = user.name || 'Anonymous';
// 现代代码
const street = user?.address?.street;
const name = user.name ?? 'Anonymous';
TypeScript 中的精确类型收窄
可选链和空值合并配合 TypeScript 的类型守卫可实现更精确的类型推断:
function process(data: { items?: string[] }) {
// data.items 被推断为 string[] | undefined
const first = data.items?.[0];
// first 被推断为 string | undefined
return first ?? 'default';
}
Babel 与浏览器兼容性
ES2020 的可选链和空值合并已被所有现代浏览器支持。若需兼容旧版浏览器,Babel 转译后的代码会引入辅助函数:
// 转译后(简化示意)
var _user$address;
var street = (_user$address = user === null || user === void 0 ? void 0 : user.address) === null || _user$address === void 0 ? void 0 : _user$address.street;
对于大量嵌套可选链的代码,转译后体积膨胀明显,应评估目标环境是否确实需要转译。
陷阱
| 陷阱 | 现象 | 解决方案 |
|------|------|---------|
| ?? 与 || 混用不加括号 | 语法错误或意外优先级 | 混用时始终显式加括号 |
| 可选链用于赋值左侧 | 语法错误 | 使用条件判断包裹赋值逻辑 |
| obj?.prop 整体为 undefined 时的运算 | obj?.prop + 1 得到 NaN | 配合 ?? 提供默认值:(obj?.prop ?? 0) + 1 |
| 装饰器版本不兼容 | Babel Legacy 与 TC39 新提案语法不同 | 明确项目使用的装饰器版本,不混用 |
| ??= 与 = 的语义混淆 | a ??= b 仅在 nullish 时赋值,而 a = b 总是赋值 | 根据业务意图选择正确的赋值方式 |