文件夹
- 文件夹
- 问题是语句有值吗
- 那么说你骗我咯
- 有啥米用呢
- 研究这个是不是闲得那个啥疼
- ES5ES6有什么差异呢
- 结论是ES6是改了规则但更合理
- 最后不不过if语句
这两天在写语言精髓那本书的第三版,讨论到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中得到了统一。