☰
ECMA-262第六版中文翻译:JavaScript工程师的规范契约与调试指南
2026/10/20 15:12:03 网站建设 项目流程

简介:本资源是ECMAScript 2015(ES6)语言规范的完整中文翻译项目成果,面向前端开发者、JavaScript语言研究者及标准学习者,解决英文原版阅读门槛高、术语理解难、规范细节不易掌握等实际问题。压缩包共18个文件,含4个HTML章节页(用于结构化浏览规范正文)、2个PDF(含主规范文档Ecma-262-6.pdf及说明性材料)、4个PNG与2个SVG图示文件(直观呈现语法结构、执行模型等关键图表)、2个CSS与ICO/JPG资源(保障网页端阅读体验),整体体积6.66MB,轻量易下载。目前已有262人学习下载,体现社区对标准化文档本地化工作的持续关注。读者可直接获取可离线查阅的中文规范全文、配套图解与目录导航体系,尤其适合深入理解let/const、模块、Promise、Proxy等ES6核心机制的设计原理与语义定义,是系统研读语言标准不可多得的参考材料。

1. 这不是“语法糖合集”:ECMA-262 第六版中文翻译是 JS 工程师绕不开的底层契约书

很多人第一次点开ecma262-6-cn仓库时,下意识以为这是个“ES6 新特性速查表”——结果发现满屏是LetDeclaration,AssignmentExpression,Runtime Semantics: Evaluation这类术语,瞬间关掉。但恰恰是这份被多数人跳过的中文翻译,成了我在某跨平台系统重构中定位let块级作用域与var变量提升混用导致内存泄漏的唯一依据。它不教你怎么写箭头函数,而是定义“箭头函数在词法环境中的绑定行为必须满足 Runtime Semantics: ArrowFunctionEvaluation 的第 4 步”。如果你常遇到“这段代码在 Chrome 跑得通、Node.js 报错、TypeScript 类型推导崩了”的玄学问题,根源往往不在运行时差异,而在你和引擎对同一段规范文本的理解偏差。这份翻译覆盖全部 26 章、300+ 小节,从词法文法(Lexical Grammar)到抽象操作(Abstract Operations),从 Promise 的PerformPromiseThen到 Proxy 的[[Get]]内部方法,是 JS 引擎实现者、Babel 插件开发者、以及需要写出零歧义代码的资深前端工程师的底层契约书。它不适合当入门教程,但当你需要确认“for...of循环中break是否会触发IteratorClose”,它就是你的后悔药。


2. 为什么必须用第六版?从const的不可变性陷阱说起

2.1 第六版的核心分水岭:从“语法扩展”到“运行时语义建模”

ES5 规范像一本操作手册:告诉你Object.create()怎么用、JSON.parse()接受什么参数。而 ES6(即 ECMA-262 第六版)首次系统性引入抽象操作(Abstract Operations)和算法步骤(Algorithm Steps)来精确描述行为。以const为例,ES5 只说“声明常量”,但没定义“重新赋值时引擎该抛什么错误、错误对象该有哪些属性、是否影响后续执行”。第六版则在第 13.6.4 节明确定义:

Runtime Semantics: Evaluation
LexicalBinding : const BindingIdentifier Initializer

  1. Let bindingId be StringValue of BindingIdentifier.
  2. Let lhs be ResolveBinding(bindingId, strict: true).
  3. ReturnIfAbrupt(lhs).
  4. Let rhs be the result of evaluating Initializer.
  5. ReturnIfAbrupt(rhs).
  6. Perform ? PutValue(lhs, rhs).

关键在第 6 步的PutValue—— 它指向第 6.2.3.2 节,其中明确要求:若目标引用(Reference)的strict标志为 true 且IsStrictReference(ref)返回 true,则必须抛出TypeError。这意味着const a = {}; a.b = 1;合法(修改对象属性),但a = {};非法(重绑定引用),而这个边界完全由PutValue的算法路径决定。没有第六版,你只能靠试错;有了它,你能直接定位到算法分支,预判所有边缘 case。

2.2 中文翻译的不可替代性:术语一致性决定调试效率

英文原版中binding,reference,environment record,lexical environment等概念高频穿插,且存在细微差别。例如:

  • Binding指“标识符与值的关联关系”(如let x = 1中x与1的绑定);
  • Reference是一个三元组{ base, referencedName, strict },用于PutValue/GetValue操作;
  • Environment Record是存储绑定的实际数据结构(如DeclarativeEnvironmentRecord);
  • Lexical Environment是包含Environment Record和外层引用的容器。

