我们已经讲解了JavaScript中变量提升的相关内容,正是由于JavaScript存在变量提升这种特性,从而导致了很多与直觉不符的代码,正是由于JavaScript存在变量提升这种特性,从而导致了很多与直觉不符的代码,这也是JavaScript的一个重要设计缺陷这也是JavaScript的一个。
两套机制还是同时运行在“一套”系统中的 var体系的、let cost作用域 体系的,为了向下兼容所以需要同时兼容两套。
通过级作用域并配合let和const关键字用域并配合let和const关键 来避免变量提升。
ES6之前是不支持块级作用域的
因为当初设计这⻔语言的时候,并没有想到JavaScript会火起来,所以只是按照最简单的方式来设计。没有了块级作用域,再把作用域内部的变量统一提升无疑是最快速、最简单的设计,不过这也直接导致了函数中的变量无论是在哪里声明的,在编译阶段都会被提取到执行上下文的变量环境中,所以这些变量在整个函数体内部的任何地方都是能被访问的,这也就是JavaScript中的变量提升。
变量提升所带来的问题 1.变量容易在不被察觉的情况下被覆盖掉
var a = 1;
function bx() {
console.log(a);
if(0){
var a = 1; // 变量提升了,没有块级作用域。
}
}
bx();
// a为undefined
执行上下文分析最后的结果。
相信做过JavaScript开发的同学都能轻松回答出来答案:“当然是先使用函数执行上下文里面的变量先使用函数执行上下文里面的变量啦!”的确是这样,这是因为在函数执行过程中,JavaScript会优先从当前的执行上下文中查找变量,由于变量提升,当前的执行上下文中就包含了变量myname,而值是undefined,所以获取到的myname的值就
是undefined。
这输出的结果和其他大部分支持块级作用域的语言都不一样,比如上面C语言输出的就是全局变量,所以这会很容易造成误解,特别是在你会一些其他语言的基础之上,再来学习JavaScript,你会觉得这种结果很不自然。
2.本应该销毁的没有销毁
{
for(var i = 0; i < 10; i++){}
console.log(i); // 10
}
ES6是如何解决变量提升带来的缺陷 上面我们介绍了变量提升而带来的一系列问题,为了解决这些问题,ES6引入了let和const关键字ES6引入了let和const关键字,从而使JavaScript也能像其他语言一样拥有了块级作用域。
let a = 1;
const b = 2;
// let 和 cost 修饰都会产生块级作用域
function varTest() {
var x = 1;
if (true) {
var x = 2; // 同样的变量
console.log(x); // 2
}
console.log(x); // 2 变量提升导致了这里也是2
}
改造让其支持块级作用域
function varTest() {
let x = 1;
if (true) {
let x = 2; // 块级作用域
console.log(x); // 2
}
console.log(x); // 1 块级作用域
}
执行这段代码,其输出结果就和我们的预期是一致的。这是因为let关键字是支持块级作用域的,所以在编译阶段,JavaScript引擎并不会把if块中通过let声明的变量存放到变量环境中,这也就意味着在if块通过let声明的关键字,并不会提升到全函数可⻅。所以在if块之内打印出来的值是2,跳出语块之后,打印出来的值就是1了。这种就非常符合我们的编程习惯了:作用块内声明的变量不影响块外面的变量符
ES6是如何做到既要支持变量提升的特性,又要支持块级作用域的呢?
执行上下文来分析如何支持的 【变量环境】【词法环境】 【可执行代码】
function foo(){
var a = 1
let b = 2
{
let b = 3
var c = 4
let d = 5
console.log(a)
console.log(b)
}
console.log(b)
console.log(c)
console.log(d)
}
foo()
/**
* 变量环境
* a = undefined
* c = undefined
* 词法环境
b = undefined
d = undefined
块的词法环境
b = 2
*
*/
函数内部通过var声明的变量,在编译阶段全都被存放到变量环境变量环境里面了
通过let声明的变量,在编译阶段会被存放到词法环境(LexicalEnvironment)词法环境(LexicalEnvironment)中。
在函数的作用域内部,通过let声明的变量并没有被存放到词法环境中。 注意这个时候在没有执行的时候没有开新栈
接下来,第二步继续执行代码第二步继续执行代码,当执行到代码块里面时,变量环境中a的值已经被设置成了1,词法环境中b的值已经被设置成了2,这时候函数的执行上下文就如下图所示:
注意词法环境的变化 里面的块执行到的时候会先编译一下创建一个新的词法环境用来保存块的词法内容
从图中可以看出,当进入函数的作用域块时,作用域块中通过let声明的变量,会被存放在词法环境的一个单独的区域中,这个区域中的变量并不影响作用域块外面的变量,比如在作用域外面声明了变量b,在该作用域块内部也声明了变量b,当执行到作用域内部时,它们都是独立的存在。
其实,在词法环境内部,维护了一个小型栈结构,栈底是函数最外层的变量,进入一个作用域块后,就会把该作用域块内部的变量压到栈顶;当作用域执行完成之后,该作用域的信息就会从栈顶弹出,这就是词法环境的结构。需要注意下,我这里所讲的变量是指通过let或者const声明的变量
理解作用域是核心。 作用域是指在程序中定义变量的区域,该位置决定了变量的生命周期。通俗地理解,作用域就是变量与函数的可访问范围,即作用域控制着变量和函数的可⻅性和生命周期。的可访问范围,即作用域控制着变量和函数的可⻅性和生命周期。
块执行结束后。
通过上面的分析,想必你已经理解了词法环境的结构和工作机制,块级作用域就是通过词法环境的栈结构来实现的,而变量提升是通过变量环境来实现,通过这两者的结合,JavaScript引擎也就同时支持了变量提升和块级作用域了。
总结补充 let const 的暂时性死去不能在声明之前访问!!!
作用域和执行上下文的区别
作用域和执行上下文是两个不同的的概念
JavaScript 属于解释型语言,JavaScript 的执行分为:解释和执行两个阶段,这两个阶段所做的事并不一样:解释阶段:词法分析,语法分析,作用域规则确定。执行阶段:创建执行上下文,执行函数代码,垃圾回收。 他们最大的区别是:作用域在解释阶段确定的,并且不会改变;执行上下文在运行阶段确定的,随时可能改变