ES2020核心特性实战指南:可选链、空值合并与BigInt落地

1. 项目概述:ES2020不是“又一个版本”,而是你日常代码里悄悄变轻的那几行

我第一次在生产环境里用上?.操作符,不是因为看了什么技术大会PPT,而是某天凌晨三点改一个埋点上报逻辑——后端接口字段结构突然嵌套深了一层,前端没做兜底,整个用户行为数据链路断了两小时。回滚代码、加判断、再上线,折腾完天都亮了。第二天晨会,我顺手把所有类似位置的obj && obj.user && obj.user.profile && obj.user.profile.name全替成了obj?.user?.profile?.name,测试通过,打包体积没变,但代码清爽得像刚洗过的玻璃。这就是 ES2020 给我的第一课:它不炫技,不造新范式,只默默帮你把那些写过一百遍、改过一千次、review时总被提醒“这里要加空值判断”的防御性胶水代码,压成一行可读、可维护、且浏览器原生支持的语法糖。

ES2020(正式名称 ECMAScript 2020)是 JavaScript 语言标准在2020年6月正式发布的第11版。它不像 ES6 那样带来模块化、箭头函数这种结构性变革,也不像 ES2015 那样彻底重写开发体验;它的定位非常务实——解决开发者日复一日在真实项目中反复踩坑的“小痛点”。空值访问、大数计算、动态导入、全局错误捕获……这些不是实验室里的玩具特性,而是你在写表单校验、处理金融金额、做微前端路由、排查线上白屏时,真正需要伸手就能用的工具。它面向的不是“想学新语法”的人,而是“今天就要修这个bug”的人。关键词里提到的Towards AI — Multidisciplinary Science Journal这类平台,恰恰说明 ES2020 的价值已溢出纯前端圈层——当AI工程团队用 JavaScript 写数据预处理脚本、用 WebAssembly 跑模型推理时,BigIntPromise.allSettled就不再是语法糖,而是精度与可靠性的基础设施。这篇文章不讲标准文档里的定义,只讲我在电商中台、SaaS后台、IoT设备管理平台三个不同场景下,如何把 ES2020 的7个核心特性,变成每天节省15分钟、减少3个潜在bug、让Code Review通过率提升20%的实操武器。

2. 核心特性深度拆解:为什么这7个特性值得你立刻停下手头工作去试

ES2020 官方共纳入9项提案,但其中两项(import.metaurl属性、String.prototype.matchAll)属于已有特性的补全或增强,实际对日常开发影响有限。真正能改变你编码肌肉记忆的,是以下7个特性。它们不是孤立存在的,而是一套协同工作的“防御性编程工具包”——从数据访问、数值计算、异步控制,到错误处理、模块加载,覆盖了现代 JavaScript 应用最常出问题的五个关键环节。我按实际使用频率和冲击力排序,并解释每个选择背后的工程权衡。

2.1 可选链操作符(Optional Chaining):终结“undefined is not an object”

?.操作符解决的是 JavaScript 最古老、最顽固的痛点:深层属性访问时的空值崩溃。传统写法如data.user.profile.avatar.url,只要中间任意一环是nullundefined,就会抛出TypeError。我们被迫写大量防御性代码:

// 旧写法:冗长、易错、可读性差 const avatarUrl = data && data.user && data.user.profile && data.user.profile.avatar && data.user.profile.avatar.url; // 或者用 try/catch(更糟,掩盖真实错误) let avatarUrl; try { avatarUrl = data.user.profile.avatar.url; } catch (e) { avatarUrl = ''; }

?.的本质是短路求值的语法糖,但它比&&更精准:&&在遇到 falsy 值(如0,'',false)时也会短路,而?.只在nullundefined时才停止访问。这意味着obj?.countcount0时仍会返回0,而不是undefined——这符合绝大多数业务逻辑对“零值有效”的预期。

提示:?.不仅用于属性访问,还支持方法调用(obj?.method?.())和数组索引(arr?.[0])。但要注意,?.不能用于赋值左侧(obj?.prop = value会报错),也不能与delete操作符连用(delete obj?.prop无效)。

我在一个实时库存系统中应用它:后端返回的 SKU 数据结构随促销活动动态变化,sku.inventory?.availableStock成为标准写法。上线后,因字段缺失导致的Cannot read property 'availableStock' of undefined错误下降了92%。这不是靠加更多 if 判断实现的,而是靠语言原生能力消除了错误发生的土壤。

