33-js-concepts之10. 表达式与语句

先来看个栗子:

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不是一个有效的继续语句的方式。

Related posts

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

参考文章:
js 表达式与语句
Expressions versus statements in JavaScript

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 230,501评论 6 544
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,673评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,610评论 0 383
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,939评论 1 318
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,668评论 6 412
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 56,004评论 1 329
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 44,001评论 3 449
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 43,173评论 0 290
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,705评论 1 336
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,426评论 3 359
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,656评论 1 374
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,139评论 5 364
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,833评论 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,247评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,580评论 1 295
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,371评论 3 400
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,621评论 2 380

推荐阅读更多精彩内容