作者回复: 是的。
赞,好几个赞。^^.
作者回复: case 'b' 永远执行不到,但它里面的x却已经声明了,并且导致case 'a'中的代码无法访问到外部的`x = 100`。
这说明case 'a'和case 'b'使用了同一个闭包。
作者回复: 《JavaScript语言精髓与编程实践》第三版。^^.
已经交稿,大概快要出了。
如果急用,可以看ECMAScript~ 别的书很少用语言层面来讲的。不过,另外,你可以看《程序原本》,对很多概念都是讲到的。在这里可以直接下载:
https://github.com/aimingoo/my-ebooks
作者回复: 正是如此😃👍
作者回复: for (let/const ...) “通常情况下”只支持一个块级作用域。
for (let/const ... in/of ...) 会迭代创建作用域副本。
有一眯眯细微的不同哦。 ^^.
作者回复: 是的。^^.
作者回复: :)
+1
作者回复: 不是,单语句也可以实现很复杂的逻辑的。如果单语句使用let/const声明,也一样可以包括逻辑。例如(这个当然不能执行):
if (false) let x = 100, y = x++; // < 这里的x就被使用了
作者回复: 1. 这个问题出在我对“for(let/const...)”这个语法没有展开讲,它跟“for(var...)”,以及后面的“for(let/const ... in/of)”其实都有区别。所以你套用它们的处理方法,结果都有点差异,对你结论会带来干扰。
你读一下ECMA这个部分:
https://tc39.es/ecma262/#sec-for-statement-runtime-semantics-labelledevaluation
注意其中的第三节的具体说明:
> IterationStatement:
for(LexicalDeclarationExpression;Expression)Statement
在后续调用中,简单地说,就是这种情况下for语句会为每次循环创建 CreatePerIterationEnvironment()来产生一个新的IterationEnv。并且thisIterationEnv 与lastIterationEnv 之间会有关联。
2. with({}) let b = 1 这个语法报错,不是因为with()没有作用域,而是它的作用域称为“对象作用域”,而不是“词法作用域”。对象作用域只有在用作global的时候可以接受var和泄露的变量声明,其它情况下,它不能接受“向作用域添加名字”这样的行为——它的名字列表来自于属性名,例如obj.x对吧。
3. eval有一个自己的作用域。
作者回复: 在node里很合理呀。
在node里的second值是:Second1,Second2,Second3
如果你把setTimeout()超时值都改成0,就看得到计算过程了。
0
1
2
Last:0
Second1
Last:1
Second2
Last:2
Second3
作者回复: 很晚才回复你的这个问题,原因是确实不好回复,不知道哪种方法才能有效地解决你的疑惑。
首先,不要相信你写的代码,它并不是最终执行的,引擎会做一些优化,这些优化不是语言本身的,所以也不适用于我们在这个课程中所讨论的。
其次,如果你需要用你所列举的类似代码来(粗略地)检查性能,那么建议把数量提高100~1000倍以上,我运行了你的代码,单个测试case大概才20ms,这种情况下,随便的一个后台进程的波动就影响了结果,有效性成问题。再一次强调,不要用这种方法来检测性能,不要相信你的代码在“字面上的表现出来的”效率。
第三,关于debug版本并track内核,我建议你参考一下下面这两篇,一篇是讲编译的,一篇是讲优化的:
```
https://zhuanlan.zhihu.com/p/25120909
https://segmentfault.com/a/1190000008285728
```
我原来的意思是说,你可能会在原生语言(例如C)这个层面调试和分析内核有困难,所以就向你推荐了一下prepack-core。这个也是一个js引擎,但是是用javascript写的,你分析起来会好一些。——但坦率地说,也并不容易,这个项目还是很难的。在这里:
```
https://github.com/aimingoo/prepack-core
```
第四,我认为我还是应该给你一个简单的分析路径,来解释你的问题。从你的代码来看,你只是想尝试for let/var两种语法到底性能上有什么样的差异。我的建议是这样:
```
var array = Array.from({length:10},(item,i)=>i);
// 例1
var a = [], checker1 = ()=>console.log(a[1] == a[5]); // anything
for (var i in array) setTimeout(()=>a.push(i), 0);
setTimeout(checker1, 0); // true
// 例2
var b = [], checker2 = ()=>console.log(b[1] == b[5]); // anything
for (let i in array) setTimeout(()=>b.push(i), 0);
setTimeout(checker2, 0); // false
```
进一步测试如下:
```
> a
[ '9', '9', '9', '9', '9', '9', '9', '9', '9', '9' ]
> b
[ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ]
```
我们分析一个问题:
* 一、在checker1()中,a[]的元素保存了相同的i值,是不是意味着所有的setTimeout()中a.push()操作其实是工作在一个环境中的?而相对的,由于在checker2()中,b[]保存了不同的i值,那么b.push()就得工作在不同的环境中(从而才能访问不同的i值)。是不是?
* 二、所以,如果在checker2()中每一次迭代都在不同的环境中,是不是说每一次迭代都要消耗一个“创建一个环境”所需要的时间和空间?如果是这样,是不是就说明了`let i`其实效率远低于`var i`?
OK. 最后说明一下,百度查不到东西是正常的,查到才不正常。^^.
作者回复: 你可以在后面用var声明试试😊
作者回复: 不是。
而是这种情况下并没有所谓的“块级作用域(变量作用域)”。
这就是需要仔细地“数”清楚一个语法有几个作用域的原因。
作者回复: 临时死区 = ?
作者回复: 换成let就undefined了。
Var的作用域是在函数或全局,而并不是声明在块级作用域中。
作者回复: 第一,你的案例一在循环体中多了一对大括号,这会导致作用域创建,去掉它才能跟后面的比较。
第二,差异很细微,建议你编译引擎的debug版本然后track内核,或者你可以尝试一个prepack-core这个项目,可以在js源代码级别来分析。
作者回复: 我不太明白你说的。能有一个比较完整的示例么?
作者回复: 1. 你的结论是对的,这里的确是一个静态解析异常。另外,“词法作用域”如果理解为静态的,那么for语句只有一个;如果考虑它在执行期的效果,那么for语句中带有let/const声明时,它会有“迭代次数+1”个。这就好象函数递归调用,函数实例只有一个,但闭包其实是递归次数个一样。它们的原因、性质和效果都是类似的。
2. 对于“for (let/const...”来说,这个语句在处理的时候,所有的IterationEnv的parent指向相同的、外部的环境,也就是指向loopEnv。这个代码在这里:
https://tc39.es/ecma262/#sec-createperiterationenvironment
它是每次迭代都创建一个新的IterationEnv,并根据perIterationBindings[]来抄写上一次迭代环境中的值到新环境。
而对于“for (let/const ... in/of)” 这个语法来说,在实现的逻辑上稍有区别,在这里:
https://tc39.es/ecma262/#sec-runtime-semantics-forin-div-ofbodyevaluation-lhs-stmt-iterator-lhskind-labelset
但是在你的问题上答案是一致的,每次迭代都是iterationEnv的parent指向loopEnv。因为在上面的代码中,迭代是基于迭代器的循环,而每次创建新的iterationEnv时使用的将是相同的outer/parent值,也就是代码中的oldEnv,也就是loopEnv。