中文翻译将这些统一译为“绑定”“引用记录”“环境记录”“词法环境”,并在附录 A 的术语表中明确定义。我曾因混淆binding和reference,误以为const禁止所有修改,实际它只禁止对Reference的PutValue操作。而中文版第 8.7 节“引用类型”表格清晰列出Reference的三个字段及其含义,比反复查英文 glossary 高效得多。

2.3 如何快速定位你要的章节?按“问题现象”反向查规范

别从目录开始读。工程实践中,我习惯用三步定位法:

  1. 现象归类:遇到await在非 async 函数中报错?归为“语法错误”;Promise.allSettled返回值结构异常?归为“抽象操作返回值”;
  2. 关键词锚定:在 PDF 全局搜索await→ 跳转到第 14.6.1 节 “Async Function Definitions”;
  3. 算法路径追踪:找到AwaitExpression : await Expression的 Runtime Semantics,再顺藤摸瓜到Await抽象操作(第 7.2.3.1 节),最终确认其要求Expression必须返回Thenable对象,否则抛TypeError。

提示:GitHub 仓库的index.html已做全文搜索优化,Ctrl+F 输入IteratorClose或PerformPromiseThen可秒级定位。PDF 版本建议用 Adobe Acrobat 的“高级搜索”,勾选“匹配整个单词”。


3. 实战:用规范验证class继承链的初始化顺序

3.1 问题场景:为什么子类构造函数里super()必须最先调用?

某次重构中,A同学把super()放在this.xxx = yyy之后,Chrome 报ReferenceError: Must call super constructor in derived class before accessing 'this'。他疑惑:“V8 为什么这么严格?Babel 编译后能跑啊。” —— 这正是规范与实现的分界点。我们直接查第六版第 14.5.14 节 “Runtime Semantics: ClassDefinitionEvaluation”。

关键算法步骤:

  1. If ClassHeritage is not present, then
    a. Let protoParent be the value of the [[Prototype]] internal slot of the current Realm’s global object.
  2. Else,
    a. Let heritageResult be the result of evaluating ClassHeritage.
    b. ReturnIfAbrupt(heritageResult).
    c. Let protoParent be ? Get(protoParent, "prototype").
    ...
  3. Let constructorInfo be DefineMethod of Constructor with arguments protoParent, constructorParent, and false.
  4. Let F be constructorInfo.[[Closure]].
  5. Set the [[ConstructorKind]] internal slot of F to "derived".
  6. Return F.

重点在第 21 步:[[ConstructorKind]]被设为"derived"。这触发了第 14.2.14 节 “Runtime Semantics: EvaluateBody” 中对this的检查逻辑:

  1. If thisArg is not undefined, then
    a. If the [[ConstructorKind]] internal slot of F is "derived", then
    i. If the [[ThisBindingStatus]] internal slot of F is not "initialized", throw a ReferenceError exception.

而[[ThisBindingStatus]]仅在super()执行完毕后由Construct抽象操作设为"initialized"(见第 7.3.13 节)。因此,任何在super()前访问this的行为,都会在第 22.a.i 步被规范强制拦截。Babel 编译能跑,是因为它用_this = _possibleConstructorReturn(this, _getPrototypeOf(...))模拟了this初始化,但这是对规范的“兼容性补丁”,而非规范本身允许的行为。

3.2 验证工具:用esprima解析 AST 对照规范文法

光看文字不够直观?我们用代码验证class声明的词法结构是否符合规范。第六版第 14.5 节定义了ClassTail文法:

ClassTail : ClassHeritageopt { ClassBodyopt }

用esprima解析class A extends B { constructor() { super(); } }:

const esprima = require('esprima'); const ast = esprima.parseScript('class A extends B { constructor() { super(); } }', { ecmaVersion: 6, tokens: true }); console.log(JSON.stringify(ast.body[0].type)); // "ClassDeclaration" console.log(ast.body[0].superClass.type); // "Identifier" (对应 ClassHeritage) console.log(ast.body[0].body.body[0].type); // "MethodDefinition" (constructor)

对照规范第 14.5.1 节 “ClassDeclaration : class BindingIdentifier ClassTail”,superClass字段的存在,正是ClassHeritageopt文法项被匹配的证据。而body中的MethodDefinition,则对应ClassBody中的ClassElement(第 14.5.15 节)。这种 AST 层级的映射,让你能把代码结构直接锚定到规范条款,避免“我以为是这样,其实规范写的是那样”的认知偏差。

3.3 常见误区:class不是语法糖,而是全新语义实体

很多教程说“class是function的语法糖”,这是严重误导。第六版明确将class定义为独立的声明类型(Declaration),其[[Construct]]内部方法与普通函数完全不同:

  • 普通函数:[[Construct]]直接创建新对象并绑定this;
  • class构造器:[[Construct]]必须先调用super()获取父类实例,再执行子类constructor体(第 9.2.2 节)。

验证方式:在控制台执行:

class A {} console.log(A.prototype.constructor === A); // true console.log(Object.getPrototypeOf(A) === Function.prototype); // true // 但 A 的 [[Construct]] 行为由 ClassDefinitionEvaluation 生成,而非 FunctionDeclaration

规范第 14.5.14 节的算法明确区分了DefineMethod(用于class方法)和CreateDynamicFunction(用于function表达式),二者生成的闭包具有不同的内部槽位(Internal Slots)。这就是为什么class A {}不能被new A()之外的方式调用——它的[[Call]]内部方法被规范定义为ThrowTypeError(第 9.2.1 节)。


4. 避坑:中文翻译里的五个典型理解偏差与修正方案

4.1 现象:let声明的“暂时性死区(TDZ)”在某些浏览器中表现不一致

原因:中文翻译将Temporal Dead Zone译为“暂时性死区”,但未强调其本质是运行时语义约束,而非语法错误。规范第 8.1.1.4 节规定:在let绑定初始化完成前,任何对该绑定的GetValue操作都必须抛ReferenceError。但部分旧版 V8 在typeof x(未声明变量)时返回"undefined",而对 TDZ 中的x却抛错——这并非 bug,而是typeof操作符在第 12.5.8 节被特别豁免:If Type(val) is Reference and IsUnresolvableReference(val) is true, return "undefined"。但 TDZ 中的x是Reference,且IsUnresolvableReference为 false(因为已声明),故不触发豁免。
解决:永远不要依赖typeof检测 TDZ 变量;用try/catch或确保声明在访问前执行。

4.2 现象:Promise.race([])立即 resolve,但Promise.all([])也立即 resolve,为何行为相同?

原因:中文翻译第 25.4.4.3 节 “Promise.race ( iterable )” 的算法第 4 步写:“If iteratorRecord.[[Done]] is true, return promise.” 但未说明iteratorRecord.[[Done]]在空数组时如何为 true。需追溯到第 25.1.1.2 节 “CreateArrayIterator” —— 空数组的迭代器在next()调用时直接返回{value: undefined, done: true}。而Promise.race的算法第 3 步Let iteratorRecord be GetIterator(iterable)创建迭代器后,第 4 步即检查[[Done]]。同理,Promise.all的第 5 步也检查iteratorRecord.[[Done]]。
解决:记住“空可迭代对象的迭代器默认done: true”,这是规范对性能的优化约定,非特例。

4.3 现象:Object.is(-0, +0)返回false,但===返回true,中文翻译未解释底层原理

原因:中文翻译第 7.2.10 节 “Object.is ( value1, value2 )” 的算法第 1 步:“If SameValueNonNumber(value1, value2) is true, return true.” 而SameValueNonNumber在第 7.2.11 节定义为:If Type(x) is Number and Type(y) is Number, then return ! SameValueZero(x, y).关键在SameValueZero(第 7.2.12 节):它要求x和y的符号位(sign bit)必须相同,而-0和+0的 IEEE 754 符号位不同。
解决:Object.is是为Map键比较设计的,-0和+0作为不同键是合理需求;日常比较仍用===。

4.4 现象:for...in遍历顺序在中文翻译中未明确,导致跨浏览器不一致

原因:中文翻译第 13.7.5.15 节 “Runtime Semantics: ForIn/OfBodyEvaluation” 仅规定“枚举对象的可枚举自有属性”,但未定义顺序。规范第 9.1.12 节 “EnumerateObjectProperties” 注明:“The mechanics and order of enumerating the properties is not specified.” 这意味着for...in顺序由引擎实现决定,V8 用插入顺序,Chakra 用定义顺序。
解决:绝不在业务逻辑中依赖for...in顺序;需有序遍历时用Object.keys(obj).sort().forEach()。

4.5 现象:async函数返回的 Promise 状态变化时机难以预测

原因:中文翻译第 14.6.14 节 “Runtime Semantics: AsyncFunctionStart” 的算法第 3 步:“Let result be the result of evaluating AsyncFunctionBody.” 但AsyncFunctionBody的求值过程(第 14.6.15 节)规定:若 body 中无await,则result为Completion{[[Type]]: normal, [[Value]]: undefined, [[Target]]: empty},此时 Promise 立即 resolve;若有await,则进入Await抽象操作,状态变为 pending。
解决:async () => 1返回的 Promise 立即 resolve;async () => { await Promise.resolve(); return 1; }则至少经历一次 microtask。用queueMicrotask(() => console.log('after'))可验证。


