JavaScript中的表达式和语句(译)

3,930 阅读7分钟

原文地址:
2ality.com/2012/09/exp…

译者也是刚刚开始学习,在翻译过程中加入了自己的理解,如有不对的地方,欢迎指出!

1.语句(Statements)和表达式(expressions)

JavaScript区分表达式和语句。表达式是输出值的,并且可以写在任何需要一个值的地方。例如函数调用里面的实参。

myvar
3 + x
myfunc("a", "b")

笼统来说,一个语句表示执行了一个动作。循环控制语句和if语句就是例子。一个程序基本上就是一系列的语句的集合(这里我们忽略声明(declarations))。在JavaScript中需要语句的地方,也可以写入一个表达式。这样的语句可以称为表达式语句(expression statement)。但是反过来则行不通了,你不能够在需要表达式的地方写入一个语句。例如,一个if语句不能作为函数的实参。

2.语句和表达式的相似种类

如果我们看一下这两种语法类型中相似的成员,语句和表达式的区别就会很清晰了。

2.1 if语句和条件运算符

下面是if语句的例子:

var x;
if (y >= 0) {
    x = y;
} else {
    x = -y;
}

上述语句等同于以下的语句:

var x = (y >= 0 ? y : -y);

等号和分号之间的内容是一个表达式。括号并不是必要的,写上只是为了易读性考虑。

2.2 分号和逗号操作符

在JavaScript中,使用分号去结束一个语句:

foo(); bar()

对于表达式来说,有并不常见的逗号操作符:

foo(), bar()

逗号操作符会对所有表达式求值并且返回第二个表达式的结果。例如:

> "a", "b"
'b'

> var x = ("a", "b");
> x
'b'

> console.log(("a", "b"));
b

3. 看起来像语句的表达式

有一些表达式看起来像是语句,后面将会解释为什么这会是一个问题。

3.1 对象字面量和代码块(block)

下面是一个对象字面量,是一个表达式,输出一个对象。

{
    foo: bar(3, 5)
}

然而,这也是一个完全符合规则的语句。它包括以下组成部分:

  • 一个代码块(block): 在大括号中的一个序列的语句。
  • 一个标签: 你可以在任何语句前面加一个标签。在上面的例子中式foo。
  • 一个语句: 表达式语句式bar(3, 5)。

{}既可以作为一个代码块(block),又可以作为一个对象字面量,这是下面WAT现象产生的缘由:

> [] + {}
"[object Object]"

> {} + []
0

考虑到加法交换律,上述的两个表达式语句难道不是应该返回同样的结果吗?
不!!因为第二个语句等同于一个代码块接着一个 + []:

> +[]
0

更多细节关于上述以及其他WAT现象,详情请看相关文章1。

JavaScript有独立的代码块吗?这可能使你很惊讶,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 () {}

你也可以给上述函数表达式一个名字,使其成为命名函数表达式:

function 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. 使用对象字面量和函数表达式作为语句

我们已经见过一些表达式与语句是不可区分的,这就意味着同样的代码在不同的执行上下文环境中有着不同的作用(要看他们是出现在表达式上下文环境还是语句上下文环境)。通常情况下,这两种上下文环境是截然分开的。但是,对于表达式语句(expression statements)来说,就存在着一个上述两种上下文环境部分重叠的区域:即存在出现在语句上下文环境的表达式。为了避免混淆,JavaScript语法禁止表达式语句使用大括号(curly barce)或关键字function开头:

Syntax

ExpressionStatement :
    [lookahead ∉ {"{", "function"}] Expression ;

NOTE An ExpressionStatement cannot start with an opening curly brace because that might make it ambiguous with a Block. Also, an ExpressionStatement cannot start with the function keyword because that might make it ambiguous with a FunctionDeclaration.

译者注:Ecma-262 5.1 edition说明,表达式语句如果使用大括号开头会与代码块(block)产生混淆,使用function关键字开头会与函数声明混淆。

如此,如果你想要写一个表达式语句,并使用上述两者其中之一开头,那该怎么做呢?
你可以将该表达式语句放入括号内,这将不会改变执行结果,但可以确保它在一个仅适用于表达式的上下文环境中执行。让我们看两个例子: eval和立即调用函数表达式。

4.1 eval

eval 将其接收到的实参解析在语句上下文环境中。若想要eval返回一个对象,那就必须用括号包裹对象字面量。

> eval("{ foo: 123 }")
123
> eval("({ foo: 123 })")
{ foo: 123 }

4.1 立即调用函数表达式(IIFEs)

下面的代码是一个立即调用函数表达式。

> (function () { return "abc" }())
'abc'

如果你遗漏了括号没有写,那么你将得到一个语法错误提示(function declarations can’t be anonymous):

> function foo() { return "abc" }()
SyntaxError: syntax error

另一个确保表达式解析在表达式上下文中的方法是使用一个一元运算符(unary),例如 + 或者 !。但是,相对于使用括号来说,这些操作符会改变表达式的执行结果。如果你不需要得到执行结果,那也是没有问题的:

> +function () { console.log("hello") }()
hello
NaN

上述函数调用的原结果是undefined,而现在的结果是NaN,正是将 + 作用于undefined的结果。Brandon Benvie 提到另外一个的可供使用的一元运算符:void(详见相关文章2)

> void function () { console.log("hello") }()
hello
undefined

4.3 相连的IIFEs

当几个IIFEs相接的时候,一定要小心,不要遗漏了分号:

(function () {}())
(function () {}())
// TypeError: undefined is not a function

上述代码报错了,因为JavaScript认为,第二行代码的含义是将第一行代码的执行结果作为一个函数进行调用。修正方法是添加一个分号:

(function () {}());
(function () {}())
// OK

使用添加操作符的方法时,在添加一元运算符的情况下(注意,+ 既是一元运算符也是二元运算符),可以省略分号,因为JavaScript会自动帮我们在语句后面添加分号。

void function () {}()
void function () {}()
// OK

JavaScript在第一行末尾插入了一个分号,因为void不是一个合法的延续语句的符号。(详见相关文章3)

5. 相关文章

  1. What is {} + {} in JavaScript?
  2. The void operator in JavaScript
  3. Automatic semicolon insertion in JavaScript