[译]JavaScript中:表达式和语句的区别

原文:http://www.2ality.com/2012/09/expressions-vs-statements.html


本文要讲的是JavaScript中非常重要的两个知识点:表达式(expressions)和语句(statements)之间的区别.

1.语句和表达式

JavaScript中的表达式和语句是有区别的.一个表达式会产生一个值,它可以放在任何需要一个值的地方,比如,作为一个函数调用的参数.下面的每行代码都是一个表达式:

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

语句可以理解成一个行为.循环语句和if语句就是典型的语句.一个程序是由一系列语句组成的.JavaScript中某些需要语句的地方,你可以使用一个表达式来代替.这样的语句称之为表达式语句.但反过来不可以:你不能在一个需要表达式的地方放一个语句.比如,一个if语句不能作为一个函数的参数.

2.其他语法

看看下面这两对类似的语法,搞懂这些后,能够帮助我们更好的理解语句和表达式之间的关系.

2.1 If语句和条件运算符

下面是一个if语句的例子:

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

类似if语句功能的表达式叫做条件运算符.上面的语句等价于下面的.

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 对象字面量和语句块

下面是一个对象字面量,也就是一个可以生成一个对象值的表达式.

{
foo: bar(3, 5)
}

不过同时,它也是一个完全合法的语句,这个语句的组成部分有:

  • 一个代码块:一个由大括号包围的语句序列.
  • 一个标签:你可以在任何语句前面放置一个标签.这里的foo就是一个标签.
  • 一条语句:表达式语句bar(3, 5).

你也许会感到震惊,那就是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() { }

这个函数的函数名(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

一个命名的函数表达式从表面上看起来,和一个函数声明并没有什么区别.但他们的效果是不同的:一个函数表达式产生一个值(一个函数).一个函数声明执行一个动作:将一个函数赋值给一个变量. 此外,只有函数表达式可以被立即调用,函数声明不可以.

3.3 解决冲突

从3.1和3.2可以看出,有些表达式和语句在表面上看不出有什么区别.也就意味着,相同的代码,出现在表达式上下文和出现在语句上下文会表现出不同的作用.通常情况下,这两种上下文是没有交集的.但是,如果是表达式语句的话,会有一个重叠:也就是说,会有一些表达式出现在语句上下文上.为了解决这种歧义,JavaScript语法禁止表达式语句以大括号或关键字"function"开头:

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

那么,如果你想写一个以那些标志开头的表达式语句,该怎办呢? 你可以把它放在一个括号内部,这样并不会改变运行结果,只会确保该表达式被解析在表达式上下文中.让我们看两个例子.第一个例子:eval会按照语句上下文解析它的参数.如果你想让eval返回一个对象,你必须在对象字面量两边加上一个括号.

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

第二个例子:下面的例子是一个立即执行的函数表达式.

> (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上的结果.

译者注:我觉的没翻译明白,所以用拙劣的水平画了张图.

posted @ 2012-09-16 23:57 紫云飞 阅读(...) 评论(...) 编辑 收藏