• Y
    2019-11-20
    老师,在es6中,其实for只要写大括号就代表着块级作用域。所以只要写大括号,不管用let 还是 var,一定是会创建相应循环数量的块级作用域的。
    如果不用大括号,在for中使用了let,也会创建相应循环数量的块级作用域。
    也就是说,可以提高性能的唯一情况只有(符合业务逻辑的情况下),循环体是单行语句就不使用大括号且for中使用var。

    作者回复: 是的。
    赞,好几个赞。^^.

     5
     15
  • zcdll
    2019-11-20
    看不懂。。。第一个 switch 那个例子都看不懂。。

    作者回复: case 'b' 永远执行不到,但它里面的x却已经声明了,并且导致case 'a'中的代码无法访问到外部的`x = 100`。

    这说明case 'a'和case 'b'使用了同一个闭包。

     2
     5
  • Wiggle Wiggle
    2019-11-22
    词法、词法作用域、语法元素……等等,这些概念特别模糊,老师有什么推荐的书吗?

    作者回复: 《JavaScript语言精髓与编程实践》第三版。^^.
    已经交稿,大概快要出了。

    如果急用,可以看ECMAScript~ 别的书很少用语言层面来讲的。不过,另外,你可以看《程序原本》,对很多概念都是讲到的。在这里可以直接下载:
    https://github.com/aimingoo/my-ebooks

    
     4
  • Elmer
    2019-12-08
    从语言设计的原则上来看,越少作用域的执行环境调度效率也就越高,执行时的性能也就越好。
    单语句如果支持变量声明,相当于需要支持为iteration env新增子作用域,降低了效率?
    如果需要完全可以自己写{} 来强制生成一个子作用域
    不知道这样说对不对

    作者回复: 正是如此😃👍

    
     3
  • Marvin
    2019-11-26
    如果使用let /const 声明for循环语句,会迭代创建作用域副本。那么不是和文中的:
    对于支持“let/const”的 for 语句来说)“通常情况下”只支持一个块级作用域这句话相矛盾么?

    作者回复: for (let/const ...) “通常情况下”只支持一个块级作用域。
    for (let/const ... in/of ...) 会迭代创建作用域副本。

    有一眯眯细微的不同哦。 ^^.

    
     3
  • 陈伟
    2019-11-21
    假设允许的话,没有块语句创建的iterationEnv的子作用域,let声明就直接在iterationEnv作用域中,会每次循环重复声明。

    作者回复: 是的。^^.

    
     3
  • westfall
    2019-11-20
    因为单语句没有块级作用域,而词法声明是不可覆盖的,单语句后面的词法声明会存在潜在的冲突。

    作者回复: :)
    +1

    
     3
  • Y
    2019-11-20
    既然是单语句就说明只有一句话,如果就一句话,还是词法声明,那就会创建一个块级作用域,但是因为是单语句,那一定就没有地方会用到这个声明了。那这个声明就是没有意义的。所以js为了避免这种没有意义的声明,就会直接报错。是这样嘛

    作者回复: 不是,单语句也可以实现很复杂的逻辑的。如果单语句使用let/const声明,也一样可以包括逻辑。例如(这个当然不能执行):

    if (false) let x = 100, y = x++; // < 这里的x就被使用了

    
     3
  • 海绵薇薇
    2019-11-23
    hello,老师好,一如既往有许多问题等待解答:)

    for(let/const ...) ... 这个语句有两个词法作用域,分别是 forEnv 和 loopEnv。还有一个概念是iterationEnv,这个是迭代时生成的loopEnv的副本。

    对于forEnv和loopEnv的范围我不是很清楚,请老师指点。

    for(let i = 0; i < 10; i++)

    ​    setTimeout(() => console.log(i))

    1 如上代码,let i 声明的 i 在forEnv还是在loopEnv / iterationEnv里?

        1.1 如果在loopEnv / iterationEnv里那么forEnv看起来就没啥用了
        
        1.2 如果在forEnv(文章中说let只会执行一次,并且forEnv是lopEnv的上级),那么按理说console.log打印出来的都是11(参考于:晓小东)

    2 关于单语 let a = 1 报错问题

        2.1 如果是单语句中词法声明被重复有问题,那么with({}) let b = 1 这个报错就解释不通了。上面是说with有自己的块作用域,这个词法声明是在自己块语句中做的,并不会和别人冲突

        2.2 同样的情况存在于for(let a...) ... 中,for也有自己的作用域,并且每次循环都会生成新的副本,也不应该存在重复问题

    3 关于上面提到的eval
        eval('let a = 1'); console.log(a) // 报错
        eval是不是自己也有一个作用域?

    期待:)
    展开

    作者回复: 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有一个自己的作用域。

    
     2
  • 晓小东
    2019-11-21

    老师您看下这段代码, 我在Chrome 打印有点不符合直觉, Second 最终打印的应该是2, 为什么还是1,2, 3;

    for (let i = 0; i < 3; i ++, setTimeout(() => console.log("Second" + i), 20))
        console.log(i), setTimeout(() => console.log('Last:' + i), 30);

    0, 1, 2
    Second: 0, 1, 2
    Last: 0, 1, 2
    展开

    作者回复: 在node里很合理呀。
    在node里的second值是:Second1,Second2,Second3

    如果你把setTimeout()超时值都改成0,就看得到计算过程了。

    0
    1
    2
    Last:0
    Second1
    Last:1
    Second2
    Last:2
    Second3

    
     2
  • wDaLian
    2020-01-12
    const array = Array.from({length:100000},(item,i)=>i)

        // 案例一
        console.time('a')
        const cc = []
        for(let i in array){
            cc.push(i)
        }
        console.log(cc)
        console.timeEnd('a')

        // 案例二
        console.time('b')
        const ccc = []
        for(var i in array){
            ccc.push(i)
        }
        console.log(ccc)
        console.timeEnd('b')
        
        // 案例三
        console.time('c')
        const cccv = []
        for(let i in array)
            cccv.push(i);
        console.log(cccv)
        console.timeEnd('c')

    1.老师你上次的评论我没看懂,第一我案例一和案例三是为了做区分所以案例一有大括号的
    2.编译引擎的debug版本然后track内核,或者你可以尝试一个prepack-core这个项目,这两个东西是啥 我百度也没查到
    3.老师你讲的都是概念的,我就想看到一个肉眼的案例然后根据概念消化,要不现在根本就是这个for循环到底应该咋写我都懵了
    展开

    作者回复: 很晚才回复你的这个问题,原因是确实不好回复,不知道哪种方法才能有效地解决你的疑惑。

    首先,不要相信你写的代码,它并不是最终执行的,引擎会做一些优化,这些优化不是语言本身的,所以也不适用于我们在这个课程中所讨论的。

    其次,如果你需要用你所列举的类似代码来(粗略地)检查性能,那么建议把数量提高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. 最后说明一下,百度查不到东西是正常的,查到才不正常。^^.

    
     1
  • Fans
    2019-11-21
    看到标题感觉的终于有一个可能会看的懂了
     1
     1
  • 许童童
    2019-11-20
    为什么单语句(single-statement)中不能出现词法声明( lexical declaration )?
    我觉得应该是语法规定 单语句后面需要一个表达式,而一个声明语句是不行的。

    作者回复: 你可以在后面用var声明试试😊

    
     1
  • qqq
    2019-11-20
    单语句对应的是变量作用域,不能出现词法声明吗

    作者回复: 不是。

    而是这种情况下并没有所谓的“块级作用域(变量作用域)”。

    这就是需要仔细地“数”清楚一个语法有几个作用域的原因。

    
     1
  • Chao
    2019-11-20
    临时死区使得 通过let / const 定义的变量。 在定义之前调用报错。

    作者回复: 临时死区 = ?

    
     1
  • antimony
    2020-02-02
    var x = 100, c = 'a';
    switch (c) {
      case 'a':
        console.log(x); // 100
        break;
      case 'b':
        var x = 200;
        break;
    }
    爱民老师,能请教一下上面那段代码为什么在chrome中会输出100呢,在我的理解中应该输出undefined才对啊,因为switch 中的x不是应该被提升了吗,望解惑谢谢。
    展开

    作者回复: 换成let就undefined了。
    Var的作用域是在函数或全局,而并不是声明在块级作用域中。

     1
    
  • wDaLian
    2020-01-11
    有点懵,这个节省指的是时间上的节省 还是空间上的节省,我计算了事件发现let 和var 基本没啥区别速度上
    const array = Array.from({length:100000},(item,i)=>i)

        // 案例一
        console.time('a')
        const cc = []
        for(let i in array){
            cc.push(i)
        }
        console.log(cc)
        console.timeEnd('a')

        // 案例二
        console.time('b')
        const ccc = []
        for(var i in array){
            ccc.push(i)
        }
        console.log(ccc)
        console.timeEnd('b')
        
        // 案例三
        console.time('c')
        const cccv = []
        for(let i in array)
            cccv.push(i);
        console.log(cccv)
        console.timeEnd('c')
    展开

    作者回复: 第一,你的案例一在循环体中多了一对大括号,这会导致作用域创建,去掉它才能跟后面的比较。

    第二,差异很细微,建议你编译引擎的debug版本然后track内核,或者你可以尝试一个prepack-core这个项目,可以在js源代码级别来分析。

    
    
  • 伪装
    2019-12-29
    越来越感觉js 是最烂的语言 没有之一 设计上是一塌糊涂
    
    
  • Elmer
    2019-12-27
    有个地方不懂。。
    let i在forenv中,i = ... 在iterationenv中
    let i
    {const c=1; i=2,;setTimeout(() => {console.log(i)}, 100)};
    {const c=2; i= 3; setTimeout(() => {console.log(i)}, 50)};
    类似这种? 可这样输出的都是3呀
    展开

    作者回复: 我不太明白你说的。能有一个比较完整的示例么?

     4
    
  • leslee
    2019-12-17
    老师好, 跟随老师学到了第10讲了, 但是回头看了一下, 发现我并没有真正的写到东西, 在看了老师的加餐后, 醒悟了, 并没有追求进度, 所以回过头来重新看第一章, 每一讲都看了不下三遍, 算是学到了一些知识, 在这一章的最后一讲, 还有些疑问, 望老师解答. 感谢

    `for(let inForEnv i in {}) let inLoopEnv;`

    1. 上文说在迭代的过程中会为后面的 let inLoopEnv 循环体创建相应数量的作用域, 但是又说使用了 let 的for单语句 通常只有一个块级作用域, 我理解的是 '创建相应数量的作用域' 是动态的,是运行时的 跟词法作用域无关, 而 '只有一个块级作用域' 是词法作用域, 也就是说 let inForEnv 跟 let inLoopEnv 在同一个词法作用域了, 每次迭代let都会冲突, 所以不允许在单语句中使用let词法声明,`Uncaught SyntaxError: Lexical declaration cannot appear in a single-statement context` 所以这个错其实是静态解析的错是么

    2: forEnv 是 loopEnv 的外部环境, 那么 IterationEnv 的外部环境是否是loopEnv , 上面所说 `创建了第二个作用域的无数个副本` 那这几个环境的层级是怎样的, 是 forEnv -> loopEnv -> iterationEnv 还是 forEnv-> loopEnv/iterationEnv
    展开

    作者回复: 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。

    
    
我们在线,来聊聊吧