Expressions
Identifiers
标识符是代码中用来标识变量,函数以及属性的字符序列。
在JS中,从语法上将标识符分为以下几种:
- IdentifiersReference: 在代码中对标识符的引用,可以跟
yield
和await
结合使用。
语法规则
- Identifier: 一般的标识符引用
[~Yield]
yield: 当不在生成器上下文中时,yield 可以作为标识符引用。[~Await]
await: 当不在异步上下文中时,await 可以作为标识符引用。
// 一般标识符引用let x = 0;console.log(x); // x 为一个IdentifierReference// 非生成器上下文中,yield 可以作为标识符引用let yield = 10;console.log(yield); // 输出: 10// 非异步上下文中,await 可以作为标识符引用let await = 20;console.log(await); // 输出: 20async function asyncFunc() {// 在异步函数中,await 不能作为标识符引用// let await = 30; // 语法错误}function* generatorFunc() {// 在生成器函数中,yield 不能作为标识符引用// let yield = 40; // 语法错误}
- BindingIdentifier:是在变量声明、函数声明或函数参数中用来命名的标识符。它用于绑定一个名称到特定的值或函数。
语法规则
- Identifier: 一般的标识符声明。
- yield: 在生成器上下文中,yield 不能作为标识符声明。
- await: 在异步上下文中,await 不能作为标识符声明。
let y = 20; // 这里的 `y` 就是一个 BindingIdentifierfunction add(a, b) { // 这里的 `a` 和 `b` 都是 BindingIdentifierreturn a + b;}const obj = {method() { // 这里 `method` 是一个方法名,不是 BindingIdentifierlet z = 30; // 这里的 `z` 是一个 BindingIdentifier}}
- LabelIdentifier: 用于标记代码块中的标签,常用于控制流语句。
语法规则
- Identifier: 一般的标识符引用
[~Yield]
yield: 当不在生成器上下文中时,yield 可以作为标识符引用。[~Await]
await: 当不在异步上下文中时,await 可以作为标识符引用。
// 在非生成器上下文中,yield 可以作为 LabelIdentifieryieldLabel: for (let i = 0; i < 5; i++) {console.log(i);break yieldLabel; // 正常工作}// 在非异步上下文中,await 可以作为 LabelIdentifierawaitLabel: for (let i = 0; i < 5; i++) {console.log(i);break awaitLabel; // 正常工作}async function asyncFunc() {// 在异步函数中,await 不能作为 LabelIdentifier// awaitLabel: for (let i = 0; i < 5; i++) {// console.log(i);// break awaitLabel; // 语法错误// }}function* generatorFunc() {// 在生成器函数中,yield 不能作为 LabelIdentifier// yieldLabel: for (let i = 0; i < 5; i++) {// console.log(i);// break yieldLabel; // 语法错误// }}
Static Semantics
Note
静态语义:早期错误是指在代码实际执行之前,由编译器或解释器在代码的静态分析阶段检测到的语法或语义错误。这些错误是通过分析代码结构而不是执行代码来发现的。
- 早期错误是指在代码执行之前就会检测到并报告的错误。例如,语法错误、在严格模式下使用保留字、重复声明变量等。
- 静态语义检查在解析代码(编译阶段的一部分)时进行。这些检查确保代码在语法上和语义上都是正确的,以便能够正确执行。
标识符在静态语义分析阶段主要有以下几种场景是会出现异常:
- 在严格模式下,BindingIdentifier中一般标识符引用的值如果是
arguments
或者eval
的话,是一个语法错误
'use strict';let arguments = 'arguments'; // SyntaxError: Binding 'arguments' in strict mode.let eval = ''; // SyntaxError: Binding 'eval' in strict mode.function foo(arguments) { // SyntaxError: Binding 'arguments' in strict mode.console.log(arguments)}function foo(eval) { // SyntaxError: Binding 'eval' in strict mode.console.log(eval)}
- 在严格模式下,标识符为
yield
为语法错误
'use strict'let yield = ''; // SyntaxError: Unexpected strict mode reserved word
- 当目标符号(goal symbol)是模块时,不能使用
await
作为标识符。
'use strict'let await = ''; // SyntaxError: Unexpected strict mode reserved word
- 标识符的不能是关键字(ReserveWord)
需要注意的是转义序列作为标识的情况。
Note
在 ECMAScript 规范中,关于 IdentifierName 的规定确保了标识符名称(IdentifierName)中的 Unicode 转义序列(Unicode escape sequences)会被规范化,因此无法使用这些转义序列来创建与保留字相同的标识符。
例如:
// 使用转义序列定义标识符let \u0069f = "value"; // 这里的 \u0069 表示字符 'i',定义了一个变量名为 'if'// 尝试定义与保留字相同的标识符,这会导致语法错误let \u0065lse = "value"; // SyntaxError: Unexpected token 'else'
当然,这种方式并不推荐。
Runtime Semantics
Note
运行时语义:求值是指在代码实际执行过程中,解释器如何计算和处理代码中的表达式、语句和程序结构。这是代码的动态行为部分,是解释器在运行时执行代码时所遵循的规则和步骤。
- 运行时语义涉及变量赋值、表达式计算、函数调用、控制流等。
- 求值是指计算表达式的值并执行语句。例如,求值一个算术表达式、调用一个函数、执行一个循环等。
标识符在运行时阶段主要执行ResolveBinding方法。
此方法是一个查找标识符绑定的抽象操作,在当前作用域链中查找特定名称的标识符,返回其对应的绑定对象,
如果没有找到,会抛出一个ReferenceError
。
Primary Expressions
Primary Expressions是JS中表达式的基础,是其他复杂表达式的构建块,主要体现在以下几种:
this
关键字(ReserveWord)- Identifiers
- Literal
- Array Initializer
- Object Initializer
- FunctionExpression
- ClassExpression
- GeneratorExpression
- AsyncFunctionExpression
- AsyncGeneratorExpression
- RegluarExpressionLiteral
- TemplateLiteral
Literal
Literal是直接表示值的表达式,主要有以下几种NullLiteral,BooleamLiteral,StringLiteral和NumericLiteral。
Note
这四种字面量在运行时阶段求值过程如下:
- NullLiteral:求值为
null
。 - BooleanLiteral:求值为
true
或者false
。 - StringLiteral:Runtime 求值过程为字符串。
- NumericLiteral:Runtime 求值过程为数字。
RegluarExpression Literals
Template Literals
Array Initializer
数组字面量允许可以以一种简洁的方式来创建和初始化数组。
语法规则, 示例如下
- 空数组
const arr = [];
- 元素列表
const arr = [1,2,3];
- 包含元素列表并以省略号结尾的数组
const arr = [1,2,3,];
- 包含元素列表带有空位(省略号)的数组
const arr = [1,,3,];
- 包含元素列表带有表达式的数组
const arr = [1+2,2,3-1,];
- 包含元素列表带有
...
的数组
const aar = [1,2,3]const arr = [1+2,2,3-1,...aar];
在运行时语义: 求值阶段主要会执行两个方法ArrayCreate和ArrayAccumulation
,分别用于创建数组和累加数组元素。
Object Initializer
对象初始化器是描述对象初始化的表达式,以类似文字的形式编写。它是零对或多对属性键和关联值的列表,
用{}
括起来。值不需要是文字;每次计算对象初始值设定项时都会计算它们。
对象字面量主要有三种形式:
- 空对象
{}
- 包含属性定义列表的对象
{PropertyDefinitionList}
- 包含属性定义列表的对象并以逗号结尾的对象
{PropertyDefinitionList,}
PropertyDefinitionList 属性定义列表可以包含一个或者多个属性定义,可以是以下几种形式:
- 标识符引用IdentifierReference
const name = 'nate'const obj = {name}
- 初始化名称封面CoverInitializedName 注意,此语法仅在对象解构中使用,其他方式会抛出语法错误
const obj = { name = 'nate' }
- 属性名和赋值表达式对PropertyName : AssignmentExpression
const obj = { name: 'nate', age: 18 }
- 方法定义MethodDefinition
const obj = {getName: () => {return 'hello'}}
Note
在Static semantics: early Error中,方法定义如果有以下两种方式,会抛出语法错误
- 方法中使用了直接使用
super
const obj = {method() {super.method()}}
- 方法中使用了
#
私有字段的错误
const obj = {#privateKey: 43,getKey() {return this.#privateKey}}
- 扩展属性Spread property
const obj1 = { a: 1, b: 2 };const obj2 = { ...obj1, c: 3 }; // { a: 1, b: 2, c: 3 }
Note
Static Semantics: Early Errors:
当对象字面量包含属性定义列表PropertyDefinitionList时,如果列表中有多个 __proto__
属性,
并且这些属性是通过 PropertyDefinition, PropertyName, AssignmentExpression 形式定义的,
那么会引发语法错误。不过,如果这个对象字面量是作为 JSON.parse
方法的一部分被解析的,则不会应用这条规则。
- 引发错误的情况
const obj = {__proto__: {},__proto__: {}}; // 语法错误:重复的 __proto__ 属性
- 合法的情况
const obj1 = {__proto__: {},otherProperty: 42}; // 合法const obj2 = {method() {// 方法定义,不是 AssignmentExpression 形式},__proto__: {}}; // 合法const obj3 = {...otherObject,__proto__: {}}; // 合法:扩展属性和 __proto__
- JSON.parse的特例
const jsonString = '{"__proto__": {}, "__proto__": {}}';const obj = JSON.parse(jsonString); // 合法:JSON.parse 不应用重复 __proto__ 的检查
PropertyName,属性名主要有以下两种形式:
- 字面属性名 LiteralPropertyName,可以是标识符名称,字符串字面量,数字字面量
const obj = {name: 'nate','age': 18,123: 'none'}
- 计算属性名 ComputedPropertyName,使用
[]
包围的表达式作为属性名
const key = 'dynamicKey'const obj = {[key]: 'dynamicValue'}
Update Expressions
Unary Operators
Unary Operators主要有
typeof
typeof运算符主要确定变量或者一元表达式的数据类型。返回一个字符串,表示操作数的类型。主要是在运行阶段进行求值
求值过程: