文件夹
这两天在写语言精髓那本书的第三版,讨论到ES6跟ES5中间对“语句的值”的不同处理。正好Weibo上也有同学对这个问题有兴趣,所以专门整理了这篇。
写博客能够啰嗦点,写书就不行了。所以这篇文章跟书上能看到的还是会不一样的。
问题是:语句有值吗?
非常不幸。我们面临的的确是一门连语句都有值的语言。在JavaScript中。代码是按语句行(Statement Lists)来解释的,所以eval()本质上还是运行的语句行。比如:
eval("1+2+3")
实际上并非在计算表达式,而是在解释运行“代码文本”。由于一个文本块隐含的有一个“文本/文件结束符(EOF)”,它与行结束符(EOL)一样能够等效于JavaScript的语句分隔符。所以上述代码等效于:
eval("1+2+3;")
假设你不想了解得这么具体,那么记住“JavaScript是按语句行运行的”就好了。
那么……这个语句的值究竟是什么呢?非常不幸,上面这个演示样例中,语句的值和表达式值是一样的,也是6。
那么说,你骗我咯?
你看,JavaScript里面有一类语句,叫表达式语句。非常不幸,你见到的绝大多数语句都是表达式语句。比如
Object.toString()
这里是一个方法调用(表达式)。你在后面加个分号(;),在语法上那就成了一个语句行,于是就成了表达式语句。由于其实JavaScript也存在单值表达式,所以一个单值也能够是一个语句。
这其实也就是函数或块首放上个”use strict”一点点也不违和的原因——它是符合JavaScript传统的语法惯例的:
function foo() {
"use strict";
1;
true;
}
上面的代码是合法的,函数foo()内有三个单值语句。函数声明本身也是一个语句。函数声明(以及全部的显式声明语句)是没有返回值的——在ECMAScript中它被定义为返回Empty。
函数调用的返回值是由于调用运算符“()”来决定的。这个运算符要求用return来返回值,假设没有则视为undefined。这就是JavaScript函数的一些特性的根源了。
“表达式运算”和“语句有值”能够解释JavaScript语法特性中的很多迷题,是这门语言在设计上的一些基本性质。
有啥米用呢?
由于eval()本质上是运行语句而不是表达式,所以语句怎样返回值就成了这个函数的终于特性。须要注意的是:不是eval()在求值,而是JavaScript代码块/语句行本身有值,而eval()不过返回这个值而已。假设没有这个特性。Ajax也就做不成了,由于我们通经常使用Ajax/JSONP从远端取个值过来,就是eval()“解析”一下,这里就是用的“运行语句并取值”的特性。
在JavaScript中,语句有值,而语句块(复合语句)的值就是这个块中最后一个有值语句的值。按ECMAScript的原文是:
The value of a StatementList is the value of the last value producing item in the StatementList. For example, the following calls to the eval function all return the value 1:
The production IfStatement : if ( Expression ) Statement else Statement is evaluated as follows:
- Let exprRef be the result of evaluating Expression.
- If ToBoolean(GetValue(exprRef)) is true, then
a. Return the result of evaluating the first Statement.- Else,
eval("1;;;;;") eval("1;{}") eval("1;var a;")
“value producing item”这个说法在ES5中是叫“value producing Statement”。可是,我并没有在ES5/ES6中找到一个明白的说法:哪些语句是生成值的语句呢?
研究这个是不是闲得那个啥疼?
那个啥疼不疼跟这个毛线关系也没有。
研究这个其实是非常重要的一件事情,由于以下这行代码究竟怎么解释,取决于我们这里的研究:
// sourceText at remote
if (x) (
function aa() {}
)
else (
function bb() {}
)
我们假设上面的代码段来自于远端。然后我们在通过ajax的方式得到它。称为”sourceText”,那么以下的代码究竟是什么结果呢?
x = true;
foo = eval(sourceText);
console.log(foo.name); // "name" property define in ES6
先解释一下当中的aa/bb函数。注意这里的两个函数在语法上不是“函数声明语句”,而是“函数表达式”——注意这里用了“()”来强制它们为表达式。
这个也不是我乱讲,在MDN(Mozilla Developer Network)官方文档上面就是这么分类的。“function statement”和“function expression”是两个不同的东西。
“函数声明语句”是无值的。而函数表达式是有值的,进而“函数表达式语句”也就是有值的。所以sourceText中。假设x是真。则if语句应该返回aa的值,否则该返回bb的值。于是。演示样例代码中的:
foo = eval(sourceText);
才有意义,而终于控制台才会输出”aa”。表明foo函数来自于aa()函数表达式。如今看来,以下这句话是真的实用了吧:
if语句的值。是其then/else分支中的statementList最后一个有值语句的值。
ES5/ES6有什么差异呢?
这两天写书的时候发现一点跟此前理解的不同的地方(正好我又用了Nodejs中旧版的V8)。所以实在搞不清楚ECMAScript的定义出了问题,还是V8的实现出了问题。
于是乎在微信上抓了Hax要讨论。无奈乎那个家伙不理我——所以我决定这周去上海找他算账。此话容后再讲。
有什么不同呢?
问题出在ES5中说。假设then/else分支中没有语句,也就是statementList为empty,那么if语句结果也就为空。他的定义非常easy,是这么写的:
The production IfStatement :
if ( Expression ) Statement else Statement
is evaluated as follows:
Let exprRef be the result of evaluating Expression.
If ToBoolean(GetValue(exprRef)) is true, then
a. Return the result of evaluating the first Statement.Else,
a. Return the result of evaluating the second Statement.
假定“first Statement”为emptyStatement。结果当然就是empty。而对于empty,JavaScript会忽略这个“语句的值”。
这个意思是说:
1;
{};
;
上面三个语句中,第2、3两行实际都是空语句——它们的值是empty。被忽略。所以整个代码文本会返回1。
那么依照这个规则。以下的代码:
1; if (true);
// 或
1; if (false);
这两种情况都应该返回1。这个就是在ES5中的情况了。
然而在ES6里面,这段规范被写成以下这样:
// 4~5: let stmtCompletion be the result of first/second Statement
// 6: ReturnIfAbrupt(stmtCompletion).7: If stmtCompletion.[[value]] is not empty, return stmtCompletion.
8: Return NormalCompletion(undefined).
这里的意思是说:假设then/else的结果不是empty那么就返回它们,否则。就得返回“undefined”。
于是以下这种演示样例:
1; if (true);
就该返回undefined了。
我当然一早就读明白了ES6,我当时的问题在于。我用了Nodejs中的旧版V8,以及firefox/chrome的旧版本号来做測试——它们声明支持了ES6。然而在这项特性上表现出来的,仍然是ES5的那个样子。
于是我就懞逼了:这些声称支持ES6的引擎错了。还是标准没写对呢?
正是由于对了解标准比了解指掌还要多的Hax没有如期出现。所以一向觉得
“标准都是人写的。是人写的就会错”
的我选择了相信…… 相同也是一堆人(以及也是相同一堆人)写的ES5。
——假设ES5是对的,那么就是ES6写错了。
结论是:ES6是改了规则,但更合理
验证这个结论的方法是:Chrome的新版中的新V8引擎,以及Firefox的新版本号都採用了ES6中的规范。当然,非常不幸,你假设用Nodejs来測试。至少当前版本号(4.4.2/5.10.1)中还是错误的、依照ES5的规范来实现的。
那么为什么我终于会觉得ES6就“更合理”一点呢?
还是得回到“语句该不该有值”这个根本问题上来讨论。
首先,ECMAScript是承认语句有值的,并且也同一时候承认“某些语句是没有值/不产生值”的。比如说,空语句就不产生值,函数声明、变量声明等等也不产生值 。
——对于成批的语句来说,不产生值则在代码上下文中对结果值无影响,产生值则影响结果。所以明白”哪些有值,哪些没有值“是非常重要的。而ES5中,这个问题导致if语句的结果有不确定性。
既然:
假设then/else中的语句有值,则if有值。假设无值,则if无值。
那么以下的代码就是不确定语义的:
// sourceText at remote
"hello";
if (x) (
function aa() {}
)
当x是true时,if语句有意义。当x为false时,if语句在上下文中就没意义了——它对结果值没有影响。而
【ES5】if语句对结果值的影响存在不确定性
这个结果在语义设计上就是非常失败的。
而到了ES6中:
【ES6】if语句总是有结果值的,要么是then/else的结果,要么是undefined
这就使得if有着确定的语义了。
最后。不不过if语句
写ECMAScript 262的那票人真不是吃闲饭的(除了写4th的时候),有些问题人家是真想得清楚。比方还是这个语句的值的问题,根本上来说不是“if语句怎么回事”,而是“怎样处理语句的值”的问题。
我昨晚基本的工作就是整理了全部这些语句在值上的效果,if/for/while/try等等语句在值的处理上惊人的一致。除了这些语句和表达式语句之外,就唯独return/yield/throw用来显式地返回结果了。
所以说。语句在“产生值(value producing)”上面的行为,在ES6中得到了统一。