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
一个命名的函数表达式从表面上看起来,和一个函数声明并没有什么区别.但他们的效果是不同的:一个函数表达式产生一个值(一个函数).一个函数声明执行一个动作:将一个函数赋值给一个变量. 此外,只有函数表达式可以被立即调用,函数声明不可以.
只要是表达式语法,脚本宿主就认为 function 是一个直接量函数,如果什么都不加,光以 function 开头的话则认为是一个函数声明,
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上的结果.
译者注:我觉的没翻译明白,所以用拙劣的水平画了张图.
在区分表达式和语句之前,我们先分别对他们进行介绍:
1.表达式(expressions)
表达式是由运算符构成,并运算产生结果的语法结构。每个表达式都会产生一个值,它可以放在任何需要一个值的地方,比如,作为一个函数调用的参数.下面的每行代码都是一个表达式:
var a = (5 + 6) / 2; //表达式:(5 + 6) / 2 var b = (function(){ return 25;})(); //表达式: (function(){ return 25;})() foo(a*b); //表达式:a*b
2.语句(statements)
语句则是由“;(分号)”分隔的句子或命令。如果在表达式后面加上一个“;”分隔符,这就被称为“表达式语句”。它表明“只有表达式,而没有其他语法元素的语句”。
var a = (5 + 6) / 2; //整行,赋值语句 if(a>12) { statements} //条件语句 var o = {}; //赋值语句 (function(obj){ obj.b = 23;})(o||{}); //表达式语句
一般的javascript中的语句分为下面几种:
(1)声明语句:变量声明和函数声明
(2)赋值语句
(3)控制语句:能够对语句执行顺序产生改变,包括条件语句和循环语句,当然还有比较特殊的标签语句。
(4)表达式语句:这些语句去掉最后分号,都也可当表达式用的。常见的有:对象操作(new、delete)、函数调用(函数执行,必有返回值)等。
var num = 9; //声明、赋值语句 vloop: //标签语句 { //其实这里大括号可以不需要的,在这里我只想向大家展示一下这种代码块结构而已 for(var i=1; i<10; i++) { //循环语句 if(i==num){ //条件语句 break vloop; }else{ num = num - 1; } } } console.log(num); //表达式语句,输出:5
由上面可以看出,表达式和语句还是存在很大区别的,可也说表达式是语句的构成部分,而语句则又可以组成可执行代码块。一般而已,我们都可以很直观的看出两者的区别,但是,一些特殊情况就不太好区别。
难以区分的表达式和语句
1.对象字面量和代码块
var o = { a : {}, b : "string" }
上面是一个简单至极的对象字面量,但是我们单单从代码的直观层面来看,这个字面量其实跟代码块非常相似,由两个标签语句组成的感觉。复杂些上,还有上面之 前我在语句最后举得那个例子,例子中代码块位于标签语句下面,里面包含有个for循环。这时候,你说这个由{}构建的代码块是表达式呢还是语句?
2.命名函数表达式
javascript中有三种函数类型:函数声明,函数表达式和函数构造器创建的函数。
(1)函数声明(FD)
function foo(){ statements; }
(2)函数表达式(FE)
var foo = function(){ statements;}
还有一种比较特殊点的:var foo = function _foo() { statements;} ,这是时候,给函数一个供内部使用的名字_foo,所有,此时,这种类型又称:命名函数表达式(NFE)。
(3)函数构造器创建
var foo = new Function(expressions);
其实上面说了函数的三种类型并不是我们这章的主要重点,这这我们也是探讨一下FD和NFE的一些区别而已,关于其他函数内容后面我单独在细说。
是不是看到FD和NFE的形式之后,又有点点迷糊了,NFE除了前面多了一个var和变量名之外,其他和FD的结构一模一样,这样的话,那是不是说明FD既可以作声明语句,也同时可以作赋值表达式呢?
解答之前两个疑惑
鉴于上面两个比较让人迷惑的语法点,javascript它自己也认识到不足,之后果断改进,做出了如下声明:JavaScript语法禁止表达式语句以大括号或关键字"function"开头。
知错能改善莫大焉,当知道javascript做出如此强制的规约时,就一下子对前面两个疑惑有了答案。
在这之前,我们还要想提下三个概念:语句上下文、表达式上下文和表达式语句。
语句上下文:在这个环境中,这这句代码(表达式或者语句)应该理解成一个语句。
表达式上下文:在这个环境中,这句代码(表达式或者语句)应该理解成一个表达式。
表达式语句:这个表达式既可以看作是一个表达式(因为它能产生一个值),又可以看作是一个执行语句(因为它能执行某些功能,比如执行一个函数等)。表达式语句可以是javascript链式编程的基础。
上面这个概念起辅助理解作用,不用做过深追究。
我们再来看之前的那两个疑惑:
第一个,vloop的冒号后面由大括号中那一段代码,里面都有循环和赋值等操作,那说明它都不是一个表达式语句,所以它不必遵循上面的规定。在 这其实它只是一个代码块语句而已。不过对于对象字面量而言,它确实一个货真价实的表达式语句,根据规约,它就只能乖乖的做表达式,不能做语句。
第二个,对于NFE类型函数来说,你可以将它看作是一个函数申明语句,同时也可以将之看成一个表达式,但是根据javascript的规定,表 达式语句不能以function开头,所有在这,NFE中的肯定是表达式了。当然,对于FD,这个很明显,是函数声明语句,不用怀疑。
其实,还有另一种判定方法,根据上下文判断,利用之前我们说过语句上下文和函数上下文。对于一个表达式语句,当你无法区分它是表达式还是语句, 你参照上下文,判断程序在这需要做什么,是变量赋值?还是语句执行?看到前面由“=”(赋值)或者用“()”(小括号,在这时分组符,里面只能包含表达 式),你就可以肯定,这是一个表达式上下文。javascript的解释器就是这个干的。
var foo = function _foo(index){ console.log("传入的参数是:"+index||"空值") }(1); //输出:传入的参数是:1 console.log(foo); //输出:undefined //根据上下文判断,"var foo = "后面是表达式上下文,所有解释器自动将后面看成一个立即执行函数,虽然没有小括号。 function foo(index){ console.log("传入的参数是:"+index||"空值") }(1) //输出:1 console.log(foo); //function foo(index){...} //解释器判断此为语句上下文,所以讲语句拆分为两段。前一段为函数声明,后一段“(1)”为一个简单的分组语句。
看到上面,你是不是灵机一动,发现你居然可以强制将表达式语句转换成表达式了!
转换方法:
(1)利用小括号,也就是分组符号(分组符内只允许表达式),进行转换。
var o; o = eval("{obj:'this is a object'}") console.log(o); //this is a object o = eval("({obj:'this is a object'})") console.log(o); //Object {obj: "this is a object"}
前者eval中没有加小括号,运行时被认为是一条语句执行,所以o被赋值成义字符串了;而后者,被认为是加上小括号,被认为是在表达式上下文中执行,所有返回一个对象。
(2)利用运算符,因为javascript引擎会认为这是参与运算的必须是表达式。
+function(index){ console.log("传入的参数是:"+index||"空值"); return index; }(1) //输出:传入的参数是:1 //表达式结果是:1
记得上面我也运行过这个句代码,不过当时前面少了个加号(“+”)。比较一下两者的结果。