JavaScript 的语法相当简单明了。本章将介绍需要注意的事项。
本节将简要介绍 JavaScript 的语法。
以下是五种基本值类型:
以下是一些基本语法的示例:
// 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`
// A conditional statement
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
多行注释使用 /* */
,可以扩展到任意范围的文本。它们不能嵌套。以下是两个示例:
/* temporarily disabled
processNext(queue);
*/
function
(
a
/* int */
,
b
/* str */
)
{
}
本节将介绍 JavaScript 中一个重要的语法区别:表达式和语句之间的区别。
粗略地说,语句执行一个动作。循环和 if
语句是语句的示例。程序基本上就是一系列语句。[8]
在 JavaScript 需要语句的地方,你也可以编写表达式。这样的语句称为表达式语句。反之则不然:你不能在 JavaScript 需要表达式的地方编写语句。例如,if
语句不能作为函数的参数。
如果我们看一下两个语法类别中相似的成员:if
语句和条件运算符(一个表达式),那么语句和表达式之间的区别就会更加清晰。
以下是一个if
语句的示例:
var
salutation
;
if
(
male
)
{
salutation
=
'Mr.'
;
}
else
{
salutation
=
'Mrs.'
;
}
有一种类似的表达式,称为条件运算符。前面的语句等效于以下代码:
var
salutation
=
(
male
?
'Mr.'
:
'Mrs.'
);
等号和分号之间的代码是一个表达式。括号不是必需的,但我发现如果将条件运算符放在括号中,则更容易阅读。
为了防止在解析过程中出现歧义,JavaScript 不允许你将对象字面量和函数表达式用作语句。也就是说,表达式语句不能以以下内容开头
function
如果表达式以这两个标记中的任何一个开头,则它只能出现在表达式上下文中。你可以通过例如将表达式放在括号中来满足该要求。接下来,我们将看两个需要这样做的例子。
eval
在语句上下文中解析其参数。如果你希望 eval
返回一个对象,则必须将对象字面量放在括号中:
> eval('{ foo: 123 }') 123 > eval('({ foo: 123 })') { foo: 123 }
以下代码是一个立即调用函数表达式 (IIFE),它的主体会立即执行(你将在通过 IIFE 引入新的作用域 中了解 IIFE 的用途)
> (function () { return 'abc' }()) 'abc'
如果省略括号,则会出现语法错误,因为 JavaScript 会将其视为函数声明,而函数声明不能是匿名的
> function () { return 'abc' }() SyntaxError: function statement requires a name
如果添加名称,也会出现语法错误,因为函数声明不能立即调用
> function foo() { return 'abc' }() SyntaxError: Unexpected token )
函数声明后面的任何内容都必须是合法的语句,而 ()
不是。
if
(
obj
!==
null
)
obj
.
foo
();
while
(
x
>
0
)
x
--
;
但是,任何语句都可以始终替换为代码块,即花括号中包含零个或多个语句。因此,你也可以这样写:
if
(
obj
!==
null
)
{
obj
.
foo
();
}
while
(
x
>
0
)
{
x
--
;
}
我更喜欢后一种形式的控制流语句。将其标准化意味着单语句主体和多语句主体之间没有区别。因此,你的代码看起来更加一致,并且在一条语句和多条语句之间切换更容易。
在本节中,我们将研究分号在 JavaScript 中的用法。基本规则是:
分号在 JavaScript 中是可选的。缺少的分号会通过所谓的自动分号插入 (ASI) 添加;请参阅自动分号插入)。但是,该功能并不总是按预期工作,这就是为什么你应该始终包含分号的原因。
for
、while
(但不包括 do-while
)if
、switch
、try
以下是 while
与 do-while
的示例
while
(
a
>
0
)
{
a
--
;
}
// no semicolon
do
{
a
--
;
}
while
(
a
>
0
);
以下是函数声明与函数表达式的示例。后者后面跟着一个分号,因为它出现在 var
声明(确实 以分号结尾)的内部
function
foo
()
{
// ...
}
// no semicolon
var
foo
=
function
()
{
// ...
};
如果在代码块后添加分号,则不会出现语法错误,因为它被视为空语句(请参阅下一节)。
这就是你需要了解的有关分号的大部分内容。如果你始终添加分号,则可能无需阅读本节的其余部分。
单独一个分号是一个空语句,什么也不做。空语句可以出现在任何需要语句的地方。当需要语句但不需要执行任何操作时,它们很有用。在这种情况下,通常也允许使用代码块。例如,以下两条语句是等效的:
while
(
processNextItem
()
>
0
);
while
(
processNextItem
()
>
0
)
{}
假设函数 processNextItem
返回剩余项目的数量。以下由三个空语句组成的程序在语法上也是正确的
;;;
自动分号插入 (ASI) 的目标是使分号在行尾成为可选的。自动分号插入一词所引发的图像是 JavaScript 解析器为你插入分号(在内部,处理方式通常不同)。
换句话说,ASI 帮助解析器确定语句何时结束。通常,它以分号结尾。ASI 规定,如果出现以下情况,语句也会结束
if
(
a
<
0
)
a
=
0
console
.
log
(
a
)
标记 console
在 0
之后是非法的,会触发 ASI
if
(
a
<
0
)
a
=
0
;
console
.
log
(
a
);
function
add
(
a
,
b
)
{
return
a
+
b
}
ASI 创建了前面代码的语法正确版本
function
add
(
a
,
b
)
{
return
a
+
b
;
}
如果关键字 return
后面有一个行终止符,也会触发 ASI。例如:
// Don't do this
return
{
name
:
'John'
};
ASI 将前面的代码转换为
return
;
{
name
:
'John'
};
这是一个空的 return 语句,后面跟着一个代码块,代码块中带有标签 name
,标签前面是表达式语句 'John'
。代码块后面是一个空语句。
有时,新行中的语句以一个标记开头,该标记允许作为前一条语句的延续。在这种情况下,即使看起来应该触发 ASI,也不会触发它。例如:
func
()
[
'ul'
,
'ol'
].
forEach
(
function
(
t
)
{
handleTag
(
t
)
})
第二行中的方括号被解释为对 func()
返回的结果进行索引。方括号内的逗号被解释为逗号运算符(在本例中返回 'ol'
;请参阅逗号运算符)。因此,JavaScript 将前面的代码视为:
func
()[
'ol'
].
forEach
(
function
(
t
)
{
handleTag
(
t
)
});
标识符用于命名事物,并在 JavaScript 中以各种语法角色出现。例如,变量的名称和未加引号的属性键必须是有效的标识符。标识符区分大小写。
标识符的第一个字符可以是以下任意一个
$
)_
)后续字符可以是
合法标识符示例
var
ε
=
0.0001
;
var
строка
=
''
;
var
_tmp
;
var
$foo2
;
尽管这使你能够在 JavaScript 代码中使用各种人类语言,但我建议坚持使用英语,包括标识符和注释。这可以确保你的代码能够被尽可能多的人理解,考虑到如今代码可以在国际范围内传播,这一点非常重要。
以下标识符是保留字——它们是语法的一部分,不能用作变量名(包括函数名和参数名):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
以下三个标识符不是保留字,但您应该将它们视为保留字
|
|
|
最后,您还应该避免使用标准全局变量的名称(请参阅第 23 章)。您可以将它们用于局部变量而不会破坏任何内容,但您的代码仍然会变得混乱。
请注意,您可以使用保留字作为未加引号的属性键(从 ECMAScript 5 开始)
> var obj = { function: 'abc' }; > obj.function 'abc'
您可以在 Mathias Bynens 的博客文章“有效的 JavaScript 变量名”中查找标识符的精确规则。
使用方法调用时,区分浮点和方法调用点非常重要。因此,您不能编写1.toString()
;您必须使用以下替代方法之一:
1
..
toString
()
1
.
toString
()
// space before dot
(
1
).
toString
()
1.0
.
toString
()
ECMAScript 5 有一种严格模式,它可以生成更简洁的 JavaScript,减少不安全的功能,提供更多警告,并使行为更加合乎逻辑。正常(非严格)模式有时称为“草率模式”。
您可以在 JavaScript 文件或<script>
元素内部输入以下行来开启严格模式:
'use strict'
;
请注意,不支持 ECMAScript 5 的 JavaScript 引擎将忽略前面的语句,因为以这种方式编写字符串(作为表达式语句;请参阅语句)通常不会执行任何操作。
您还可以为每个函数开启严格模式。为此,请像这样编写您的函数
function
foo
()
{
'use strict'
;
...
}
当您使用的是旧版代码库,并且在任何地方开启严格模式都可能会破坏代码时,这非常方便。
总的来说,严格模式启用的更改都是为了改进代码。因此,强烈建议您将其用于您编写的代码——只需在文件开头将其打开即可。但是,有两点需要注意:
以下部分将更详细地解释严格模式的功能。您通常不需要了解它们,因为您通常会收到更多关于您不应该做的事情的警告。
所有变量都必须在严格模式下显式声明。这有助于防止拼错。在草率模式下,分配给未声明的变量会创建一个全局变量:
function
sloppyFunc
()
{
sloppyVar
=
123
;
}
sloppyFunc
();
// creates global variable `sloppyVar`
console
.
log
(
sloppyVar
);
// 123
在严格模式下,分配给未声明的变量会引发异常
function
strictFunc
()
{
'use strict'
;
strictVar
=
123
;
}
strictFunc
();
// ReferenceError: strictVar is not defined
严格模式限制了与函数相关的功能。
在严格模式下,所有函数都必须在作用域的顶层声明(全局作用域或直接在函数内部)。这意味着您不能将函数声明放在块内。如果您这样做,您将收到一个描述性的SyntaxError
。例如,V8 会告诉您:“在严格模式代码中,函数只能在顶层或直接在另一个函数内部声明”:
function
strictFunc
()
{
'use strict'
;
if
(
true
)
{
// SyntaxError:
function
nested
()
{
}
}
}
这无论如何都没有用,因为函数是在周围函数的作用域中创建的,而不是在块的“内部”。
如果要解决此限制,可以通过变量声明和函数表达式在块内创建函数
function
strictFunc
()
{
'use strict'
;
if
(
true
)
{
// OK:
var
nested
=
function
()
{
};
}
}
函数参数的规则不那么宽松:禁止两次使用相同的参数名,以及与参数同名的局部变量。
arguments
对象在严格模式下更简单:属性arguments.callee
和arguments.caller
已被删除,您不能分配给变量arguments
,并且arguments
不会跟踪参数的变化(如果参数发生变化,则相应的数组元素不会随之变化)。arguments 已弃用的功能解释了详细信息。
在草率模式下,非方法函数中this
的值是全局对象(浏览器中的window
;请参阅全局对象):
function
sloppyFunc
()
{
console
.
log
(
this
===
window
);
// true
}
在严格模式下,它是undefined
function
strictFunc
()
{
'use strict'
;
console
.
log
(
this
===
undefined
);
// true
}
这对构造函数很有用。例如,以下构造函数Point
处于严格模式:
function
Point
(
x
,
y
)
{
'use strict'
;
this
.
x
=
x
;
this
.
y
=
y
;
}
由于严格模式,当您不小心忘记new
并将其作为函数调用时,您会收到警告
> var pt = Point(3, 1); TypeError: Cannot set property 'x' of undefined
在草率模式下,您不会收到警告,并且会创建全局变量x
和y
。有关详细信息,请参阅实现构造函数的技巧。
在严格模式下,非法操作属性会引发异常。例如,尝试设置只读属性的值会引发异常,尝试删除不可配置属性也会引发异常。以下是前者的示例:
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: Cannot assign to
// read-only property 'length'
}
在草率模式下,您可以像这样删除全局变量foo
:
delete
foo
在严格模式下,只要您尝试删除非限定标识符,就会收到语法错误。您仍然可以像这样删除全局变量
delete
window
.
foo
;
// browsers
delete
global
.
foo
;
// Node.js
delete
this
.
foo
;
// everywhere (in global scope)
在严格模式下,eval()
函数变得不那么奇怪:在计算的字符串中声明的变量不再添加到eval()
周围的作用域中。有关详细信息,请参阅使用 eval() 计算代码。