本章涵盖 JavaScript 的语句:变量声明、循环、条件语句等。
var
用于 声明 变量,创建变量并使您能够使用它。 等号运算符 (=
) 用于为其赋值:
var
foo
;
foo
=
'abc'
;
var
还允许您将前面的两个语句合并为一个语句
var
foo
=
'abc'
;
最后,您还可以将多个 var
语句合并为一个语句
var
x
,
y
=
123
,
z
;
在 第 16 章 中阅读有关变量工作原理的更多信息。
复合语句 例如循环和条件语句 嵌入了一个或多个“主体”——例如,while
循环:
while
(
«
condition
»
)
«
statement
»
对于主体 «statement»
,您可以选择。您可以使用单个语句
while
(
x
>=
0
)
x
--
;
或者您可以使用 代码块(它算作单个语句):
while
(
x
>
0
)
{
x
--
;
}
本节探讨 JavaScript 的循环语句。
break ⟦«label»⟧
continue ⟦«label»⟧
标签是一个标识符,后跟一个冒号。在循环前面,标签允许您即使从嵌套在其中的循环中也可以中断或继续该循环。 在代码块前面,您可以跳出该代码块。在这两种情况下,标签的名称都将成为 break
或 continue
的参数。以下是从代码块中跳出的示例:
function
findEvenNumber
(
arr
)
{
loop
:
{
// label
for
(
var
i
=
0
;
i
<
arr
.
length
;
i
++
)
{
var
elem
=
arr
[
i
];
if
((
elem
%
2
)
===
0
)
{
console
.
log
(
'Found: '
+
elem
);
break
loop
;
}
}
console
.
log
(
'No even number found.'
);
}
console
.
log
(
'DONE'
);
}
while
(
«
condition
»
)
«
statement
»
只要 condition
成立,就执行 statement
。如果 condition
始终为 true
,则会得到一个无限循环
while
(
true
)
{
...
}
在以下示例中,我们删除数组的所有元素并将它们记录到控制台
var
arr
=
[
'a'
,
'b'
,
'c'
];
while
(
arr
.
length
>
0
)
{
console
.
log
(
arr
.
shift
());
}
以下是输出
a b c
do-while
循环:
do
«
statement
»
while
(
«
condition
»
);
至少执行一次 statement
,然后只要 condition
成立就执行。例如
var
line
;
do
{
line
=
prompt
(
'Enter a number:'
);
}
while
(
!
/^[0-9]+$/
.
test
(
line
));
在 for
循环 中:
for
(
⟦«
init
»⟧
;
⟦«
condition
»⟧
;
⟦«
post_iteration
»⟧
)
«
statement
»
init
在循环之前执行一次,只要 condition
为 true
,循环就会继续。您可以在 init
中使用 var
来声明变量,但这些变量的作用域始终是完整的外部函数。 post_iteration
在循环的每次迭代之后执行。考虑到所有这些,前面的循环等效于以下 while
循环
«
init
»
;
while
(
«
condition
»
)
{
«
statement
»
«
post_iteration
»
;
}
以下示例是 迭代数组的传统方式(其他可能性在 最佳实践:迭代数组 中进行了描述)
var
arr
=
[
'a'
,
'b'
,
'c'
];
for
(
var
i
=
0
;
i
<
arr
.
length
;
i
++
)
{
console
.
log
(
arr
[
i
]);
}
如果省略头部的所有部分,则 for
循环将变为无限循环
for
(;;)
{
...
}
for-in
循环:
for
(
«
variable
»
in
«
object
»
)
«
statement
»
迭代 object
的所有属性键,包括继承的属性键。 但是,标记为不可枚举的属性将被忽略(请参阅 属性特性和属性描述符)。以下规则适用于 for-in
循环
var
来声明变量,但这些变量的作用域始终是完整的外部函数。不要使用 for-in
来 迭代数组。首先,它迭代索引,而不是值:
> var arr = [ 'a', 'b', 'c' ]; > for (var key in arr) { console.log(key); } 0 1 2
其次,它还迭代所有(非索引)属性键。以下示例说明了在向数组添加属性 foo
时会发生什么
> var arr = [ 'a', 'b', 'c' ]; > arr.foo = true; > for (var key in arr) { console.log(key); } 0 1 2 foo
因此,最好使用普通的 for
循环或数组方法 forEach()
(请参阅 最佳实践:迭代数组)。
for-in
循环迭代 所有(可枚举的)属性,包括继承的属性。这可能不是您想要的。让我们使用以下构造函数来说明这个问题:
function
Person
(
name
)
{
this
.
name
=
name
;
}
Person
.
prototype
.
describe
=
function
()
{
return
'Name: '
+
this
.
name
;
};
Person
的实例从 Person.prototype
继承属性 describe
,for-in
可以看到该属性
var
person
=
new
Person
(
'Jane'
);
for
(
var
key
in
person
)
{
console
.
log
(
key
);
}
以下是输出
name describe
通常,使用 for-in
的最佳方法是通过 hasOwnProperty()
跳过继承的属性
for
(
var
key
in
person
)
{
if
(
person
.
hasOwnProperty
(
key
))
{
console
.
log
(
key
);
}
}
以下是输出
name
最后一个需要注意的是:person
可能具有属性 hasOwnProperty
,这将阻止检查工作。为了安全起见,您必须直接引用泛型方法(请参阅 泛型方法:从原型中借用方法)Object.prototype.hasOwnProperty
for
(
var
key
in
person
)
{
if
(
Object
.
prototype
.
hasOwnProperty
.
call
(
person
,
key
))
{
console
.
log
(
key
);
}
}
还有其他更方便的方法可以迭代属性键,这些方法在 最佳实践:迭代自有属性 中进行了描述。
此循环仅存在于 Firefox 上。 不要使用它。
本节介绍 JavaScript 的条件语句。
在 if-then-else
语句 中:
if
(
«
condition
»
)
«
then_branch
»
⟦
else
«
else_branch
»⟧
then_branch
和 else_branch
可以是单个语句,也可以是语句块(请参阅 循环和条件语句的主体)。
您可以 链接多个 if
语句:
if
(
s1
>
s2
)
{
return
1
;
}
else
if
(
s1
<
s2
)
{
return
-
1
;
}
else
{
return
0
;
}
请注意,在前面的示例中,所有 else
分支都是单个语句(if
语句)。对于 else
分支仅允许使用代码块的编程语言,需要某种 else-if
分支才能进行链接。
以下示例的 else
分支称为 悬挂 分支,因为不清楚它属于两个 if
语句中的哪一个:
if
(
«
cond1
»
)
if
(
«
cond2
»
)
«
stmt1
»
else
«
stmt2
»
这是一个简单的规则:使用大括号。 前面的代码段等效于以下代码(其中很明显 else
属于谁):
if
(
«
cond1
»
)
{
if
(
«
cond2
»
)
{
«
stmt1
»
}
else
{
«
stmt2
»
}
}
switch
语句:
switch
(
«
expression
»
)
{
case
«
label1_1
»
:
case
«
label1_2
»
:
...
«
statements1
»
⟦
break
;
⟧
case
«
label2_1
»
:
case
«
label2_2
»
:
...
«
statements2
»
⟦
break
;
⟧
...
⟦
default
:
«
statements_default
»
⟦
break
;
⟧⟧
}
计算 expression
,然后跳转到标签与结果匹配的 case
子句。 如果没有标签匹配,switch
将跳转到 default
子句(如果存在),否则不执行任何操作。
case
之后的“操作数”可以是任何表达式;它通过 ===
与 switch
的参数进行比较。
如果未以终止语句结束子句,则执行将继续到下一个子句。最常用的终止语句是 break
。 但是 return
和 throw
也可以工作,即使它们通常离开的不仅仅是 switch
语句。
以下示例说明了如果使用 throw
或 return
,则不需要 break
function
divide
(
dividend
,
divisor
)
{
switch
(
divisor
)
{
case
0
:
throw
'Division by zero'
;
default
:
return
dividend
/
divisor
;
}
}
在此示例中,没有 default
子句。因此,如果 fruit
与任何 case
标签都不匹配,则不会发生任何事情
function
useFruit
(
fruit
)
{
switch
(
fruit
)
{
case
'apple'
:
makeCider
();
break
;
case
'grape'
:
makeWine
();
break
;
// neither apple nor grape: do nothing
}
}
在这里,一行中有多个 case
标签
function
categorizeColor
(
color
)
{
var
result
;
switch
(
color
)
{
case
'red'
:
case
'yellow'
:
case
'blue'
:
result
=
'Primary color: '
+
color
;
break
;
case
'orange'
:
case
'green'
:
case
'violet'
:
result
=
'Secondary color: '
+
color
;
break
;
case
'black'
:
case
'white'
:
result
=
'Not a color'
;
break
;
default
:
throw
'Illegal argument: '
+
color
;
}
console
.
log
(
result
);
}
此示例演示了 case
之后的值可以是任意表达式
function
compare
(
x
,
y
)
{
switch
(
true
)
{
case
x
<
y
:
return
-
1
;
case
x
===
y
:
return
0
;
default
:
return
1
;
}
}
前面的 switch
语句通过遍历 case
子句来查找其参数 true
的匹配项。如果其中一个 case
表达式计算结果为 true
,则执行相应的 case
主体。因此,前面的代码等效于以下 if
语句
function
compare
(
x
,
y
)
{
if
(
x
<
y
)
{
return
-
1
;
}
else
if
(
x
===
y
)
{
return
0
;
}
else
{
return
1
;
}
}
本节介绍 with
语句在 JavaScript 中的工作原理以及为什么不鼓励使用它。
with
语句的语法如下
with
(
«
object
»
)
«
statement
»
它将 object
的属性转换为 statement
的局部变量。例如
var
obj
=
{
first
:
'John'
};
with
(
obj
)
{
console
.
log
(
'Hello '
+
first
);
// Hello John
}
它的预期用途是在多次访问对象时避免冗余。以下是包含冗余代码的示例
foo
.
bar
.
baz
.
bla
=
123
;
foo
.
bar
.
baz
.
yadda
=
'abc'
;
with
使其更短
with
(
foo
.
bar
.
baz
)
{
bla
=
123
;
yadda
=
'abc'
;
}
通常不鼓励使用 with
语句(下一节将解释原因)。例如,在严格模式下禁止使用它:
> function foo() { 'use strict'; with ({}); } SyntaxError: strict mode code may not contain 'with' statements
避免使用 如下代码:
// Don't do this:
with
(
foo
.
bar
.
baz
)
{
console
.
log
(
'Hello '
+
first
+
' '
+
last
);
}
而是使用名称较短的临时变量
var
b
=
foo
.
bar
.
baz
;
console
.
log
(
'Hello '
+
b
.
first
+
' '
+
b
.
last
);
如果您不想将临时变量 b
暴露给当前作用域,则可以使用 IIFE(请参阅 通过 IIFE 引入新的作用域)
(
function
()
{
var
b
=
foo
.
bar
.
baz
;
console
.
log
(
'Hello '
+
b
.
first
+
' '
+
b
.
last
);
}());
您还可以选择将要访问的对象作为 IIFE 的参数
(
function
(
b
)
{
console
.
log
(
'Hello '
+
b
.
first
+
' '
+
b
.
last
);
}(
foo
.
bar
.
baz
));
要了解为什么不推荐使用 with
,请查看以下示例,并注意函数的参数如何完全改变其工作方式:
function
logMessage
(
msg
,
opts
)
{
with
(
opts
)
{
console
.
log
(
'msg: '
+
msg
);
// (1)
}
}
如果 opts
具有属性 msg
,则第 (1) 行中的语句不再访问参数 msg
。它访问属性
> logMessage('hello', {}) // parameter msg msg: hello > logMessage('hello', { msg: 'world' }) // property opts.msg msg: world
with
语句会导致三个问题
您无法通过查看标识符的语法环境(其词法上下文)来确定它引用的是什么。根据 Brendan Eich 的说法,这才是 with
被弃用的真正原因,而不是性能方面的考虑
with
违反了词法作用域,使得程序分析(例如,为了安全起见)变得困难或不可行。
with
语句内部,您无法静态地确定一个名称是指变量还是属性。只有变量可以被压缩器重命名。以下是一个 with
语句导致代码脆弱的例子
function
foo
(
someArray
)
{
var
values
=
...;
// (1)
with
(
someArray
)
{
values
.
someMethod
(...);
// (2)
...
}
}
foo
(
myData
);
// (3)
您可以阻止第 (3) 行的函数调用工作,即使您没有访问数组 myData
的权限。
怎么做?通过向 Array.prototype
添加一个属性 values
。例如
Array
.
prototype
.
values
=
function
()
{
...
};
现在,第 (2) 行的代码调用的是 someArray.values.someMethod()
而不是 values.someMethod()
。原因是在 with
语句内部,values
现在指的是 someArray.values
,而不是第 (1) 行的局部变量了。
这不仅仅是一个思想实验:数组方法 values()
被添加到 Firefox 中,并破坏了 TYPO3 内容管理系统。Brandon Benvie 找到了问题所在。