💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
## 6.语法 > 原文: [http://exploringjs.com/impatient-js/ch_syntax.html](http://exploringjs.com/impatient-js/ch_syntax.html) > > 贡献者:[Hunter-liu](https://github.com/lq920320) ### 6.1. JavaScript 语法概述 #### 6.1.1. 基本语法 注释: ```javascript // 单行注释 ``` ```javascript /* 多行 注释 */ ``` *原始*(原子)值: ```javascript // Booleans true false // Numbers (JavaScript 的数字只有一种类型) -123 1.414 // String (JavaScript 的字符没有对应的类型) 'abc' "abc" ``` *断言*描述了计算结果的预期结果,如果这些期望不正确则抛出异常。例如,以下断言声明计算 7 加 1 的结果必须为 8: ```javascript assert.equal(7 + 1, 8); ``` `assert.equal()`是一个方法调用(对象是`assert`,方法是`.equal()`),有两个参数:实际结果和预期结果。它是 Node.js 断言 API 的一部分,本书后面的[将对此进行解释](/docs/10.md)。 打印日志到[浏览器的控制台](/docs/7.md#531浏览器控制台)或 Node.js: ```javascript // 将值标准输出打印(另一种方法调用) console.log("Hello!"); ``` ```javascript // 将错误信息标准输出打印 console.error('Something weng wrong!'); ``` 运算符: ```javascript // 布尔运算符 assert.equal(true && false, false); // 与 assert.equal(true || false, true); // 或 // 数学运算符 assert.equal(3 + 4, 7); assert.equal(5 - 1, 4); assert.equal(3 * 4, 12); assert.equal(9 / 3, 3); // 字符串操作符 assert.equal('a' + 'b', 'ab'); assert.equal('I see ' + 3 + ' monkeys', 'I see 3 monkeys'); // 比较运算符 assert.equal(3 < 4, true); assert.equal(3 <= 4, true); assert.equal('abc' === 'abc', true); assert.equal('abc' !== 'def', true); ``` 声明变量: ```javascript let x; // 声明 x(可变的) x = 3 * 5; // 给 x 赋值 let y = 3 * 5; // 声明变量并为其赋值 const z = 8; // 声明 y(不可变的) ``` 控制流声明: ```javascript // 条件语句 if (x < 0) { // x 小于 0? x = -x; } ``` 普通函数声明: ```javascript // add1() 有 a 和 b 两个参数 function add1(a, b) { return a + b; } // 调用函数 add1() assert.equal(add1(5, 2), 7); ``` 箭头函数表达式(特别用作函数调用和方法调用的参数): ```javascript const add2 = (a, b) => a + b; // 调用方法 add2() assert.equal(add2(5, 2), 7); const add3 = (a, b) => { return a + b }; ``` 上面的代码包含以下箭头函数(关于*表达式*和*语句块*等术语将在在[本章后面解释](/docs/8.md#64-语句与表达)): ```javascript // 主体为一个表达式的箭头函数 (a, b) => a + b // 主体为一个代码块的箭头函数 (a, b) => { return a + b } ``` 对象: ```javascript // 通过 object 符号({})创建一个对象 const obj = { first: 'Jane', // 属性 last: 'Doe', // 属性 getFullName() { // 属性(方法) return this.first + ' ' + this.last; }, }; // 获取某个属性值 assert.equal(obj.first, 'Jane'); // 设置某个属性的值 obj.first = 'Janey'; // 调用对象的方法 assert.equal(obj.getFullName(), 'Janey Doe'); ``` 数组(数组也是对象): ```javascript // 通过 Array 符号([])创建一个数组 const arr = ['a', 'b', 'c']; // 获取某个数组元素 assert.equal(arr[1], 'b'); // 设置某个数组元素的值 arr[1] = 'β'; ``` #### 6.1.2. 模块 每个模块都是一个文件。例如,考虑以下两个包含模块的文件: ```js file-tools.js main.js ``` `file-tools.js`中的模块导出其功能`isTextFilePath()`: ```js export function isTextFilePath(filePath) { return filePath.endsWith('.txt'); } ``` `main.js`中的模块导入整个模块`path`和函数`isTextFilePath()`: ```js // 导入整个模块 `path` import * as path from 'path'; // 导入模块 file-tools.js export的一个函数 import {isTextFilePath} from './file-tools.js'; ``` #### 6.1.3. 法定变量和属性名称 变量名称和属性名称的语法类别称为*标识符*。 标识符允许具有以下字符: - Unicode 字母:`A` - `Z`,`a` - `z`(等) - `$`,`_` - Unicode 数字:`0` - `9`(等) - 变量名不能以数字开头 有些单词在 JavaScript 中有特殊含义,称为*保留字*。例如:`if`,`true`,`const`。 保留字不能用作变量名: ```js const if = 123; // SyntaxError: Unexpected token if ``` 但它们被允许作为属性的名称: ``` > const obj = { if: 123 }; > obj.if 123 ``` #### 6.1.4. 连接类型 用于连接单词的常见类型是: - 驼峰:`threeConcatenatedWords` - 下划线(也称*蛇形*):`three_concatenated_words` - 分隔线(也称*串形*):`three-concatenated-words` #### 6.1.5. 命名大写 通常,JavaScript 使用驼峰大小写,但常量除外。 小写: - 函数,变量:`myFunction` - 方法:`obj.myMethod` - CSS: - CSS 实体:`special-class` - 对应的 JavaScript 变量:`specialClass` 大写: - 类名:`MyClass` - 常数:`MY_CONSTANT` - 常量也常用骆驼写成:`myConstant` #### 6.1.6. 在哪里加分号? 在语句的最后: ```js const x = 123; func(); ``` 但是,如果该语句以大括号结尾,那就不加分号了: ```js while (false) { // ··· } // 无分号 function func() { // ··· } // 无分号 ``` 但是,在这样的语句之后添加分号不是语法错误 - 它被解释为空语句: ```js // 函数声明后跟空语句 function func() { // ··· }; ``` ![](https://img.kancloud.cn/ff/a8/ffa8e16628cad59b09c786b836722faa.svg) **测验:基本** 参见[测验应用程序](/docs/11.md#91测验)。 ### 6.2. (深入) 本章的所有其余部分都是深入说明(上面的内容)。 ### 6.3. 身份标识 #### 6.3.1. 有效标识符(变量名等) 首字符: - Unicode 字母(包括`é`和`ü`等重音字符和非拉丁字母的字符,如`α`) - `$` - `_` 后续字符: - 合法的首字符 - Unicode 数字(包括东方阿拉伯数字) - 一些其他 Unicode 标记和标点符号 例如: ```js const ε = 0.0001; const строка = ''; let _tmp = 0; const $foo2 = true; ``` #### 6.3.2. 保留字 保留字不能是变量名,但它们可以是属性名。 所有 JavaScript *关键字*都是保留字: > `await` `break` `case` `catch` `class` `const` `continue` `debugger` `default` `delete` `do` `else` `export` `extends` `finally` `for` `function` `if` `import` `in` `instanceof` `let` `new` `return` `static` `super` `switch` `this` `throw` `try` `typeof` `var` `void` `while` `with` `yield` 以下标记也是关键字,但目前未在该语言中使用: > `enum` `implements` `package` `protected` `interface` `private` `public` 以下值是保留字: > `true` `false` `null` 从技术上讲,这些单词不是保留的,但你也应该避免使用它们,因为它们实际上是关键字: > `Infinity` `NaN` `undefined` `async` 您也不应将全局变量的名称(`String`,`Math`等)用于您自己的变量和参数。 ### 6.4. 语句与表达 在本节中,我们将探讨 JavaScript 如何区分两种句法结构:_ 语句 _ 和 _ 表达式 _。之后,我们会发现这会导致问题,因为相同的语法可能意味着不同的东西,具体取决于它的使用位置。 ![](https://img.kancloud.cn/7b/17/7b17729df07100ffa1c582a9b46ac13a.svg) **我们假装只有语句和表达** 为简单起见,我们假装 JavaScript 中只有语句和表达式。 #### 6.4.1. 语句 *语句* 是一段可以执行并执行某种操作的代码。例如,`if`是一段语句: ```js let myStr; if (myBool) { myStr = 'Yes'; } else { myStr = 'No'; } ``` 语句的另一个例子:函数声明。 ```js function twice(x) { return x + x; } ``` #### 6.4.2. 表达式 *表达式*是可以*评估*以产生值的一段代码。例如,括号之间的代码是一个表达式: ```js let myStr = (myBool ? 'Yes' : 'No'); ``` 括号之间使用的运算符 `_?_:_` 称为*三元运算符*。它是`if`语句的表达式版本。 让我们看一下表达式的更多例子。我们输入表达式,REPL 为我们评估它们: ``` > 'ab' + 'cd' 'abcd' > Number('123') 123 > true || false true ``` #### 6.4.3. 什么是允许的? JavaScript 源代码中的当前位置决定了您可以使用哪种语法结构: - 函数体必须是一系列语句: ```js function max(x, y) { if (x > y) { return x; } else { return y; } } ``` - 函数调用或方法调用的参数必须是表达式: ```js console.log('ab' + 'cd', Number('123')); ``` 但是,表达式可以用作语句。然后将它们称为*表达式语句*。相反的情况并非如此:当上下文需要表达式时,你便不能使用语句。 以下代码演示了某个表达式`bar()`可以是表达式还是语句——它取决于上下文: ```js console.log(bar()); bar(); ``` ### 6.5. 语法模糊 JavaScript 有几种语法歧义的编程结构:相同的语法被不同地解释,这取决于它是在语句上下文中还是在表达式上下文中使用。本节探讨了这一现象及其引发的陷阱。 #### 6.5.1. 相同的语法:函数声明和函数表达式 *函数声明*是一个声明: ```js function id(x) { return x; } ``` *函数表达式*是一个表达式(`=`右侧的): ```js const id = function me(x) { return x; }; ``` #### 6.5.2. 相同的语法:object literal 和 block 在下面的代码中,`{}`是 *对象字面值*:一个创建空对象的表达式。 ```js const obj = {}; ``` 这是一个空代码块(声明): ```js { } ``` #### 6.5.3. 消除歧义 歧义只是语句上下文中的一个问题:如果 JavaScript 解析器遇到模糊语法,它不知道它是简单语句还是表达式语句。例如: - 如果语句以`function`开头:它是函数声明还是函数表达式? - 如果语句以`{`开头:它是对象字面值还是代码块? 为了解决歧义,以`function`或`{`开头的语句永远不会被解释为表达式。如果希望表达式语句以这些标记中的任何一个开头,则必须将其包装在括号中: ```js (function (x) { console.log(x) })('abc'); // Output: // 'abc' ``` 在这段代码中: 1. 我们首先通过函数表达式创建一个函数: ```js function (x) { console.log(x) } ``` 1. 然后我们调用该函数:`('abc')` #1 只被解释为表达式,因为我们将它包装在括号中。如果我们没有,我们会得到一个语法错误,因为 JavaScript 需要一个函数声明,之后还会警告缺少的函数名称。此外,你不能在函数声明后立即进行函数调用。 在本书的后面,我们将看到更多由语法模糊引起的陷阱的例子: * [通过对象解构分配](/docs/41.md#3443语法陷阱通过对象解构分配) * [从箭头函数](/docs/28.md#23333语法陷阱从箭头函数返回一个对象字面值) ### 6.6. 分号 #### 6.6.1. 分号的经验法则 每个语句都以分号结束。 ```js const x = 3; someFunction('abc'); i++; ``` 例外:以块结尾的语句。 ```js function foo() { // ··· } if (y > 0) { // ··· } ``` 以下情况有点棘手: ```js const func = () => {}; // 分号! ``` 整个`const`声明(一个语句)以分号结尾,但在其中,有一个箭头函数表达式。那就是:声明本身并不是以花括号结尾;它是嵌入式箭头函数表达式。这就是为什么最后会有一个分号的原因。 #### 6.6.2. 分号:控制语句 控制语句的主体本身就是一个声明。例如,这是`while`循环的语法: ```js while (condition) 语句 ``` 正文可以是一行语句: ```js while (a > 0) a--; ``` 但代码块也是声明,因此也是控制声明的合法主体: ```js while (a > 0) { a--; } ``` 如果你想让一个循环有一个空的主体,那么你的首选便是一个空语句(它只是一个分号): ```js while (processNextItem() > 0); ``` 你的第二个选择是一个空语句块: ```js while (processNextItem() > 0) {} ``` ### 6.7. 自动分号插入(ASI) 虽然我建议总是写分号,但大多数都是 JavaScript 中的可选项。使这成为可能的机制称为*自动分号插入*(ASI)。在某种程度上,它可以纠正语法错误。 ASI 的工作原理如下。语句解析会直到出现以下情况: - 分号 - 行终止符后跟非法标记 换句话说,ASI 可以看作在换行符处插入分号。接下来的小节将介绍 ASI 的陷阱。 #### 6.7.1. ASI 意外触发 关于 ASI 的好消息是——如果你不依赖它并且总是写分号——你只需要注意一个陷阱。这是 JavaScript 禁止在一些标记之后的换行符。如果插入换行符,也会插入分号。 最实际相关的标记是`return`。例如,考虑以下代码: ```js return { first: 'jane' }; ``` 此代码解析为: ```js return; { first: 'jane'; } ; ``` 也就是说,一个空的 return 语句,后跟一个代码块,后跟一个空语句。 为什么 JavaScript 会这样做?它可以防止在`return`之后意外返回一行中的值。 #### 6.7.2. ASI 意外没有触发 在某些情况下,当您认为 ASI 应该触发时,ASI *并没有触发*。对于那些不喜欢分号的人来说,这会使生活更加复杂,因为他们需要意识到这些情况。以下是三个例子。还有更多。 **例 1:**非预期的函数调用。 ```js a = b + c (d + e).print() ``` 解析为: ```js a = b + c(d + e).print(); ``` **例 2:**意外分裂。 ```js a = b /hi/g.exec(c).map(d) ``` 解析为: ```js a = b / hi / g.exec(c).map(d); ``` **例 3:**非预期的属性访问。 ```js someFunction() ['ul', 'ol'].map(x => x + x) ``` 被执行为: ```js const propKey = ('ul','ol'); assert.equal(propKey, 'ol'); // 因为逗号 someFunction()[propKey].map(x => x + x); ``` ### 6.8. 分号:最佳实践 我建议你要经常写分号: - 我喜欢它提供代码的视觉结构——你清楚地看到一个语句何时结束。 - 要记住的规则较少。 - 大多数 JavaScript 程序员使用分号。 然而,也有许多人不喜欢添加分号的视觉混乱。如果你是其中之一:可能会认为没有它们的代码*亦是*合法的。我建议你使用工具来帮助您避免错误。以下是两个例子: - 自动代码格式化程序 [Prettier](https://prettier.io) 可以配置为不使用分号。然后它会自动修复问题。例如,如果它遇到以方括号开头的行,则它以分号为前缀。 - 静态检查器 [ESLint](https://eslint.org) 有一套你可以表述你的首选样式的[规则](https://eslint.org/docs/rules/semi)(始终使用分号或尽可能少的分号),并向你对关键问题报警。 ### 6.9. 严格模式 从 ECMAScript 5 开始,你可以选择在所谓的*严格模式*中执行 JavaScript。在该模式下,语言稍微清晰:不存在一些怪异的写法并且同时会抛出更多异常。 默认(非严格)模式也称为*草率模式*。 请注意,默认模式在模块和类中默认打开,因此在编写现代 JavaScript(几乎总是位于模块中)时,您并不需要了解它。在本书中,我假设严格模式始终打开。 #### 6.9.1. 开启严格模式 在旧脚本文件和 CommonJS 模块中,通过将以下代码放在第一行中,您可以为完整文件切换严格模式: ```js 'use strict'; ``` 关于这个“指令”的巧妙之处在于,5 之前的 ECMAScript 版本只是忽略它:它是一个什么都不做的表达式语句。 你还可以仅为单个函数打开严格模式: ``` function functionInStrictMode() { 'use strict'; } ``` #### 6.9.2. 示例:严格模式 让我们看一个示例,其中草率模式做一些严格模式不会做的坏事:更改未知变量(未通过`let`或类似创建)创建一个全局变量。 ```js function sloppyFunc() { unknownVar1 = 123; } sloppyFunc(); // Created global variable `unknownVar1`: assert.equal(unknownVar1, 123); ``` 严格模式则做得更好: ```js function strictFunc() { 'use strict'; unknownVar2 = 123; } assert.throws( () => strictFunc(), { name: 'ReferenceError', message: 'unknownVar2 is not defined', }); ``` `assert.throws()`要求它的第一个参数,某个函数,在调用时抛出`ReferenceError`。 ![](https://img.kancloud.cn/ff/a8/ffa8e16628cad59b09c786b836722faa.svg) **测验:高级** 参见[测验应用程序](/docs/11.md#91测验)。