2.2 空值合并操作符(Nullish Coalescing):告别||的隐式类型转换陷阱

??操作符常与?.搭配出现,但它解决的是另一个维度的问题:提供默认值。很多人习惯用||

// 危险!0, '', false 都会被当作“空”而取默认值 const count = response.data?.count || 0; // 当 count 是 0 时,结果却是 0?不,是 0 || 0 = 0,看似正确,但逻辑脆弱 const name = user?.name || 'Anonymous'; // 当 name 是 '' 时,得到 'Anonymous',但业务上空字符串可能就是有效状态

||的问题是它基于falsy 值判断,而 JavaScript 中有7个 falsy 值(false,0,-0,0n,'',null,undefined,NaN)。??则严格得多:只在左侧操作数为nullundefined时,才返回右侧操作数。其他所有值,包括0,'',false,都被视为有效值而直接返回。

// 安全!语义清晰:只有真·空值才用默认值 const count = response.data?.count ?? 0; // count 是 0 → 返回 0;count 是 undefined → 返回 0 const name = user?.name ?? 'Anonymous'; // name 是 '' → 返回 '';name 是 null → 返回 'Anonymous'

我在一个配置中心前端中强制推行??。后端返回的config.timeout字段,0表示“禁用超时”,undefined表示“使用系统默认值”。用||会导致0被错误地替换为默认值,引发严重超时问题。切换到??后,配置逻辑的语义完全对齐了业务需求。

2.3 BigInt:JavaScript 终于能精确表示 2^53 以上的整数了

JavaScript 的Number类型基于 IEEE 754 双精度浮点数,安全整数范围是-(2^53 - 1)2^53 - 1(即±9007199254740991)。超过此范围,整数精度丢失:

console.log(9007199254740991 + 1); // 9007199254740992 ✅ console.log(9007199254740991 + 2); // 9007199254740992 ❌ 丢失了 +2 console.log(123456789012345678901234567890n + 1n); // 123456789012345678901234567891n ✅

BigInt通过在数字后加n创建(如123n,0xffn),它是一种全新的原始类型,支持所有数学运算符(+,-,*,/,%,**),但不支持与Number类型混合运算10n + 5会报错),也不能用于Math对象方法Math.pow(10n, 2)无效)。

注意:BigInt不能使用===Number比较(10n === 10false),必须用==10n == 10true)或先转换类型(Number(10n) === 10)。但在比较大小时,><等关系运算符可以跨类型使用(10n > 5true)。

我在一个区块链浏览器项目中重度依赖BigInt。以太坊的区块高度、Gas 费用、Token 余额动辄是几十位的整数。过去用Number处理,balance.toString()会输出科学计数法(1.23e+25),UI 上显示为12300000000000000000000000,但最后几位其实是0——这是精度丢失的视觉表现。改用BigInt后,余额显示、转账计算、Gas 估算全部回归精确。更重要的是,BigInt支持toString(16)直接转十六进制,与智能合约 ABI 编码无缝对接。

2.4 Promise.allSettled:异步世界的“尽最大努力”策略

Promise.all是经典工具,但它有一个致命缺陷:只要有一个 Promise reject,整个all就立即 reject,其他仍在运行的 Promise 结果全部丢失。这在需要“收集所有请求结果,无论成功失败”的场景下极其不利,比如批量提交订单、并行获取多个微服务数据、上报多条日志。

Promise.allSettled彻底改变了这一点。它返回一个 Promise,该 Promise总是 fulfilled,其结果是一个数组,每个元素都是一个对象,包含status'fulfilled''rejected')以及对应的valuereason

const promises = [ fetch('/api/user'), fetch('/api/orders'), fetch('/api/settings') ]; // Promise.all:一个失败,全盘皆输,无法知道其他两个是否成功 Promise.all(promises) .then(results => console.log('All succeeded:', results)) .catch(error => console.log('One failed, all lost:', error)); // Promise.allSettled:稳如老狗,结果尽在掌握 Promise.allSettled(promises) .then(results => { const successful = results.filter(r => r.status === 'fulfilled').map(r => r.value); const failed = results.filter(r => r.status === 'rejected').map(r => r.reason); console.log('Successful:', successful); console.log('Failed:', failed); });

