Expressions

Identifiers

标识符是代码中用来标识变量函数以及属性字符序列

在JS中,从语法上将标识符分为以下几种:

  1. IdentifiersReference: 在代码中对标识符的引用,可以跟yieldawait结合使用。

语法规则

  1. Identifier: 一般的标识符引用
  2. [~Yield] yield: 当不在生成器上下文中时,yield 可以作为标识符引用。
  3. [~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); // 输出: 20
async function asyncFunc() {
// 在异步函数中,await 不能作为标识符引用
// let await = 30; // 语法错误
}
function* generatorFunc() {
// 在生成器函数中,yield 不能作为标识符引用
// let yield = 40; // 语法错误
}
  1. BindingIdentifier是在变量声明、函数声明或函数参数中用来命名的标识符。它用于绑定一个名称到特定的值或函数。

语法规则

  1. Identifier: 一般的标识符声明。
  2. yield: 在生成器上下文中,yield 不能作为标识符声明。
  3. await: 在异步上下文中,await 不能作为标识符声明。
示例
let y = 20; // 这里的 `y` 就是一个 BindingIdentifier
function add(a, b) { // 这里的 `a` 和 `b` 都是 BindingIdentifier
return a + b;
}
const obj = {
method() { // 这里 `method` 是一个方法名,不是 BindingIdentifier
let z = 30; // 这里的 `z` 是一个 BindingIdentifier
}
}
  1. LabelIdentifier: 用于标记代码块中的标签,常用于控制流语句。

语法规则

  1. Identifier: 一般的标识符引用
  2. [~Yield] yield: 当不在生成器上下文中时,yield 可以作为标识符引用。
  3. [~Await] await: 当不在异步上下文中时,await 可以作为标识符引用。
示例
// 在非生成器上下文中,yield 可以作为 LabelIdentifier
yieldLabel: for (let i = 0; i < 5; i++) {
console.log(i);
break yieldLabel; // 正常工作
}
// 在非异步上下文中,await 可以作为 LabelIdentifier
awaitLabel: 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

静态语义:早期错误是指在代码实际执行之前,由编译器或解释器在代码的静态分析阶段检测到的语法或语义错误。这些错误是通过分析代码结构而不是执行代码来发现的。

  • 早期错误是指在代码执行之前就会检测到并报告的错误。例如,语法错误、在严格模式下使用保留字、重复声明变量等。
  • 静态语义检查在解析代码(编译阶段的一部分)时进行。这些检查确保代码在语法上和语义上都是正确的,以便能够正确执行。

标识符在静态语义分析阶段主要有以下几种场景是会出现异常:

  1. 严格模式下,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)
}
  1. 严格模式下,标识符为yield为语法错误
'use strict'
let yield = ''; // SyntaxError: Unexpected strict mode reserved word
  1. 目标符号(goal symbol)是模块时,不能使用await作为标识符。
'use strict'
let await = ''; // SyntaxError: Unexpected strict mode reserved word
  1. 标识符的不能是关键字(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中表达式的基础,是其他复杂表达式的构建块,主要体现在以下几种:

Literal

Literal是直接表示值的表达式,主要有以下几种NullLiteralBooleamLiteralStringLiteralNumericLiteral

Note

这四种字面量在运行时阶段求值过程如下:

RegluarExpression Literals

RegularExpression 语法

Template Literals

Template String详细信息

Array Initializer

数组字面量允许可以以一种简洁的方式来创建和初始化数组。

语法规则, 示例如下

  1. 空数组
const arr = [];
  1. 元素列表
const arr = [1,2,3];
  1. 包含元素列表并以省略号结尾的数组
const arr = [1,2,3,];
  1. 包含元素列表带有空位(省略号)的数组
const arr = [1,,3,];
  1. 包含元素列表带有表达式的数组
const arr = [1+2,2,3-1,];
  1. 包含元素列表带有...的数组
const aar = [1,2,3]
const arr = [1+2,2,3-1,...aar];

运行时语义: 求值阶段主要会执行两个方法ArrayCreateArrayAccumulation,分别用于创建数组和累加数组元素。

Object Initializer

对象初始化器是描述对象初始化的表达式,以类似文字的形式编写。它是零对或多对属性键和关联值的列表, 用{}括起来。值不需要是文字;每次计算对象初始值设定项时都会计算它们。

对象字面量主要有三种形式:

  1. 空对象{}
  2. 包含属性定义列表的对象 {PropertyDefinitionList}
  3. 包含属性定义列表的对象并以逗号结尾的对象 {PropertyDefinitionList,}

PropertyDefinitionList 属性定义列表可以包含一个或者多个属性定义,可以是以下几种形式:

  1. 标识符引用IdentifierReference
const name = 'nate'
const obj = {name}
  1. 初始化名称封面CoverInitializedName 注意,此语法仅在对象解构中使用,其他方式会抛出语法错误
const obj = { name = 'nate' }
  1. 属性名和赋值表达式对PropertyName : AssignmentExpression
const obj = { name: 'nate', age: 18 }
  1. 方法定义MethodDefinition
const obj = {
getName: () => {
return 'hello'
}
}

Note

Static semantics: early Error中,方法定义如果有以下两种方式,会抛出语法错误

  1. 方法中使用了直接使用super
const obj = {
method() {
super.method()
}
}
  1. 方法中使用了#私有字段的错误
const obj = {
#privateKey: 43,
getKey() {
return this.#privateKey
}
}
  1. 扩展属性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 方法的一部分被解析的,则不会应用这条规则。

  1. 引发错误的情况
const obj = {
__proto__: {},
__proto__: {}
}; // 语法错误:重复的 __proto__ 属性
  1. 合法的情况
const obj1 = {
__proto__: {},
otherProperty: 42
}; // 合法
const obj2 = {
method() {
// 方法定义,不是 AssignmentExpression 形式
},
__proto__: {}
}; // 合法
const obj3 = {
...otherObject,
__proto__: {}
}; // 合法:扩展属性和 __proto__
  1. JSON.parse的特例
const jsonString = '{"__proto__": {}, "__proto__": {}}';
const obj = JSON.parse(jsonString); // 合法:JSON.parse 不应用重复 __proto__ 的检查

PropertyName,属性名主要有以下两种形式:

  1. 字面属性名 LiteralPropertyName,可以是标识符名称,字符串字面量,数字字面量
const obj = {
name: 'nate',
'age': 18,
123: 'none'
}
  1. 计算属性名 ComputedPropertyName,使用[]包围的表达式作为属性名
const key = 'dynamicKey'
const obj = {
[key]: 'dynamicValue'
}

Update Expressions

Unary Operators

Unary Operators主要有

typeof

typeof运算符主要确定变量或者一元表达式的数据类型。返回一个字符串,表示操作数的类型。主要是在运行阶段进行求值

求值过程