5. 进阶技巧:用规范反向生成 TypeScript 类型定义

5.1 为什么Promise.all的返回类型是Promise<T[]>而非Promise<Array<T>>?

TypeScript 的lib.es2015.promise.d.ts中:

all<T>(values: readonly (T | PromiseLike<T>)[]): Promise<T[]>;

这个T[]并非随意设计。我们查第六版第 25.4.4.1 节 “Promise.all ( iterable )” 的算法:

  1. Let values be a new empty List.
  2. Let nextIndex be 0.
  3. Repeat, while nextIndex < len,
    a. Let nextValue be Get(iterable, ToString(nextIndex)).
    b. Append nextValue to values.
    c. Increment nextIndex by 1.
  4. Let promiseResolve be Get(promise, "resolve").
  5. Let result be Call(promiseResolve, promise, «promise»).
  6. Let resultCapability be NewPromiseCapability(result).
  7. Let index be 0.
  8. Let valuesLen be the length of values.
  9. Let remainingElementsCount be valuesLen.
  10. Let resultValues be a new List of length valuesLen.

关键在第 5 步和第 14 步:values是一个List(规范中的抽象数据结构),而resultValues是同样长度的新List。规范虽未定义List的 TypeScript 类型,但第 7.1 节 “List and Record Specifications” 明确List是“有序元素集合”,且所有操作(Append,Get)均按索引访问。这与 JavaScript 数组的length和索引访问语义完全一致。因此,TS 将List<T>映射为T[]是对规范语义的精准还原,而非妥协。

5.2 从Proxy的[[Get]]内部方法推导gettrap 的参数签名

第六版第 9.5.2.1 节 “[[Get]] (P, Receiver)” 定义:

  1. Assert: Type(O) is Object.
  2. Assert: IsPropertyKey(P) is true.
  3. Let handler be O.[[ProxyHandler]].
  4. If handler is null, throw a TypeError exception.
  5. Let target be O.[[ProxyTarget]].
  6. Let trap be ? Get(handler, "get").
  7. If trap is undefined, then
    a. Return ? Get(target, P, Receiver).
  8. Let trapResult be ? Call(trap, handler, «target, P, Receiver»).
  9. Let booleanTrapResult be ToBoolean(trapResult).
  10. If booleanTrapResult is false, return undefined.
  11. Return trapResult.

参数«target, P, Receiver»直接对应 TS 的get(target: T, p: PropertyKey, receiver: any): any。注意P的类型是PropertyKey(第 6.1.5 节定义为String | Symbol),这解释了为何gettrap 中p不能是 number(1会被自动转为字符串"1")。而Receiver参数决定了this绑定,规范第 9.5.2.1 节注释:“The Receiver parameter is used to determine the correctthisvalue for thegetoperation.” —— 这正是Reflect.get(target, key, receiver)的设计依据。

5.3 构建自己的规范查询工作流:VS Code + Markdown Preview

我日常将ecma262-6-cn的 HTML 版本拖入 VS Code,配合以下配置实现秒级查询:

  1. 安装插件Markdown Preview Enhanced;
  2. 在.vscode/settings.json中添加:
{ "markdown-preview-enhanced.enableScriptExecution": true, "markdown-preview-enhanced.previewTheme": "github-light.css", "editor.quickSuggestions": { "strings": true } }
  1. 创建ecma-search.md文件,粘贴常用搜索片段:
### 快速跳转 - [词法文法](#sec-lexical-grammar) - [Promise.all](#sec-promise.all) - [Proxy [[Get]]](#sec-proxy-object-internal-methods-and-internal-slots-get-p-receiver) - [await 表达式](#sec-async-function-definitions-runtime-semantics-evaluation)
  1. 按Ctrl+Shift+P→Markdown Preview Enhanced: Open Preview to the Side,点击链接即跳转。

注意:HTML 版本的锚点 ID 严格按规范小节编号生成(如#sec-promise.all),无需记忆,直接复制 PDF 中的章节号即可。

从那以后我每次遇到“这个行为到底合不合法”的疑问,都强制走一遍“现象→关键词→HTML 锚点→算法步骤→抽象操作”的闭环。哪怕花 15 分钟,也比在 Stack Overflow 上猜 2 小时靠谱。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询