[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 ] 为了简单起见,我假装声明是语句。*
- 本书简介
- 前言
- 关于这本书你需要知道些什么
- 如何阅读本书
- 目录
- I. JavaScript的快速入门
- 第1章 基础的JavaScript
- II. 背景知识
- 第2章 为什么选择JavaScript?
- 第3章 JavaScript的性质
- 第4章 JavaScript是如何创造出来的
- 第5章 标准化:ECMAScript
- 第6章 JavaScript的历史里程碑
- III. 深入JavaScript
- 第7章 JavaScript语法
- 第8章 值
- 第9章 运算符
- 第10章 布尔值
- 第11章 数字
- 第12章 字符串
- 第13章 语句
- 第14章 异常捕获
- 第15章 函数
- 第16章 变量:作用域、环境和闭包
- 第17章 对象和继承
- 第18章 数组
- 第19章 正则表达式
- 第20章 Date
- 第21章 Math
- 第22章 JSON
- 第23章 标准全局变量
- 第24章 编码和JavaScript
- 第25章 ECMAScript 5中的新功能
- IV. 技巧、工具和类库
- 第26章 元代码样式指南
- 第27章 调试的语言机制
- 第28章 子类化内置构造函数
- 第29章 JSDoc:生成API文档
- 第30章 类库
- 第31章 模块系统和包管理器
- 第32章 其他工具
- 第33章 接下来该做什么
- 著作权