我在一个 SaaS 平台的“一键同步”功能中使用它。该功能需同时向 CRM、ERP、BI 三个系统推送客户数据。allSettled让我能清晰告诉用户:“CRM 同步成功,ERP 因网络超时失败(错误码503),BI 因权限不足失败(错误码403)”,而不是简单粗暴地提示“同步失败,请重试”。这极大提升了运维效率和用户体验。

2.5 globalThis:统一的全局对象访问入口

JavaScript 在不同宿主环境中,全局对象名称不同:浏览器是window,Node.js 是global,Web Worker 是self,甚至某些嵌入式环境是this。这导致跨环境代码(如通用工具库、Polyfill)必须写一堆判断:

// 旧写法:丑陋且易漏 const getGlobal = () => { if (typeof window !== 'undefined') return window; if (typeof global !== 'undefined') return global; if (typeof self !== 'undefined') return self; throw new Error('Unable to locate global object'); }; const globalObj = getGlobal();

globalThis是一个标准化的、在任何 JavaScript 环境中都指向当前全局对象的属性。它不是一个变量,而是一个只读属性,存在于全局作用域。

// 新写法:干净、统一、未来-proof console.log(globalThis); // 浏览器中是 window,Node 中是 global,Worker 中是 self globalThis.myLib = { version: '1.0' }; // 在任何环境都能挂载到全局

我在开发一个开源的 UI 组件库时,用globalThis替换了所有window.xxx的引用。这使得组件库不仅能跑在浏览器,还能无缝集成到 Electron、React Native WebView、甚至 Deno 环境中。globalThis的价值不在于它多强大,而在于它终结了“环境适配”的琐碎劳动。

2.6 动态 import():按需加载的终极形态

import()函数(注意:是函数调用,不是声明)允许你在运行时、条件分支中、甚至循环内部,动态地导入模块。它返回一个 Promise,解决了静态import无法满足的三大场景:条件加载(如根据用户权限加载管理后台)、路由懒加载(React Router/Vue Router 的基石)、以及减小首屏体积(将非关键代码分割出去)。

// 静态 import:编译时确定,无法条件化 // import { heavyModule } from './heavy.js'; // 动态 import:运行时决定,灵活无比 if (user.hasAdminRole()) { const { AdminPanel } = await import('./admin-panel.js'); render(AdminPanel); } // 或者在事件处理器中 button.addEventListener('click', async () => { const { Chart } = await import('./charting-library.js'); initChart(Chart); });

import()的核心优势在于与构建工具(Webpack、Vite、Rollup)深度集成。当你写import('./module.js'),构建工具会自动将module.js及其依赖打包成一个独立的 chunk 文件(如chunk.abc123.js),并在运行时按需下载。这比手动fetch+eval安全无数倍,也比require.ensure(Webpack 2)等旧方案更标准、更易用。

我在一个大型企业级后台中,将所有报表模块、导出功能、第三方 SDK(如地图、支付)全部改为动态import()。首屏 JS 包体积从 2.1MB 降至 850KB,Lighthouse 性能评分从 42 提升至 89。更重要的是,用户点击“查看销售报表”时,才加载sales-report.js,而不是在首页就下载所有报表代码。

2.7 String.prototype.matchAll:正则匹配的“全量收割机”

String.prototype.match()方法在全局正则(/gflag)下,只返回匹配的字符串数组,丢失了捕获组(capturing groups)和索引信息matchAll()则返回一个迭代器,每次迭代产生一个RegExpExecArray,其中包含完整的匹配信息:0是完整匹配,1..n是捕获组,还有index(匹配起始索引)和input(原字符串)。

const text = 'Contact us at support@example.com or sales@example.com'; const regex = /(\w+)@(\w+\.\w+)/g; // match():只给字符串,丢了邮箱用户名和域名的分组 console.log(text.match(regex)); // ['support@example.com', 'sales@example.com'] // matchAll():给你一切,结构化、可编程 const matches = [...text.matchAll(regex)]; matches.forEach(match => { console.log(`Full: ${match[0]}, User: ${match[1]}, Domain: ${match[2]}, Index: ${match.index}`); }); // Full: support@example.com, User: support, Domain: example.com, Index: 14 // Full: sales@example.com, User: sales, Domain: example.com, Index: 40

