作者回复: 不是。块级作用域与它内部声明了什么没关系。例如,一个“块语句{ }”就有一个块级作用域,哪怕它内部一行代码也没有。
对于你说的if(1)这个例子来说,这里有两个语句,一个是if语句本身,它是个“单语句”(ECMAScript就是这么定义的,NodeJS的错误提示里也有),它没有块级作用域;而后面的一对大括号“{}”,是一个“块语句”,有一个块。
传统习惯上过来的开发人员会把“if () { ... }”理解成一个语句,而在JavaScript中,这是两个语句。
作者回复: 我之前没有注意过这个例子,倒是忽略了它在语句执行上的特点。不过这并不算特例。
因为在finally{}块中的执行流程仍然会回到try{}块,例如说,你在try{}块中使用return语句,那么在return之前会执行到finally{}块,而finally{}执行完之后,还会回到try{}块里的return语句来返回。所以最终“完成并退出”整个try语句的,还是try块。
在效果上,这类似于(也就是finally{}是一个call()):
try {
return void finally(), x;
}
catch {}
作者回复: 你说的都是。不过也不止的哟。比如说break语句自己就返回empty呀,还有continue,还有for语句的某些处理,以及yield等等,都有返回Empty的情况。
作者回复: 如果你说的是`for each ( ... in ...)`,那么这个语句不在ECMAScript的规范里面,在mozilla的spidermonkey引擎里,也是被废弃的特性了。
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for_each...in
作者回复: 其实真实的情况与你想的有点区别(也与我在文章中讲的有点细节上的不同)。关键在于:所有的表达式,原则上都是既可以返回完成记录,也可以返回引用,也可以返回值的。
后面两种比较容易理解,但表达式返回“完成记录”的意义在哪儿呢?多数情况下是没有意义的,但是只有允许这种情况,ECMAScript才能在表达式(的实现逻辑)中抛异常啊。所以多数情况下表达式返回值的Result都是值或引用两种,但偶尔也会返回类型为Throw的异常完成记录。也正是因为这个缘故,在ECMAScript中,所有所有的取表达式计算结果的写法,都采用类似下面这种模式:
```
* Let ref be the result of evaluating ...
* Let val be ? GetValue(ref)
```
首先,在GetValue()里面会写,如果ref不是引用,那么就直接返回,这样GetValue就会把“异常类型”的完成记录原样抛出来。然后,你注意第二行中的那个“?”号,那个表明如果GetValue()的调用结果是“异常类型”的完成记录,那么就结束当前的执行,继续把异常往外抛。
而且?号还有一个作用,就是直接从Normal类型的完成记录中把值解出来。也就是如果r是NormalCompletion,那么r = r.value。这样一来,就确保任何`?...`操作的结果,要么是异常被抛出,要么就是完成记录r中的值(r.value)。
所以,事实上整个“表达式执行”的结果Result也是支持返回值为完成记录的(而不仅仅是引用和值),只是绝大多数都过滤掉了。
接下来才是你的问题。函数执行也只是正常地返回了一个完成记录而已(如上面所说的,这是正常的行为,而不是语句执行的特例)。如果它是使用Return,那么也会在调用完成前被替换成Normal类型。然后函数调用操作会保证在完成之前得到的仅仅是一个一般的JavaScript语言类型中的数据(Result),或者非正常的完成类型。你看看这里就明白了:
```
// FROM: https://tc39.es/ecma262/#sec-evaluatecall
...
// 取函数调用结果
* Let result be Call(func, thisValue, argList).
// 断言:要么是非正常返回,要么就是语言类型
* Assert: If result is not an abrupt completion, then Type(result) is an ECMAScript language type.
```
而Call()是调用F.[[Call]]来实现的,它的主要代码就一行:
```
// FROM: https://tc39.es/ecma262/#sec-call
* Return ? F.[[Call]](V, argumentsList).
```
注意这里的?号,就是要么抛异常出去,要么就是把结果(完成记录r)中的值(r.value)取出来了。——这里再强调一个小的关键点:函数调用是不能返回规范类型中的“引用”的,也就是说结果值已经用GetValue(ref)把值取出来过了。
作者回复: 谢谢Aaron。
我想你读过前11章,看到第二篇加餐内容(“让JavaScript运行起来”)之后,你的大多数问题就都有解了,而且会对你已经领悟到的内容有许多“更新”,认识会再加深一些的。
说回你最后的两个疑问。表达式执行(包括函数执行),本质上都是求值运算,所以它们应当只返回值。但是事实上所有的执行——包括函数、表达式和语句也都“同时”是可以返回完成状态,这样才能在表达式中向外抛异常,因为异常抛出就是一个完成状态。
但是ECMAScript对所有在表达式层面上返回的“完成状态”做了处理,相当于在语言层面上“消化了”这些状态。所以绝大多数情况下,你认为表达式执行返回的Result是值或引用就好了。稍有例外的是,函数调用返回的是一个type为Return的完成状态,只不过它在内部方法Call处理之后,也已经变成了值而已。
关于这个问题,正好是在这一课的留言中,我给Elmer的回复中解释了更多的细节。你可以看看。
作者回复: 这个是优先级的问题。你在c3里面去掉了一对括号,运算符的优先级变了。
作者回复: 1. 可以。
2. 不太对。break labelName只与“块”相关,与函数没直接关系。语句的“块”也是有返回值的,因为JavaScript里面存在“语句执行是有值的”这个设定。
注意有许多语句是有“块(块级作用域)”的,而不仅仅是块语句(也就是一对大括号,它称为Block语句)。
作者回复: 函数执行啊,其实是表达式执行的特例。它会通过完成记录来返回return语句返回结果。
但是,在内部过程Call()的调用中它会取出值,而不是直接返回“Return类型的完成类型”。所以在“函数调用作为表达式的操作数”时,运算处理的还是“Result/Value值”,而不是“完成记录”。
由于函数调用会“从完成记录中取出值”,所以它不能返回“引用(规范类型)”。举例来说:
```
# 示例
> obj = { foo() { return this === obj } }
# 分组表达式能返回引用
> (obj.foo)()
true
# return不能返回引用
> (function () { return obj.foo })()()
false
```