先来看个栗子:
function(){} // Uncaught SyntaxError: Unexpected token (
(function(){}) // ƒ (){}
function f(x){ return x + 1 }() // Uncaught SyntaxError: Unexpected token )
function f(x){ return x + 1 }(1) // 1
解释:
第一行代码:因为JavaScript 将 function 关键字当作一个 函数声明语句 的开始,而函数声明语句 function 关键字后面应该是 函数名,这里后面跟圆括号,当然会报错。
第二行代码:给它加上一对圆括号,解析器会把()里的当做表达式去解析,在这里就会当做匿名函数表达式解析,所以不会报错。
第三行代码:在一条语句后面加上()会被当做分组操作符,分组操作符里必须要有表达式,所以这里报错;
第四行代码:在一条函数声明语句后面加上(1),仅仅是相当于在声明语句之后又跟了一条毫无关系的表达式,等价于下面代码:
function f(x){ return x + 1 }
(1) // 1
表达式:
js 中的一个短语,js 解释器会将其计算出一个结果。程序中的常量是最简单的一类表达式。
变量名也是一种简单的表达式,它的值就是赋值给变量的值。复杂表达式是由简单表达式组成的。
比如,数据访问表达式是由一个表示数组的表达式、左方括号、一个整数表达式和右方括号构成。它们所组成的新的表达式的运算结果是该数组的特定位置的元素值。
同样的,函数调用表达式由一个表示函数对象的表达式和0个或多个参数表达式构成。
将简单表达式组合成复杂表达式最常用的方法就是使用运算符(opetator)。
运算符按照特定的运算规则对操作数(通常是两个)进行运算,并计算出新值。
乘法运算符“”是比较简单的例子。表达式xy是对两个变量表达式x和y进行运算并得出结果。有时我们更愿意说运算符返回了一个值而不是“计算”出了一个值。
语句:
js 整句或命令。js 语句是以分号结束;表达式计算出一个值,但语句用来自行以使某件事发生。
“使某件事发生”的一个方法是计算带有副作用的表达式。
诸如赋值和函数调用这些有副作用的表达式,是可以作为单独的语句的,这种把表达式当做语句的用法也称作表达式语句(expression statement)。类似的语句还有声明语句(declaration statement),声明语句用来声明新变量或定义新函数。
翻译一下Axel关于表达式和语句的文章:
1. 语句和表达式
Javascript区分表达式和语句。一个表达式产生一个值且可以被放置在需要一个值的地方,例如作为一个函数调用中的一个参数。下面的每行都包含一个表达式:
myvar
3 + x
myfunc("a", "b")
大致地说,一个语句执行一个动作。循环和if语句就是例子。 一个程序基本是语句的序列(我们在这里忽略声明)每当JavaScript期待一个语句,你也可以写一个表达式。这样的表达式被称作表达式语句。反过来则不可行:你不能在JavaScript需要表达式的地方写语句。例如,一个if语句不能成为一个函数的参数。
2. 类似的语句和表达式
当我们观察这两种句法类型中的相似成员时,两者的区别会更加清晰。
2.1 If 语句 vs 条件运算符
下面是一个if语句的栗子:
if (y >= 0) {
x = y;
} else {
x = -y;
}
表达式有一个类似的,条件操作符. 上面的语句等价于下面的语句:
var x = (y >= 0 ? y : -y);
=和;之间的代码是一个表达式。括号不是必须的,但这样条件操作符更易读。
2.2 分号 vs 逗号操作符
在JavaScript中,我们使用分号来连接语句:In JavaScript, one uses the semicolon to chain statements:
foo(); bar()
对于表达式,这里有不出名的逗号运算符:
foo(), bar()
这个运算符计算两个表达式,并返回第二个表达式的结果。栗子:
> "a", "b"
'b'
> var x = ("a", "b");
> x
'b'
> console.log(("a", "b"));
b
3. 看起来像语句的表达式
Some expressions look like statements. We’ll examine why that is a problem at the end of this section.
3.1 对象字面量 vs 块
下面是一个对象字面量object literal,一个产生一个对象的表达式:
{
foo: bar(3, 5)
}
然而,这也是一个完美符合规则的语句,它有着这些组件:
- 一个块:花括号包裹的语句序列。
- 一个标签label: 你可以在任何语句前面加上一个标签前缀,在这里标签是foo。
- 一个语句: 表达式语句bar(3, 5)。
{} 作为一个块或者是一个对象字面量取决于下面的WAT规则:
> [] + {}
"[object Object]"
> {} + []
0
+运算符是可互换的,那么这两个(表达式)语句不应该返回相同的结果吗?不,因为第二个语句等价于一个+在后的代码块:
> +[]
0
// 而[].toString()等于"", {}.toString()等于[object Object],所以第一个语句得到上述结果。
关于这里的细节部分和其他规则,请参考What is {} + {} in JavaScript?
JavaScript有单独的块? JavaScript有可以单独存在的代码块(与作为循环或if语句的一部分相反),对此你可能会感到惊讶。下面的代码表明这种块的一个用例:你可以给它们一个标签,并从它们当中break出去。.
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 函数表达式 vs. 函数声明
下面是一个函数表达式(注意这直接作为语句是不合法的,JavaScript会把它识别为函数声明,声明必须有函数名):
function () { }
你也可以给函数表达式一个名字,把它变成命名函数表达式named function expression:
function foo() { }
函数名foo只存在于函数内部,并且比如说,可以用来自身递归:
> var fac = function me(x) { return x <= 1 ? 1 : x * me(x-1) }
> fac(10)
3628800
> console.log(me)
Uncaught ReferenceError: me is not defined
一个命名函数表达式与一个函数声明(大致可以说是一个语句)无法区分的。但它们的影响是不同的:一个函数表达式产生一个值(一个函数),而一个函数声明执行一个行为——创造一个变量,变量的值为这个函数。除此之外,只有函数表达式能被立即执行(调用),函数声明则不行。
4. 使用对象字面量和函数表达式作为语句
我们了解到一些表达式无法和语句作区分。那意味着同样的代码在表达式上下文和语句上下文中执行会有不同的效果。一般而言这两个上下文是有清晰的区分的。然而,对于表达式语句,存在一个交叠overlap:在一个语句上下文中存在表达式。为了避免歧义,JavaScript语法禁止表达式语句以花括号或function开头:
ExpressionStatement :
[lookahead ∉ {"{", "function"}] Expression ;
那么如果你实在是想以这两种方式开头来写一个表达式语句,怎么办?你可以把它放进括号中,这不会改变运行的结果,却能确保它存在于一个表达式-only的上下文中。我们来看两个例子:eval和立即执行函数表达式。
4.1 eval
eval在语句上下文中解析它的参数。如果你想让eval返回一个对象,你必须用括号将对象字面量包裹起来(不输入引号的话就不用括号包裹)。
> eval("{ foo: 123 }")
123
> eval("({ foo: 123 })")
{ foo: 123 }
> eval({ foo: 123 })
{ foo: 123 }
4.2 立即执行函数表达式Immediately invoked function expressions (IIFEs)
下面的代码是一个立即执行函数表达式:
> (function () { return "abc" }())
'abc'
如果忽略外面的括号,会报语法错误(函数声明不能是匿名的):
> function () { return "abc" }()
SyntaxError: function statement requires a name
实际运行的报错: Uncaught SyntaxError: Unexpected token (
如果你在function和括号之间添加一个函数名,还是会报语法错误(函数声明不能被立即调用):
> function foo() { return "abc" }()
SyntaxError: syntax error
实际运行的报错:Uncaught SyntaxError: Unexpected token )
还有一种确保一个表达式在表达式上下文中被解析的方法,即一个一元运算符,例如+或者!或者!!。但是,与括号相反的是,这些运算符会对表达式的结果造成影响。如果你不需要多产生的结果的话,也算海星:
> +function () { console.log("hello") }()
hello
NaN
> !function () { console.log("hello") }()
hello
true
> !!function () { console.log("hello") }()
hello
false
NaN是对undefined,即函数的返回值进行+运算的结果。Brandon Benvie 提出了另一个可用的一元运算符void:
> void function () { console.log("hello") }()
hello
undefined
4.3 立即执行函数表达式的串联
当你要串联立即执行函数时,你必须要小心不要忘了分号:
> (function () {}())
(function () {}())
Uncaught TypeError: (intermediate value)(...) is not a function
这段代码产生一个错误,因为JavaScript认为第二行是企图获得调用作为一个函数的第一行的结果。加一个分号就好了:
(function () {}());
(function () {}())
// undefined
对于一元运算符(+既是一元也是二元),你可以忽略分号,因为JavaScript会自动插入分号。
void function () {}()
void function () {}()
// undefined
JavaScript在第一行后面插入一个分号,因为void不是一个有效的继续语句的方式。
网友评论