企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
[TOC] # 第7章 JavaScript语法 JavaScript的语法相当简单。本章介绍要注意的事项。 # 语法概述 本节将简要介绍JavaScript的语法。 以下是五种基本值类型: **布尔:** ```js true false ``` **数字:** ```js 1023 7.851 ``` **字符串:** ```js 'hello' "hello" ``` **简单对象(对象字面量):** ```js { firstName: 'Jane', lastName: 'Doe' } ``` **数组:** ```js [ 'apple', 'banana', 'cherry' ] ``` 以下是基本语法的几个示例: ```js // Two slashes start single-linecomments var x; // declaring a variable x = 3 + y; // assigning a value to the variable `x` foo(x, y); // calling function `foo` with parameters `x` and `y` obj.bar(3); // calling method `bar` of object `obj` // 一个条件语句 if (x === 0) { // Is `x` equal to zero? x = 123; } // Defining function `baz` with parameters `a` and `b` function baz(a, b) { return a + b; } ``` 注意两种不同的用途 等号: * 单个等号(=)用于将值分配给变量。 * 三等号(===)用于比较两个值(参见“ 等式运算符”)。 # 注释 有两种注释: 通过`//`注释该整行。以下是一个例子: ``` var a = 0; // init ``` 多行注释`/* */`可以扩展到任意范围的文本。它们不能嵌套。这里有两个例子: ```js /* temporarily disabled processNext(queue); */ function (a /* int */, b /* str */) { } ``` # 表达式和语句 本节将介绍JavaScript中的重要句法区别:表达式和语句之间的区别。 ## 表达式 一个表达式可以产生一个值,也可以在希望的地方被重新改写,例如,如在函数调用中的参数或在赋值的右侧。 以下每行都包含一个表达式: ```js myvar 3 + x myfunc('a', 'b') ``` ## 语句([Statements](https://en.wikipedia.org/wiki/Statement_(computer_science))) 概略来说,一个语句执行一个动作。循环和`if`语句是语句的示例。程序基本上是一系列语句的组合。[ 8 ] JavaScript在任何位置需要一个语句,您都可以编写一个表达式。这样的语句被称为表达式语句。反之则不成立:如果JavaScript在该位置期望的是一个表达式,您不能编写出一个语句。例如,`if`语句不能成为函数的参数 ### 条件语句与条件表达式 如果我们观察这两种相似的句法范畴的成员,语句和表达式之间的区别将变得更清楚: if语句和条件运算符(一个表达式)。 以下是一个`if`语句的示例: ```js var salutation; if (male) { salutation = 'Mr.'; } else { salutation = 'Mrs.'; } ``` 有一个类似的 的表达式,条件运算符。前面的语句相当于以下代码: ```js var salutation = (male ? 'Mr.' : 'Mrs.'); ``` 等号和分号之间的代码是一个表达式。括号不是必需的,但是如果我把它放在括号里,我发现条件运算符更容易阅读。 ### 使用模糊表达式作为语句 两种表达式看起来像语句 - 他们的句法范畴是模糊的: * 对象字面量(表达式)看起来像块(语句): ```js { foo: bar(3, 5) } ``` 前面的结构要么是一个对象字面量(细节:[对象字面量](###)),要么是标签后跟`foo:`标签,后跟调用了`bar(3, 5)`的函数。 * 命名函数表达式看起来就像函数声明(语句): ```js function foo() { } //函数声明的典型格式 var f = function foo(){console.log(typeof foo);}; //命名函数表达式 ``` 前面的结构是一个命名函数表达式或一个函数声明。前者产生一个函数,后者创建一个变量并为其分配一个函数(两种函数定义的细节:[函数定义](第15章))。 为了在解析过程中避免歧义,JavaScript不允许将对象字面量和函数表达式用作语句。也就是说,表达式语句不能以下面这两个符合开始: * 一个花括号 * 关键字 `function` 如果一个表达式从这两个标记开始,它只能出现在一个表达式上下文中。例如,在表达式周围放上括号,来遵守这个要求。接下来,我们将需要看看两个例子。 ### 通过eval()执行解析一个对象字面量 `eval`在语句上下文中解析它的参数。如果你想让`eval`返回对象的话你必须在对象字面量上加上括号: ```js > eval('{foo:123}') 123 > eval('({foo:123})') {foo:123} ``` ### 立即调用的函数表达式---IIFE 下列代码是一个立即调用的函数表达式(IIFE),它是一个身体立即执行的函数(您将了解IIFE主要用途 [通过IIFE创建新作用域](第16章)): ```js > (function () { return 'abc' }()) 'abc' ``` 如果你省略了括号,你会得到一个语法错误,因为JavaScript对于函数声明,不能使用匿名声明: ```js > function () { return 'abc' }() SyntaxError: function statement requires a name ``` 如果你添加一个名字,你也会得到一个语法错误,因为函数声明不能立即被调用: ```js > function foo() { return 'abc' }() SyntaxError: Unexpected token ) ``` 函数声明中的任何一个都必须是合法的声明,`()`却不是。 ## 控制流语句和块 对于控制流语句,主体是一个单独的语句。这里有两个例子 ``` if (obj !== null) obj.foo(); while (x > 0) x--; ``` 然而,任何语句都可以被一个包含零个或多个语句的大括号替换。因此,你也可以写 ```js if (obj !== null) { obj.foo(); } while (x > 0) { x--; } ``` 我更喜欢后一种形式的控制流语句。对它进行标准化意味着单语句体和多语句体之间没有区别。因此,您的代码看起来更一致,在一个语句和多个语句之间进行切换更容易。 # 使用分号的规则 在这个部分, 我们检查如何在JavaScript中使用分号。基本规则是: * 通常,语句以分号终止。 * 异常是以块结尾的语句。 分号在JavaScript中是可选的。缺失分号是通过所谓的自动分号插入添加的(ASI;参见[自动分号插入](第7章))添加缺少的分号。但是,该功能并不总是按预期方式工作,因此您应该始终包含分号。 ## 以块结尾的语句后没有分号 以下语句如果以块结尾,则不会以分号结尾: * 循环:for,while(但不是do-while) * 分支:if,switch,try * 函数声明(但不是函数表达式) 下面是一个例子while对比do-while: ```js while (a > 0) { a--; } // no semicolon do { a--; } while (a > 0); ``` 这里是函数声明与函数表达式的一个例子。后者随后是分号,因为它出现在`var`声明中(以分号结尾):: ```js function foo() { // ... } // no semicolon var foo = function () { // ... }; ``` | 注意 | | --- | | 如果在块之后添加分号,不会收到语法错误,因为它被认为是空的语句(请参阅下一节)。 | | 提示 | | --- | | 这是你需要知道的关于分号的大部分内容。如果您总是添加分号,则可以不阅读本节的其余部分的。 | ## 空语句 分号本身是空的语句,什么也不做。空语句可以出现在任何语句的任何地方。它们在需要声明但不需要的情况下很有用。在这种情况下,通常也允许块。例如,以下两个语句是等价的: ```js while (processNextItem() > 0); while (processNextItem() > 0) {} ``` 函数`processNextItem`被假定为返回剩余条目的数量。 下面的程序由三个空语句组成,在语法上也是正确的: ```js ;;; ``` ## 自动分号(ASI automatic semicolon insertion ) ECMAScript 提供[自动分号插入(ASI)机制](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-automatic-semicolon-insertion),自动分号插入(ASI)的目标是在一行的末尾让分号成为可选的。 自动分号插入是 JavaScript 解析器为您插入分号(内部通常以不同的方式处理)。 换句话说,ASI 帮助解析器确定语句何时结束。通常,它以分号结尾。ASI规定,出现下面的情况,语句(statement)也会结束: * 一行结束符(例如,换行),后面跟着一个非法的标记。 * 遇到关闭的大括号。 * 到达文件的结尾。 ### 示例:通过非法标记进行 ASI 以下代码包含一个行终止符,后跟非法标记: ```js if (a < 0) a = 0 console.log(a) ``` 0之后的标记`console`是非法的,并且触发ASI: ```js if (a < 0) a = 0; console.log(a); ``` ### 示例:通过关闭大括号进行 ASI 在下面的代码中,大括号内的语句没有被分号终止: ```js function add(a,b) { return a+b } ``` ASI创建了前面代码的语法正确的版本: ```js function add(a,b) { return a+b; } ``` ### 陷阱:ASI 可以意外地分解语句 如果关键字后面有一个行终止符,也会触发 ASI 。例如:`return` ```js // Don't do this return { name: "John" }; ``` ASI将前述转为: ``` return; { name: "John" }; ``` 这返回了`undefined`,后面是一个包含了`name`标记的表达式语句为`"John"`的块。 这个问题通过删除`return`和对象之间的换行来解决: ~~~ return { name: "John" }; ~~~ 我的建议是研究[自动分号插入的确切方式](http://www.bradoncode.com/blog/2015/08/26/javascript-semi-colon-insertion/),以避免这种情况。 当然,永远不要在`return`和返回的表达式之间放置换行符。 ### 陷阱:ASI可能会意外地没有被触发 有时,一条新行中的语句以一个标记作为延续,作为前一个语句的延续。那么ASI不会被触发,即使它似乎应该被触发。例如: ```js func() [ 'ul', 'ol' ].forEach(function (t) { handleTag(t) }) ``` 第二行中的方括号被解释为由`func()`返回的结果的索引。括号内的逗号被解释为逗号运算符(在本例中返回`'ol'`;请参见[逗号操作符](第9章))。因此,JavaScript将前面的代码视为: ```js func()['ol'].forEach(function (t) { handleTag(t) }); ``` ## 合法标识符 标识符用于命名事物,并出现在JavaScript中的各种语法角色中。例如,变量的名称和未引用的属性键必须是有效的标识符。标识符是大小写敏感的。 标识符的开头字符是以下之一: * 任何Unicode字母,包括拉丁字母,如D,希腊字母如λ,和西里尔字母,如Д * 美元符号($) * 下划线(_) 后续字符是: * 任何合法的开头(第一个)字符 * Unicode类别中的任何Unicode数字“十进制数(Nd)”; 这包括欧洲数字如7和印度数字如3 * 各种其他Unicode标记和标点符号 合法标识符示例: ```js var ε = 0.0001; var строка = ''; var _tmp; var $foo2; ``` 尽管这使您能够在JavaScript代码中使用多种人类语言,但我还是建议您使用英语,以识别标识符和注释。这确保了你的代码可以被最大的人群理解,这一点很重要,因为现在有多少代码可以在国际上传播。 以下标识符是保留字 - 它们是语法的一部分,不能用作变量名(包括函数名和参数名): <table> <tr> <td>arguments</td> <td>break</td> <td> case </td> <td>catch </td> </tr> <tr> <td>class</td> <td> const </td> <td> continue </td> <td> debugger </td> </tr> <tr> <td>default</td> <td> delete</td> <td> do </td> <td> else </td> </tr> <tr> <td> enum</td> <td> export</td> <td> extends</td> <td> false</td> </tr> <tr> <td>finally</td> <td> for</td> <td> function </td> <td> if </td> </tr> <tr> <td> implements</td> <td> import</td> <td> in</td> <td> instanceof</td> </tr> <tr> <td>interface</td> <td> let</td> <td> new</td> <td> null </td> </tr> <tr> <td> package</td> <td> private</td> <td> protected</td> <td> public</td> </tr> <tr> <td>interface</td> <td> let</td> <td> new</td> <td> null </td> </tr> <tr> <td> return</td> <td> static</td> <td> super</td> <td> switch</td> </tr> <tr> <td>this</td> <td> throw</td> <td> true</td> <td> try </td> </tr> <tr> <td> typeof</td> <td> var</td> <td> void</td> <td> while</td> </tr> </table> 以下三个标识符不是保留字,但您应该将它们视为: <table> <tr> <td> Infinity</td> <td> NaN</td> <td> undefined</td> </tr> </table> 最后,你也应该远离标准全局变量的名称(见[第23章](###))。您可以将它们用于局部变量,而不会破坏任何内容,但是您的代码仍然会变得混乱。 请注意,您可以使用保留字作为未引用的属性键(从ECMAScript 5开始): ```js > var obj = { function: 'abc' }; > obj.function 'abc' ``` 您可以在Mathias Bynens的博客文章“[有效的JavaScript变量名称](http://mathiasbynens.be/notes/javascript-identifiers)”中查找标识符的准确规则。 ## 数字字面量的方法调用 对于方法调用,区分浮点点和方法调用点是很重要的。因此,您不能编写`1.toString();`您必须使用下列选项之一: ```js 1..toString() 1 .toString() // 点之前有空格 (1).toString() 1.0.toString() ``` ## 严格模式 ECMAScript 5具有严格的模式,导致更清晰的JavaScript,具有更少的不安全功能,更多的警告和更多的逻辑行为。正常(非限制)模式有时称为“草率模式”。 ### 启用严格模式 您可以通过在js文件中,或者`<script>`标签里面,首行输入以下代码来开启严格模式: ```js 'use strict'; ``` 请注意,不支持ECMAScript 5的JavaScript引擎将简单地忽略前面的语句,因为以这种方式写入字符串(作为表达式语句;参见[语句](###))通常不执行任何操作。 每个功能也可以打开严格的模式。为此,请写下你的函数: ```js function foo() { 'use strict'; ... } ``` 当您使用遗留代码库时,这是很方便的,在任何地方切换严格模式可能会破坏一些东西 ### 严格模式:建议与注意事项 一般来说,通过严格模式启用的更改都是更好的。因此,强烈建议您将其用于写入的新代码 - 只需在文件开头打开即可。但是,有两个注意事项: * **为现有代码启用严格模式可能会破坏它** 该代码可能依赖于不再可用的功能,或者它可能依赖于在草率模式下不同于严格模式的行为。不要忘记,您可以选择将启用严格模式的函数添加到处于草率模式的文件中。 * **小心包装** 当您连接和/或压缩minify文件时,必须注意严格模式不会被关闭,因为它应该被打开,反之亦然。两者都可以打破代码。 以下部分将详细说明严格模式特性。你通常不需要知道他们,因为你会对一些你不应该做的事情有更多的警告。 ### 严格模式下必须声明变量 所有变量都必须 以严格模式明确声明。这有助于防止打字错误。在草率模式下,分配给未声明的变量将创建一个全局变量: ```js function sloppyFunc() { sloppyVar = 123; } sloppyFunc(); // 隐式的创建了全局变量 `sloppyVar` console.log(sloppyVar); // 123 ``` 在严格模式下,分配给未声明的变量会引发异常: ```js function strictFunc() { 'use strict'; strictVar = 123; } strictFunc(); // ReferenceError: strictVar is not defined ``` ### 严格模式下的函数 严格模式限制函数相关功能。 #### 函数必须声明在顶层的范围 在严格模式下,所有函数必须在一个范围的顶层(全局范围或函数内直接声明)中声明。这意味着您不能在函数块中放置函数声明。如果你这样做,你会得到一个描述性的SyntaxError。例如,V8告诉你:“在严格模式代码中,函数只能在顶层声明或者立即在另一个函数中调用:” ```js function strictFunc() { 'use strict'; if (true) { // SyntaxError: function nested() { } } } ``` 这是一些没有用的东西,因为函数是在周围函数的范围内创建的,而不是块内的。 如果要解决此限制,可以通过变量声明和函数表达式在块内创建一个函数: ```js function strictFunc() { 'use strict'; if (true) { // OK: var nested = function () { }; } } ``` #### 更严格的函数参数规则 函数参数的规则不允许: 禁止使用两次相同的参数名称,因为具有相同的名称的局部变量作为参数。 #### 参数对象具有较少的属性 `arguments`对象是在严格模式会更简单:属性`arguments.callee`和`arguments.caller`已被去除,则不能分配给`arguments`,并且`arguments`不跟踪参数变化(如果一个参数改变,相应的数组元素不会随之改变)。[arguments的弃用功能](#第15章)查看更多。 #### 这在非方法函数中是未定义的 在草率模式中,在非方法函数的`this`值是全局对象(在浏览器中是`window`;参见[全局对象](#第16章)): ```js function sloppyFunc() { console.log(this === window); // true } ``` 在严格的模式下,它是`undefined`: ```js function strictFunc() { 'use strict'; console.log(this === undefined); // true } ``` 这对于构造函数很有用。例如,以下构造函数,`Point`处于严格模式: ```js function Point(x, y) { 'use strict'; this.x = x; this.y = y; } ``` 由于严格的模式,当您意外忘记`new`并将其当作函数调用,您会收到警告: ```js var pt = Point(3, 1); TypeError: Cannot set property 'x' of undefined ``` 在草率模式下,你没有得到警告,全局变量`x`和`y`被创建。有关详细信息,请参阅[实现构造函数的小贴士](#第17章)。 ### 严格模式中,设置或者删除不可改变的属性会抛出异常 对属性的非法操作会在严格模式下抛出异常。例如,试图设置只读属性的值会抛出一个异常,就像试图删除一个不可配置的属性一样。这是前一个例子: ```js var str = 'abc'; function sloppyFunc() { str.length = 7; // no effect, silent failure console.log(str.length); // 3 } function strictFunc() { 'use strict'; str.length = 7; // TypeError: 不能设置只读属性length } ``` ### 严格模式中的不合格标识符不能删除 在草率模式下,您可以删除如下所示的全局变量foo: ``` delete foo ``` 在严格模式下,只要您尝试删除不合格的标识符,就会收到语法错误。您仍然可以删除全局变量: ```js delete window.foo; // browsers delete global.foo; // Node.js delete this.foo; // everywhere (in global scope) ``` ### 严格模式中,`eval()`更加简洁 在严格模式下,`eval()`功能变得不那么古怪:在被执行的字符串中声明的变量不会被添加到eval()周围的作用域。有关详细信息,请参阅[使用eval()执行代码](第23章)。 ### 严格模式中禁用的特性 在严格模式下禁用的两个JavaScript特性: * 该`with`声明是不允许的(参见[With with Statement](第13章))。您在编译时会收到语法错误(加载代码时)。 * 不再存在八进制数字:在草率的模式下,一个带着前导零`0`的整数被解释为八进制(基数8)。例如: ```js 010 === 8 true ``` 在严格模式下,如果您使用这种字面值,则会收到一个语法错误: ```js function f() { 'use strict'; return 010 } SyntaxError: Octal literals are not allowed in strict mode. ``` *[ 8 ] 为了简单起见,我假装声明是语句。*