6.13 新语法特性:可选链、空值合并、逻辑赋值、Pipeline Operator、Decorators

可选链、空值合并、逻辑赋值、Pipeline Operator、Decorators

可选链空值合并Decorators新语法

原理

JavaScript 语法持续演进,TC39 每年引入的新特性旨在提升代码安全性、可读性和表达能力。本节聚焦近年来对工程实践影响最深远的语法特性。

可选链(Optional Chaining,ES2020)

可选链操作符 ?. 允许安全地访问嵌套属性,当引用为 nullundefined 时短路返回 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)

空值合并操作符 ?? 仅在左侧为 nullundefined 时返回右侧值,与 || 不同,它不会将 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/fpramda 等函数式工具库,或简单的中间变量。

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;
  }
}

装饰器函数接收被装饰的值和上下文对象(包含 kindnameaccessstaticprivate 等元数据),可返回新的值替换原值。

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 总是赋值 | 根据业务意图选择正确的赋值方式 |

测验

关联章节网络

当前章节
关联章节
交叉引用
后续延伸