--- title: JavaScript-语法 date: 2021-07-12 15:20:03 tags: JavaScript categories: 笔记 url: javascript-syntax --- 在 JavaScript 中,有很多常见的语法仍然有很多地方容易产生困惑、造成误解。 ## 语句与表达式 语句(statement)和表达式(expression)常常被混为一谈,但他们二者之间有细微的差别。语句相当于一句话,而表达式更类似于一条短语。 JavaScript 中表达式可以返回一个结果值: ```js let a = 3 * 6; const b = a; b; ``` 上述带有`let`与`const`的语句被称为“声明语句”(declaration statement),而不带声明关键词的`a = 3 * 6;`则是“赋值表达式”。 乍一看可能还是不能太过于区分他们之间的区别,但是有些地方会让我们强制区分他们。在现代 SPA 框架中,模板语法中通常只允许插入 JavaScript 表达式,而不允许插入语句。例如在常见的 Vue 和 React 中: ```vue ``` ### 语句的结果值 基本上所有的语句都有一个结果值(undefined 也算)。 如果经常和控制台打交道,应该会发现很多时候控制台都会给我们返回一个 undefined。其实这就是语句所返回的值,只不过大部分情况下都是 undefined。 代码块中的返回值是最后一个语句/表达式的结果: ```js let a ; // undefined if (true) { a = 3 + 1; } // 4 ``` 目前代码块语句返回的值无法被拿到: ```js b = if (true) { a = 3 + 1; } // VM268:1 Uncaught SyntaxError: Unexpected token 'if' ``` 当然也有变通的方法 evil: ```js b = eval("if (true) {a = 3 + 1;}"); b; // 4 ``` 除了万恶的 evil,ES7 还有一项“do 表达式”提案,目前环境还没实现: ```js b = do { if (true) { a = 3 + 1; } }; b; // 4 ``` ### 表达式的副作用 副作用通常是指执行了语句之后,除了返回值或赋值,还有其他变量等被修改。 最常见的副作用的表达式是函数调用: ```js const foo = () => { a = a + 1; }; let a = 1; foo(); // 结果:undefined,副作用:a 的值被修改 ``` ### 上下文规则 在 JavaScript 语法规则中,有时候同样的语法在不同的情况下会有不同的解释。这些语法规则孤立起来会很难理解。 #### 大括号 大括号的作用有很多,随着 JavaScript 的演进可能会出现更多类似的情况。 **对象常量** 大括号可以使用表达式的方式定义对象: ```js const a = { foo: bar() } ``` 大括号内的内容被赋值给变量 a,因而它是一个对象常量。 **标签** 将上述声明语句去掉,这时的大括号可不是一个孤立的对象常量。因为这里的`{...}`被看作了一个普通的代码块。这里的`foo: bar()`被解释为标签语句。 ```js { foo: bar() } ``` 标签语句配合 continue 和 break 语句可以实现类似于 goto 的方式进行跳转。但 goto 是一种极为糟糕的编码方式,所以 JavaScript 并不支持真正的 goto 语句。 #### 代码块 有一个强制类型转换的坑经常被提到: ```js [] + {} // "[object Object]" {} + [] // 0 ``` 看上去简直就是 JavaScript 在和我们开玩笑。其实不然,上述情况并不是非常难以理解。 第一行是普通的强制类型转换,二者都会被强制转换字符串,而`[]`被转换为了`''`,`{}`被转换为了`"[object Object]"`,所以他们相加的最后结果就是`"[object Object]"`。 第二行看上去有点莫名其妙,换个位置结果就变了。这里其实`{}`并没有参与运算,而是被当作了一段独立的空代码块。真正参与运算的是`+[]`,最终被转换为数字 0。 #### 对象解构 从 ES6 开始`{...}`也可用于解构赋值。 ```js const foo = () => { return { name: 'xfy', age: 18, }; }; const { name, age } = foo(); console.log(name, age); ``` 同理,函数形参也可以利用解构赋值。 #### else if 和可选代码块 其实`else if`并不存在于 JavaScript 语法中,只是多个`if else`只包含单条语句的时候可以省略代码块的`{...}`。 实际上`else if`是这样的: ```js if (a) { console.log(a); } else { if (b) { console.log(b); } else { console.log(c); } } ``` ## 运算符优先级 多数情况下,JavaScript 的数学运算符与真正的数学一样,按照真正的数学的顺序进行运算。但 JavaScript 的语句并不是数学公式,而且语句中通常会充斥着各种各样的运算符。他们也是有各自的运算优先级的。 ### 短路 and 与 or 操作拥有“短路”(short circuiting)的特性,别急,我们还没走错片场。在 JavaScript 中,他们分别是`&&`和`||`运算符。 短路的意思是,当左边的操作数能够得出结果时,就可以忽略右边的操作数。 对于`&&`来说,左边的操作数为假值时,则就没有必要再判断右边的值。同理,对于`||`来说,左边的操作数为真值时,也没有必要再判断右边的操作数。 ### 关联 JavaScript 的代码是从上往下执行的,而单条语句是从左往右执行的。但在特定的运算符下,某些语句需要先求得右边的值。 例如: ```js true ? false : true ? true : true; ``` 这段代码乍一看很令人头疼,但它确实说明运算符的关联最容易理解的例子。在这里,第二个三目运算符的结果会影响第一个三目运算符,所以这里要执行右关联,也就是需要先求得第二个三目运算符的结果。 > 虽然是右关联,但它的运算顺序还是从左到右。`a ? b : c;`还是会先执行 a 然后 b c。 ## 自动分号 有时 JavaScript 会自动为代码补上分号,即分号自动插入机制(Automatic Semicolon Insertion,ASI)。 ASI只会在换行符处起作用,而不会在代码中间插入分号。 常见的情况是在 return 后自动加上,这样 return 后换行的代码就会不生效。return 语句可以跨越多行,但是其后必须右换行符以外的代码。 ```js const foo = () => { return ( 1 + 2 ); }; ``` ### 纠错机制 是否应该完全依赖 ASI 来编码,这是 JavaScript 社区中最具争议性的话题之一。 支持的一方认为 ASI 大有裨益,能省略掉那些不必要的`;`,让代码更简洁。此外 ASI 让许多`;`变得可有可无,因此只要代码没问题,有没有`;`都一样。 反方则认为 ASI 机制问题太多,对于缺乏经验的初学者尤其如此,因为自动插入`;`会无意改变代码的逻辑。有些人还认为依赖 linter 这些工具找出错误,而不是依赖 JavaScript 引擎。 事实上,现在多数`;`可以通过 ESlint 这样的 linter 工具来在格式化代码是插入。这种方式相比较用引擎来找出错误好很多,而且它不会在运行时改变代码原有的意思。 ## 小结 JavaScript 语法规则中的许多细节需要我们多花时间和精力来了解。从长远来看,这有助于更深入地掌握这门语言。