web前端高级 – 两道变态到吐血的关于变量提升的大厂面试题

之所以说是两道“变态”的面试题,因为这两类JavaScript代码在运行时不按常理出牌,究其根本原因就是:在运行的过程中,会多出一个私有的块级上下文,从而导致运行结果与常规不同。下面我们就来具体分析一下;

变态一

{
	function foo(){}
	foo = 1
}
console.log(foo);

上面这段代码在老版本浏览器(IE10以下)和在新版本浏览器中运行出来的结果是完全不同。先不管最终运行结果是什么,我们先来分析一下在新老版本中的运行步骤

老版本浏览器:
首先还是开辟一块栈内存(ECStack)供代码执行接下来形成一个全局上下文EC(G),然后变量提升,声明并定义一个函数foo代码执行,因为老版本浏览器不存在块级上下文,所以这里代码块中的代码也是直接在全局上下文中执行。第一句代码在变量提升时已经处理过,直接跳过执行第二句代码“foo=1”,这里foo由函数被变更为值1执行console.log(foo) 输出值1 新版本浏览器:
第一步也是先开辟栈内存(ECStack)供代码执行第二步形成全局上下文EC(G),然后变量提升,这里就开始出现不同了。这里有这么一套运行机制:
在全局上下文中,除了函数/对象以外的大括号,如果在其它大括号(循环体、判断体、代码块等)中出现let/const/function,则会再单独形成一个块级私有上下文如果在除函数/对象以外的其它大括号中出现function时,则变量提升只是声明不再定义 第三步变量提升:依照上面的第二条规则,代码块中出现了function,所以在全局上下文中只声明一个foo,并不定义第四步代码执行:依照第二步第一条规则,代码块中出现了function,所以这里会单独再形成一个私有块级上下文,而该块级上下文的上级上下文就是全局上下文。私有上下文中代码执行:在新形成的块级上下文中同样也有变量提升,于是在私有上下文中函数foo又被提升了一次,并且这次是声明+定义。这里开始出现变态机制:变量提升过后,当代码执行到function foo(){}时,按常理在变量提升阶段已经处理过了,这里就不需要再处理了,但是为了同时兼容ES3/ES5和ES6,这里会产生一个特殊的机制:
把当前这行代码【之前】对foo的所有操作,都映射给全局一份,但是之后再对foo操作则都认为是自己私有的了 在全局变量提升时foo仅仅声明没有定义,依照上面的规则:代码执行到function foo(){}之前是在块级上下文中变量提=》声明定义了foo,所以这里定义的foo会再给全局一份,此时全局的foo也就被定义了。继续执行:在私有上下文中foo被赋值为1,这时则认为是操作的是私有的foo,跟全局就没有关系了。而最终全局中的foo依然是一个函数。打印输出f foo(){}

web前端高级 – 两道变态到吐血的关于变量提升的大厂面试题

将上面的代码简单改造一下,结果又会不一样了,如下我们在foo=1后面又加了一句 “function foo(){}”,而最终的运行结果就变为1了。
这是因为:无论在全局上下文还是在私有块级上下文中都会进行两次变量提升,执行步骤与上面的基本一致,但是当代码执行到第二个“function foo(){}”时,依然会遵照上面的规则:“把当前这行代码【之前】对foo的所有操作,都映射给全局一份”,而当前这行代码之前则是给foo赋值为1,所以全局中的foo值也被映射为1了

{
	function foo(){}
	foo = 1
	function foo(){}
}
console.log(foo);

变态二

var x = 1;
function func(x, y = function anonmymous1(){x=2}){
	x = 3;
	y();
	console.log(x);
}
console.log(x);

这道题并不变态,按照我们正常的逻辑执行就可以了;需要注意的是:形参y的值是一个函数anonmymous1,并且是在func的上下文中创建的,所以anonmymous1的作用域就是func的上下文,在func的上下文中有变量x值由5 变为3,当执行函数y时,在函数y中并不存在变量x,所以到上级上下为中寻找,也就是到func的上下文中查找,发现有变量x,然后将其值改为2,所以在func里输出x的值为2。而全局变量x的值仍为1
但是下面我们把上述代码稍作改动,立马就变成了一道变态题:我们只需要在func中的x=3前加一个var,运行机制立马就不一样了。看下面:

var x = 1;
function func(x, y = function anonmymous1(){x=2}){
	var x = 3;
	y();
	console.log(x);
}
console.log(x);

仅仅加了个var就造成了不同的结果,这是因为在ECMA中有如下规范:

如果函数中定义了形参,并且给 形参设置了默认值(不管值为啥,也不管是否传递了实参)并且在函数体中出现了基于let/var/const等声明的变量(let和const声明的变量不能与形参同名,否则会报错)这样在函数执行时,除了函数本身会形成 一个私有上下文 外,还会基于函数体中的代码形成一个 “全新的块级上下文”,这个块级上下文的作用域就是函数形成是私有上下文,并且函数中的代码不再执行,而是拿到新的块级上下文中去执行
如果块级上下文中声明的变量和函数私有上下文中的形参名称一致,则在块级上下文执行代码之前,会把形参变量值同步给块级上下文中的变量。

下面我们来初步分析:

首先还是开辟栈内存(ECStack)供代码执行形成全局上下文EC(G);变量提升,声明变量x,声明并定义函数func全局上下文中代码执行:x赋值为1,调用函数func函数func执行:形成一个私有上下文EC(FUNC)
在私有上下文中:初始化作用域链<EC(FUNC), EC(G)>初始化this形参赋值,x赋值为5;创建函数anonymous1(作用域为:当前上下文EC(FUNC)) 该函数使命到此结束,同时形成一个新的私有块级上下文EC(B)(作用域为:当前上下文EC(FUNC))本来函数中的代码都将会拿到新的块级上下文EC(B)中执行;在块级上下文中同样要初始化作用域链,变量提升(声明私有变量x,同时将上级上下文中x的值同步过来),然后是代码执行块级上下文中代码执行:x赋值为3,调用函数y,此时y并不是当前上下文中的变量,于是向上级上下文EC(FUNC)中查找函数y(anonymous1)执行:形成私有上下文,发现在y形成的私有上下文中并不存在变量x,同样向上级上下文EC(FUNC)中查找,并将上级上下文中的x值改为2。函数y执行结束,内存释放,重新回到块级上下文EC(B)中,执行代码“console.log(x);”,发现x就是当前上下文中的私有变量(值为3)于是直接输出结果3.函数执行结束,回到全局上下文,整个过程中全局上下文中的x并未受到影响,所以还是原来的值1

web前端高级 – 两道变态到吐血的关于变量提升的大厂面试题

匿名

发表评论

匿名网友