原文链接:http://2ality.com/2012/09/expressions-vs-statements.html
这篇博客着重于在JavaScript语法中十分重要的语句(statements)和表达式(expressions)之间的区别。
1.语句和表达式
JavaScript对语句(statements)和表达式(expressions)有十分明确的划分。一个表达式返回一个值,可以在任何需要值的地方使用表达式,例如:作为函数调用时使用的参数。以下每一行都包含一个表达式:
myvar
3 + x
myfunc("a", "b")
我们可以粗略的将一个语句描述为一个行为。循环结构和if语句就是语句的例子。程序基本上是一系列语句的结合(基础声明除外)。无论何时,当JavaScript需要编写一条语句时,均可以写入一个表达式。这样的语句称为
表达式语句(expression statement)。但是反之并不成立,你不能编写一条语句来代替表达式。例如:if语句不能成为函数的参数。
2.相似的语句和表达式
为了加深对语句和表达式之间区别的理解,我们来举几个表达式和语句十分相似的例子。
2.1. if语句和条件运算符(conditional operator)
下面是一个if语句的例子:
var x;
if (y >= 0) {
x = y;
} else {
x = -y;
}
与表达式类似的是条件运算符。上述语句可用以下语句代替:
var x = (y >= 0 ? y : -y);
注意:等号和分号之间的代码是一个表达式。括号不是必要的,括号是为了使代码更加清晰易懂。
在JavaScript中,分号用来连接不同的语句:
foo(); bar();
表达式中还有一个鲜为人知的逗号运算符:
foo(), bar()
逗号运算符计算两个表达式的值并返回第二个表达式的值。如下所示:
> "a", "b"
'b'
> var x = ("a", "b");
> x
'b'
> console.log(("a", "b"));
b
3.与语句类似的表达式
3.1.1.对象字面量(object literal)和块状作用域(block)
下面的例子显示了一个对象字面量:
{
foo: bar(3, 5)
}
但是,
上述代码同样也是一个合法的语句!上述例子由以下几个部分组成:
(1)代码块:花括号中的一系列语句;
(2)标签:你可以在任意语句前添加标签,这里的标签是foo(请自行联想命名空间——译者);
(3)语句:表达式语句bar(3,5);
{}可用于定义作用域或对象字面量,究竟代表什么取决于下列WAT(?):
> [] + {}
"[object Object]"
> {} + []
0
上述代码显示了一个很奇怪的现象,众所周知,加号运算符两侧的语句应当是可交换的,但为何这两个语句输出了不同的结果呢?关键在于第二个语句,第二个语句相当于一个代码块({}),后面加上一个[]。
> +[]
0
JavaScript允许一个块状作用域既不充当循环也不充当if语句的一部分而独立存在。下面的代码演示了这样一个例子,你可以通过标签命名块状作用域,并在合适的时机跳出这个作用域,返回到上层作用域中:
function test(printTwo) {
printing: {
console.log("One");
if (!printTwo) break printing;
console.log("Two");
}
console.log("Three");
}
> test(false)
One
Three
> test(true)
One
Two
Three
3.2.
函数表达式(function expression)与
函数声明(function declaration)如下是一个函数声明:
function () { }
当然,你也可以为这个表达式加上一个名字,将它变成命名后的函数表达式(手动滑稽):
function foo() { }
这个名为foo的函数可在自身内部进行递归调用:
> var fac = function me(x) { return x <= 1 ? 1 : x * me(x-1) }
> fac(10)
3628800
> console.log(me)
ReferenceError: me is not defined
命名后的函数表达式和函数声明仅从语句上是无法区分的。但他们的效果却截然不同:函数表达式产生一个值(函数);函数声明是一个动作:创建一个变量,其值为函数。此外,如需立即调用函数,则必须使用函数表达式,不能使用函数声明。
4.使用对象字面量和函数表达式作为语句
我们已经看到,有些表达式和语句无法区分开,这意味着相同的代码由于其上下文环境不同,作用也是不同的。但是,表达式语句却将表达式写在了语句的上下文之中。为了避免歧义,JavaScript语法禁止表达式语句以大括号和关键字function开始。
ExpressionStatement :
[lookahead ∉ {"{", "function"}]Expression ;
那么,如果你想写一个以这两个标记中的任何一个开始的表达式语句,你该怎么做?你可以把它放在括号里面,它不会改变它的结果,但确保它出现在只有表达式的上下文中。我们来看两个例子:eval并立即调用函数表达式。
4.1.eval
eval在语句上下文中解析它所受到的参数。如果你想让eval返回一个对象,则必须在对象字面量周围加上括号。
> eval("{ foo: 123 }")
123
> eval("({ foo: 123 })")
{ foo: 123 }
4.2.
立即被调用的函数表达式/自执行函数(IIFEs)
下列代码定义的是一个自执行函数:
> (function () { return "abc" }())
'abc'
如果省略括号,则会出现语法错误(函数声明不能声明匿名函数):
> function () { return "abc" }()
SyntaxError: function statement requires a name
即使你添加一个名字,也会返回语法错误(函数声明不能被立即调用):
> function foo() { return "abc" }()
SyntaxError: syntax error
另一个保证表达式在表达式上下文中被解析的方法是在函数声明之前添加一个一元运算符(比如+或!),但是与括号不同的是,这些符号会改变输出的表示形式,如下所示:
> +function () { console.log("hello") }()
hello
NaN
NaN的出现正是由于+号运算undefined的结果。也可以使用以下void运算符:
> void function () { console.log("hello") }()
hello
undefined
4.3.连接多个IIFEs
当你连接IIFEs时,不要忘记分号:
(function () {}())
(function () {}())
// TypeError: undefined is not a function
代码错误原因是JavaScript认为第二行是尝试将第一行的结果作为函数调用。解决方法是在第一行末尾加上一个分号:
(function () {}());
(function () {}())
// OK
对于只有一元运算符(加号既是一元也是二进制)的操作符,可以省略分号,因为执行时会自动添加分号:
void function () {}()
void function () {}()
// OK
JavaScript在第一行之后插入一个分号,因为void不是继续执行这个语句的有效方式。