09 | 块级作用域:var缺陷以及为什么要引入let和const?
09 | 块级作用域:var缺陷以及为什么要引入let和const?
讲述:李兵
时长14:20大小13.12M
作用域(scope)
变量提升所带来的问题
1. 变量容易在不被察觉的情况下被覆盖掉
2. 本应销毁的变量没有被销毁
ES6 是如何解决变量提升带来的缺陷
JavaScript 是如何支持块级作用域的
总结
思考时间
赞 52
提建议
精选留言(106)
- coolseaman2019-08-24【最终打印结果】:VM6277:3 Uncaught ReferenceError: Cannot access 'myname' before initialization 【分析原因】:在块作用域内,let声明的变量被提升,但变量只是创建被提升,初始化并没有被提升,在初始化之前使用变量,就会形成一个暂时性死区。 【拓展】 var的创建和初始化被提升,赋值不会被提升。 let的创建被提升,初始化和赋值不会被提升。 function的创建、初始化和赋值均会被提升。展开
作者回复: 很好,这个答案大家可以参考下
共 36 条评论507 - 朙2019-08-24这篇真的是神作啊。 有一个疑问,在abcd那个例子里,第一步<编译并创建执行上下文>的图里并没有块级作用域的b=undefined; d=undefined。而在第二步里<继续执行代码>的图中才出现b=undefined; d=undefined。想问下这个块级作用域的b=undefined; d=undefined是不是应该在第一步的编译阶段里就有。还是说在执行阶段像函数那样,块级作用域会有一个自己的编译阶段展开
作者回复: 执行函数时才有进行编译,抽象语法树(AST)在进入函数阶段就生成了,并且函数内部作用域是已经明确了,所以进入块级作用域不会有编译过程,只不过通过let或者const声明的变量会在进入块级作用域的时被创建,但是在该变量没有赋值之前,引用该变量JavaScript引擎会抛出错误---这就是“暂时性死区”
共 10 条评论65 - YBB2019-08-26有个问题,在一个块级作用域中,let和const声明的变量是在编译阶段被压入栈中还是执行阶段被压入栈中?在文中的表述来看,第一个let声明的变量是在编译阶段就压入栈中的,但是后面的变量又感觉是在执行是压入栈中,有点混乱。
作者回复: 对的,你的理解没错 函数只会在第一次执行的时候被编译,所以编译时变量环境和词法环境最顶层数据已经确定了。 当执行到块级作用域的时候,块级作用域中通过let和const申明的变量会被追加到词法环境中,当这个块执行结束之后,追加到词法作用域的内容又会销毁掉。
共 4 条评论60 - Tim2020-01-12看得很生气,全篇文章不提变量的「创建」「初始化」「赋值」这三种区别,把创建和初始化揉在一起了,也是看了精选留言里第一条评论之后Google才查找到,否则刚开始我真的不理解为啥都已经在词法环境找到了变量却报错了!按照这种理论的话,是否说明词法环境只有变量,并没有等于undefined? 真的不需要更新一下吗?????
作者回复: 变量初始化和创建再上上一节《变量提升:JavaScript代码是按顺序执行的吗?》中已经讲过了, 我们将到了一个变量编译阶段和执行阶段分别要做那些事情。 这一节主要是将var和let的区别以及底层实现机制的,我看你的疑问是下面这个问题: function test(){ console.log(a) let a = 7; } test() 执行test的时候,编译阶段a已经在内存中,为什么提前访问不了? 这主要是因为V8虚拟机做了限制,虽然a在内存中,但是当你在let a 之前访问a时,根据ECMAScript定义,虚拟机会阻止的访问! 如果你还有其它具体的问题,欢迎继续提出!
共 11 条评论51 - 晓小东2019-08-25在ES3开始,try /catch 分句结构中也具有块作用域。补充……
作者回复: 赞
共 3 条评论26 - 李懂2019-08-24执行上下文是在编译时创建的,在执行代码的时候已经有词法环境了,而且变量已经默认初始化了undefiend,为什么还会存在暂时性死区,老师解答一下!
作者回复: 暂时性死去是语法规定的,也就是说虽然通过let声明的变量已经在词法环境中了,但是在没有赋值之前,访问该变量JavaScript引擎就会抛出一个错误。
共 7 条评论18 - …Lucky2019-09-04老师,按照最后的思考题。let,const会在编译阶段创建,但不赋值。但是上面几个图中都是直接赋值的undefined。这是否矛盾 ?共 5 条评论9
- William2019-08-24第二步,继续执行代码。 这张图我觉得有错误,当进入foo函数内部的代码块之后,并没有了编译阶段,此时,新创建的栈顶块级作用域的内容为空,而并没有 b = undefined 和 d = undefined 两项内容。 执行完 let b = 3 之后,分配内存,块级作用域出现 b = 3 一项。 执行 let d = 5 之后,为d分配内存,栈顶块级作用域增加一项 d = 5。
作者回复: 使用let/const声明的变量,伴随着词法环境被创建,但只有在变量的词法绑定(LexicalBinding)已经被求值运算后,才能够被访问。 你也可以在let b声明之前断点下,看看scope中的值有没有,你会看scope中的值已经存在了。
共 8 条评论9 - 爱吃锅巴的沐泡2019-09-01对文中foo()函数的分析和一些问题: 我调试了一下,①断点打在 let b = 2,此时的scope中只有local:a = 1,b = undefined,c = undefined;并没有block,这应该说明js是解释性语言,一句一执行的。 ②当断点走到 let b = 3时,这时进入了作用域,scope中有了block:b = undefined,d = undefined,这应该说明在进入作用域之前AST已经生成,并确定了作用域的范围。 问题:1、老师提到在进入作用域时let声明的变量被创建,结合断点可以证明,那么是不是说 let声明的变量在该作用域内提升了,但没有提升赋值语句?因为在②处已经有了d = undefined。 问题:2、把foo()中的作用域变形如下: { let b = 3 console.log(d) var c = 4 let d = 5 console.log(a) console.log(b) } 当断点走到 let b = 3处,scope的block中只有b = undefined,并没有d = undefined,是因为“暂时性死区”是js在语法上的设置,防止访问声明前的变量,而在进入作用域之前就会有语法树的生成,所以在编译到console.log(d)时,遇到错误,所以没有在词法环境中创建变量d。这样分析是否正确?展开共 1 条评论7
- 小锅锅2019-08-25老师,听你对比了c语言,既然let const存在暂时性死区,那么c语言的变量也存在同样的暂时性死区报错吗?
作者回复: c语言都没这概念,因为你在定义之前使用一个变量,首先过不了c语言的编译
7 - 朙2019-08-24if(0){ var myname = " 极客邦 "} 这段代码里的if条件是false很有意思。是说编译阶段不管if会不会执行。里面的代码都会编译,因此这里的myname变量提升,从而导致上面的console.log(myname)输出undefined吗? 另外let 声明的变量会提升吗?
作者回复: 对的,第一个分析的没问题 第二个let不会产生变量提升
共 2 条评论7 - 宗麒麟2020-04-20精选留言也好多精品啊,老师看到应该也很欣慰吧共 1 条评论5
- 阿郑2020-03-28ES6明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。 总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称TDZ) 因此,思考题中的那段代码,执行会报错: Uncaught SyntaxError: Identifier 'myname' has already been declared at <anonymous>:1:1展开5
- sugar2020-01-31想问一下,js代码中这种写法:{ var a = 100; },和不加外边这层大括号,有什么区别吗?我看到vue的源码中有很多这样的用法,却没明白目的是啥共 2 条评论4
- love star2020-03-23第三次看老师的文章,之前好多点都忽略了,五体投地,惊为天人。 复盘的重要性。 希望 V8 也能给我知识栈一样的扩充共 1 条评论3
- hzj.2019-08-25这就是 let 的暂时死区~3
- 风语菡2020-06-02function foo (){ let myname= '极客时间' { console.log(myname) let myname= '极客邦' } } foo(); 执行上面代码输出的结果是:VM5498:5 Uncaught ReferenceError: Cannot access 'myname' before initialization at foo (<anonymous>:5:15) at <anonymous>:9:1 foo @ VM5498:5 (anonymous) @ VM5498:9 若执行下面代码; let myname= '极客时间' { console.log(myname) let myname= '极客邦' } 则输出的结果是:Uncaught SyntaxError: Identifier 'myname' has already been declared 想请教下,为嘛这两段代码报的错不一致,在函数作用域内,报的是referenceError,而在全局作用域报的是syntaxError。用词法环境分析,有点不太明白展开共 2 条评论3
- 爱吃锅巴的沐泡2020-01-14老师您好,对于Y s留言用户的问题,我想接着问: ES5标准文档中规定, 执行环境包括:词法环境、变量环境、this绑定。 其中执行环境的词法环境和变量环境组件始终为词法环境对象。当创建一个执行环境时,其词法环境组件和变量环境组件最初是同一个值。在该执行环境相关联的代码的执行过程中,变量环境组件永远不变,而词法环境组件有可能改变。 问题1:变量环境组件永远不变,而词法环境组件有可能改变。 这里您给出的解释是说词法环境里会有块级作用域的进入和退出,但这是ES5的规范呀,还没有作用域的概念呀,这里不解? 变量环境组件为什么永久不变? 问题2:创建执行环境时,变量环境和词法环境最初是同一个值,想知道这个值具体是指什么值? 问题3:我理解的ES5中 变量环境中存储的是提升的变量和函数声明(都是类似var xx=undefined; function funname(){}),所以变量环境是不变的,在执行过程中变量的变化是在词法环境中体现的,词法环境管理着静态作用域的。 到了ES6,有了let和const,是不是就把原来词法环境中变化的变量转移到了变量环境中,把let和const的变化放到了现在的词法环境中。 希望老师能举个例子或者画个图,详细分析一下在ES5中变量环境和词法环境中变量的变化? ES5和ES6的执行环境的区别是啥? 问题有点多,但是网上写的和规范内容都不一样,感觉不靠谱,请老师解答!展开共 2 条评论2
- -_-|||2019-12-29文中”通过 let 声明的变量,在编译阶段会被存放到词法环境(Lexical Environment)中。 在函数的作用域内部,通过 let 声明的变量并没有被存放到词法环境中。”,第二段”通过 let 声明的变量并没有被存放到词法环境中”,可是上图中画的声明的变量b已经放到词法环境中了,只不过没有赋值,所以是不是写错了,应该是没有赋值,而不是没有放到词法环境中。1
- Geek_East2019-12-06我想,理解execution context和scope的区别是理解这个问题的一个关键;很多时候执行上下文和作用域都混着说
作者回复: 这是两样不同的东西,一个表示一个表示函数运行时的上下文,一个表示词法作用域! 我会在下篇介绍V8的专栏中详细分析这块内容!
1