matchAll()的价值在于它让正则从“文本提取工具”升级为“结构化解析引擎”。我在一个日志分析平台中用它解析 Nginx access log。每条日志格式固定,但字段间用空格分隔,且部分字段含空格(如User-Agent)。用matchAll配合精心设计的正则,能一次性提取出ip,timestamp,method,path,status,userAgent等12个字段,准确率100%,性能比split(' ')+ 复杂逻辑判断快3倍。

3. 实操落地指南:从 Babel 配置到生产环境避坑全记录

光知道特性不够,真正落地时,你会遇到构建工具兼容性、TypeScript 类型、浏览器支持、以及那些文档里不会写的“灰色地带”。下面是我踩过坑、验证过的完整实操路径,覆盖开发、测试、上线全流程。

3.1 构建工具配置:Babel 7.12+ 是你的底线

ES2020 特性并非所有浏览器都原生支持(尤其BigInt?./??在旧版 Safari/IE 中缺失),因此生产环境几乎都需要 Babel 转译。Babel 7.12 是第一个完整支持 ES2020 的版本,低于此版本会有遗漏。

核心插件配置:

  • @babel/plugin-proposal-nullish-coalescing-operator:对应??
  • @babel/plugin-proposal-optional-chaining:对应?.
  • @babel/plugin-proposal-bigint:对应BigInt
  • @babel/plugin-proposal-promise-all-settled:对应Promise.allSettled
  • @babel/plugin-proposal-global-this:对应globalThis
  • @babel/plugin-syntax-dynamic-import:对应import()(注意:这是 syntax 插件,不转译,仅启用解析)
  • @babel/plugin-proposal-string-match-all:对应matchAll

.babelrc示例:

{ "presets": [ ["@babel/preset-env", { "targets": { "browsers": ["> 0.5%", "last 2 versions", "Firefox ESR", "not dead"] }, "useBuiltIns": "usage", "corejs": 3 }] ], "plugins": [ "@babel/plugin-proposal-nullish-coalescing-operator", "@babel/plugin-proposal-optional-chaining", "@babel/plugin-proposal-bigint", "@babel/plugin-proposal-promise-all-settled", "@babel/plugin-proposal-global-this", "@babel/plugin-syntax-dynamic-import", "@babel/plugin-proposal-string-match-all" ] }

关键细节:@babel/preset-envuseBuiltIns: 'usage'会根据你代码中实际使用的特性(如Promise.allSettled),自动引入core-js的 polyfill,避免全量引入。corejs: 3是必须的,因为core-js@2不支持Promise.allSettledmatchAll

3.2 TypeScript 支持:类型安全的双保险

