Statement completion values

最近Paul Irish在Twitter上提了一个JavaScript的小问题,觉得很有趣,所以写篇文章来记录一下。

在浏览器的console中运行如下的JavaScript片段:

1
'omg'; var x = 4;

结果返回omg,看到这种情况,最先想到的就是变量提升,所以写了下面的代码:

1
var x; 'omg'; x = 4;

结果返回4,这就让人很迷惑了。于是请教了下公司的前端,得到的答案是 var x = 4被提升了,但是我并不认同。
因为变量提升仅仅是声明语句提前了,但是变量初始化仍然在原来的位置。var x = 4实际上包含两条语句, var x; x = 4,
其中var x;是声明语句,首先JavaScript引擎会找到所有的声明,并用合适的作用域使之关联起来,这一阶段是在编译时完成的,初始化或者赋值语句
将在运行时执行,所以x = 4不会被提升。直到代码运行到其所在的位置时才会被赋值。所以写了下面的代码加以验证:

1
console.log(y, y === undefined); 'omg'; var y = 4;

结果输出undefined true,返回omg,事实证明只有var y被提升了。
最后Javascript的创建者Brendan Eich回答了这个问题:

It’s not a return value, rather a statement completion value.

但是什么是statement completion value,和return value又有什么区别呢?

statement completion value

直观的看, statement completion value是执行一段代码块所得到的值,例如:

1
2
3
4
5
6
>Math.random()
<0.853512441173391
>a = 5
<5
>var b
<undefined

实际上,目前捕获statement completion value的唯一方式是使用eval:

1
2
3
4
5
6
>eval("Math.random()")
<0.03151934138190282
>eval("a = 5")
<5
>eval("var b")
<undefined

需要指出的是并不是所有的语句都有completion value, 例如for(var x = 42;false;);

那么JavaScript又是如何处理statement lists的呢?ECMA规范有下面一段话:

The value of a StatementList is the value of the last value producing item in the StatementList.

依据上面的规范,下面的语句列表都会返回 1:

1
2
3
eval("1;;;;;")
eval("1;{}")
eval("1;var a;")

依此可以得到结论:一个statement lists总是返回最后一个非空语句的值。所以就能够很好的解释本文开头提出的问题了。
omg;completion valueomg, var x = 4completion valueundefined,他们组合到一起就返回omg

ES7已经有提案要支持statement completion value的捕获了,具体的语法是:

1
2
3
var x = do {
...
}

参考文献