我穿越了,文章写于*2020-04-11 21:06
知其然而知其所以然!
最近在群里看到一道题目:
var a = 0;
if (true) {
a = 1;
function a(){}
a = 2;
console.log(a);
}
console.log(a);
这是一道关于JS变量提升(hoisting)的笔试题,可能有点人似曾相识,看似简单,但是错的人还是蛮多的。可以先自测一下~
最终答案:2,1(ES6中) 2,2(ES3中)。
一、出乎意料的结果
关于JS变量提升(hoisting)的概念我在这里就不多说了,不懂的可以自行查阅MDN:developer.mozilla.org/zh-CN/docs/…
先从ES6角度来分析,即浏览器支持块级作用域的情况下(因为有大部分人不知道该代码在ES3中的执行情况)。
首先来看一下一些同学得出的答案:2,0。
那么他是这么想的,执行逻辑相当于:
// EC(G) 全局执行环境
//变量提升
// var a;
var a = 0;
if (true) {
// EC(block) 块级作用域
// 函数声明提升(提升声明a并且初始化为一个函数)
// function a(){}
function a(){}
a = 1;
a = 2;
console.log(a);// 打印2
}
console.log(a);// 打印 0
没错,在JS执行过程中,{} 会形成一个块级作用域,并且对function生效。但是事实上代码真的是这样执行的吗?
我们直接在Chrome下测试一下。
显然,代码并不是像上面所说的那样。为了更能清楚的解释这个原因,我们切换到ES3(IE10以下) 中去执行一下看看结果。
显然,明显出现不同的结果了吧,我们一起来分析分析。
在ES3中,该代码执行等价于:
//EC(G) 全局执行环境
//变量声明
// var a;
//函数声明提升并初始化
//function a(){}
var a = 0;
//if (true) {
a = 1;
function a(){}
a = 2;
console.log(a);// 打印 2
//}
console.log(a);// 打印 2
然而,在ES迭代过程中,并不是把旧的执行机制完全丢弃,而是采用进行向后兼容的方式进行更新,所以在Chrome(ES6)中执行的时候还存在非常重要的一步兼容操作:
//EC(G) 全局执行环境
//变量声明
// var a;
//函数声明提升(仅提升声明a,并不会把a初始化为函数)-------tips1---------
// function a
var a = 0;
if (true) {
// EC(block) 块级作用域
// 函数声明提升
// function a(){}
a = 1;// 重新对a进行赋值
//兼容操作:执行完14行的时候,会将之前对a的所有修改操作映射到EC(G)中,
//用于兼容ES3的方式,那么此时全局的a = 1。
function a(){};//-------------tips2---------------
a = 2;//这里修改的是当前EC(block)的a
console.log(a);// 2
}
console.log(a);// 1
上面有两个标注为----tips----的地方容易会让你产生疑虑。
如果你对第一个tips1产生疑虑,那么请看这段代码:(你可以手动尝试)
//var a = 0;
//我去掉这段代码console.log(a);
//打印 undefined,说明tips1成立。
if(true){
a = 1;
function a(){}
a = 2;
console.log(a)// 打印 2
}
console.log(a);// 打印 1
根据执行结果显示,tips1成立。
如果你对tips2产生疑虑,那么请看下面代码断点调试右边scope的变化:
第一步:Global中a=0,此时只有Global,即EC(G)。
第二步:创建EC(block),此时函数a已经提升到最前面, Global中a=0。
第三步:将EC(block)中的a赋值为1, Global中a=0,图略。
第四步:当执行完15行代码的时候,Global中的a已经修改为1。
第五步:将EC(block)中的a赋值为2,之后修改的a都不会对全局a造成影响。Global中a=1。
第六步:销毁EC(block),Global中a=1。
所以在执行function a(){}后会对当前函数作用域或者全局作用域的a产生影响,进行向后兼容操作。如果存在多条该语句,那么则会同步多次修改。
二、差异点
上面详细分析了在ES3和ES6的环境下JS在hoisting方面的不同点。有人可能会有疑虑,为什么不拿ES5进行分析?
简单原因就是ES5较ES3没有实质性的变化。
需要注意的是,上面的比较都是针对块级作用域进行分析,在ES6中,在全局作用域和函数作用域的分类上新增块级作用域 ,而块级作用域仅对function、let、const生效,对var则不存在限制。而在ES3中,对var的操作只需要区分函数作用域和全局作用域即可。
另外,可能有些同学还会见过类似这样的题目:(考察hoisting工作时机)
console.log(a);
if (false) {
a = 1;
function a(){}
a = 2;
console.log(a);
}
这里考察的是, JS hoisting是在编译(词法分析)的时候进行,而不是在代码执行的时候,如何验证?请看上面代码在ES3和ES6中的表现:
Chrome中:
IE10以下:
三、总结
以上就是个人对比不同es版本下hoisting表现不同差异的分析和总结,这类题目在面试中很常见,或许能够助你一臂之力~