TypeScript 3.7+ 原生支持?.??,并提供了强大的类型推导:

  • obj?.prop的类型是T | undefined(如果prop类型是T
  • left ?? right的类型是leftType | rightType,且会正确排除null/undefined

对于BigInt,TS 3.2+ 引入了bigint类型,但需注意:

  • 123n的字面量类型是123n(字面量类型),不是泛型bigint
  • 要声明变量为bigint,需显式标注:const num: bigint = 123n;
  • BigInt()构造函数返回bigint,但Number()转换bigint会报错(BigInt不能隐式转Number

TS 配置 (tsconfig.json) 关键项:

{ "compilerOptions": { "target": "ES2019", // 或更高,但 ES2020 不是必须的,Babel 会处理 "lib": ["ES2020", "DOM"], // 显式加入 ES2020,确保类型定义可用 "strict": true, "noImplicitAny": true, "esModuleInterop": true } }

3.3 浏览器兼容性与 Polyfill 策略:别让BigInt在 Safari 13 上崩溃

特性ChromeFirefoxSafariEdgeNode.js
?./??80+74+13.1+88+14.0+
BigInt67+68+14+79+10.4+
Promise.allSettled76+71+13+79+12.9+
globalThis71+65+12.1+76+12.0+
import()63+67+11.1+76+12.0+
matchAll73+67+13+79+12.0+

Polyfill 策略:

  • ?./??:Babel 转译即可,无需 runtime polyfill。
  • BigIntcore-js@3提供了BigIntpolyfill,但性能极差(基于字符串模拟),仅用于开发调试。生产环境强烈建议:检测浏览器支持,不支持则降级或提示。检测代码:
    if (typeof BigInt === 'undefined') { alert('您的浏览器不支持大整数运算,请升级到 Safari 14+/Chrome 67+'); }
  • Promise.allSettledcore-js@3polyfill 成熟稳定,可放心使用。
  • globalThiscore-js@3polyfill 安全可靠。
  • import():无需 polyfill,现代构建工具已完美支持代码分割。

3.4 生产环境避坑清单:那些让你加班到凌晨的“小细节”

  1. ?.delete的组合是语法错误
    delete obj?.prop会报SyntaxError。正确做法是先判断再删除:

    if (obj?.prop !== undefined) { delete obj.prop; }
  2. BigInt不能与Number混合运算,但==比较是特例
    10n == 10返回true,但10n === 10返回false。在switch语句中,BigInt会触发default分支(因为switch使用===)。务必统一类型。

  3. import()的路径必须是字符串字面量或模板字符串,不能是变量拼接
    import('./' + moduleName + '.js')会报错,因为构建工具无法静态分析。必须写成import(./${moduleName}.js)(模板字符串),或使用webpackChunkName注释:

    const module = await import( /* webpackChunkName: "dynamic-[request]" */ `./${moduleName}.js` );
  4. matchAll()返回的是迭代器,不是数组
    不能直接.map().forEach()。必须先转为数组:[...str.matchAll(regex)],或用for...of循环:

    for (const match of str.matchAll(regex)) { console.log(match); }
  5. Promise.allSettledreason是原始错误对象,不是字符串
    results[0].reason.message可能是undefined(如果reason是一个普通对象)。安全访问方式:

    const errorMsg = results[0].reason instanceof Error ? results[0].reason.message : String(results[0].reason);

4. 真实项目问题排查实录:从报错日志到根因修复的全过程

理论再扎实,不如一次真实的线上故障排查来得深刻。以下是我在三个不同项目中,利用 ES2020 特性快速定位并解决棘手问题的真实案例,附带完整的排查思路和修复代码。

4.1 案例一:电商后台“库存同步失败”告警——?.揭开数据源污染真相

现象:
凌晨2点,监控系统报警:InventorySyncJob失败率突增至40%,错误日志为TypeError: Cannot read property 'stock' of undefined。该 Job 负责从 ERP 系统拉取 SKU 库存,更新到本地缓存。

初步排查:

  • 查看最近部署记录:无相关代码变更。
  • 检查 ERP 接口文档:/api/sku/inventory返回结构未变。
  • 本地模拟调用:返回数据正常,response.data.skuList[0].stock存在。

深入分析:
日志中失败的 SKU ID 集中在几个特定品类。我登录 ERP 后台,发现这些品类的 SKU 正在进行“临时下架”操作,其库存数据被 ERP 临时设为null。但我们的代码是这样写的:

// 旧代码:假设 data.skuList[i] 永远存在 const stock = response.data.skuList[i].stock;

response.data.skuList[i]null时,null.stock报错。

ES2020 解法:
将访问链全面升级为可选链,并结合空值合并提供默认值:

// 修复后:健壮、清晰、一行解决 const stock = response.data?.skuList?.[i]?.stock ?? 0; // 如果 skuList 是 null/undefined,或 i 超出范围,或 stock 是 null/undefined,都返回 0

效果:

  • 修复后,Job 失败率归零。
  • 更重要的是,?.让我们意识到:数据源本身是不可信的。这个修复不仅解决了当前 bug,还推动团队建立了“所有外部 API 响应字段访问必须用?.”的代码规范。

4.2 案例二:SaaS 平台“报表导出空白”——Promise.allSettled暴露隐藏的权限错误

现象:
用户反馈,点击“导出年度报表”按钮后,页面长时间无响应,最终弹出“导出失败”。前端 Network 面板显示,/api/reports/annual请求返回 200,但响应体为空 JSON{}

排查过程:

  • 后端日志:该接口调用三个子服务(analytics-service,billing-service,user-service),其中billing-service返回了 403 Forbidden。
  • 前端代码:使用Promise.all并行调用三个服务:
    const [analytics, billing, users] = await Promise.all([ fetch('/api/analytics'), fetch('/api/billing'), // 这里 403,导致 all reject fetch('/api/users') ]);
    由于Promise.all的“全或无”特性,billing的 403 导致整个Promise.allreject,analyticsusers的成功响应被丢弃,前端无法组装报表数据,只能报错。

ES2020 解法:
重构为Promise.allSettled,并添加精细化错误处理:

const results = await Promise.allSettled([ fetch('/api/analytics'), fetch('/api/billing'), fetch('/api/users') ]); const [analyticsRes, billingRes, usersRes] = results; // 单独处理每个结果 let analyticsData = null; if (analyticsRes.status === 'fulfilled') { analyticsData = await analyticsRes.value.json(); } else { console.error('Analytics service failed:', analyticsRes.reason); showNotification('分析数据加载失败,部分报表可能不完整'); } let billingData = null; if (billingRes.status === 'fulfilled') { billingData = await billingRes.value.json(); } else if (billingRes.reason.status === 403) { // 特殊处理:权限不足,显示友好提示 showNotification('您没有查看账单数据的权限,报表中将不包含财务信息'); } else { console.error('Billing service failed:', billingRes.reason); } // ... 同理处理 usersData // 最终,即使 billing 失败,analytics 和 users 数据仍可用,报表至少部分生成

效果:

  • 用户看到的是“账单数据因权限不足未加载,其余数据已导出”,而非笼统的“导出失败”。
  • 运维同学能立即从日志中看到是billing-service的 403,而非一个模糊的Promise.allreject。

4.3 案例三:IoT 设备管理平台“设备状态异常”——BigInt精度丢失引发的连锁反应

现象:
某型号工业传感器上报的uptime(运行毫秒数)在设备运行约 25 天后,前端显示的“在线时长”开始跳变,有时显示为负数,有时显示为巨大正数(如9223372036854776000)。

根因分析:

  • 传感器固件使用 64 位整数存储uptime,通过 MQTT 发送到后端。
  • 后端 Node.js 服务(v12.16)接收后,用Number(payload.uptime)转为 JS Number。
  • uptime超过2^53(约 25.3 天),Number精度丢失,90071992547409919007199254740992Number中表示为同一个值。
  • 前端计算currentUptime - lastUptime时,由于精度丢失,差值计算错误,导致“在线时长”计算失真。

ES2020 解法:
前后端全线升级为BigInt

  • 后端(Node.js):接收 MQTT payload 时,用BigInt(payload.uptime)替代Number()
  • 前端:API 响应中,uptime字段改为字符串(如"12345678901234567890"),前端用BigInt(res.uptime)解析。
  • 计算逻辑:所有时间差、比较、格式化均在BigInt上进行:
    const currentUptime = BigInt(res.current.uptime); const lastUptime = BigInt(res.last.uptime); const diffMs = currentUptime - lastUptime; // 精确的 BigInt 差值 const diffSecs = diffMs / 1000n; // BigInt 除法,结果仍是 BigInt const hours = diffSecs / 3600n;

效果:

  • 设备运行 100 天后,前端显示的“在线时长”与设备固件内置时钟完全一致,误差为 0。
  • BigInttoString()输出是精确的十进制字符串,UI 显示不再有科学计数法干扰。

5. 进阶技巧与未来演进:让 ES2020 成为你技术雷达的基准线

掌握 ES2020 不是终点,而是你构建现代 JavaScript 技术栈的起点。它教会你的,不仅是7个新语法,更是一种“防御性、精确性、渐进式”的工程哲学。以下是我总结的进阶实践和对未来趋势的观察。

5.1 组合技:?.+??+await= 一行健壮的异步数据获取

这三个特性组合起来,能写出极其简洁又健壮的异步数据流:

// 获取用户头像 URL,步骤:API 调用 → 解析 JSON → 取 avatar 字段 → 默认值 const avatarUrl = (await (await fetch('/api/user')).json())?.avatar?.url ?? '/default-avatar.png'; // 拆解理解: // 1. fetch('/api/user') → Promise<Response> // 2. await 第一个 Promise → Response // 3. .json() → Promise<any> // 4. await 第二个 Promise → any (可能是 null/undefined) // 5. ?.avatar?.url → 安全访问,可能为 undefined // 6. ?? '/default-avatar.png' → 提供默认值

这行代码涵盖了网络请求、JSON 解析、空值处理、默认值设置四个环节,且每一环都具备防御性。它比写一个async function+try/catch+if判断的代码更短、更易读、更少出错。

5.2 TypeScript 与BigInt的深度协同:类型即契约

在 TS 中,BigInt的类型安全能发挥更大价值。例如,定义一个精确的“货币”类型:

type CurrencyAmount = { value: bigint; // 精确到最小单位(如分) currency: 'CNY' | 'USD' | 'EUR'; }; // 安全的加法函数 function addAmount