关于变量提升的JS难题

64 阅读5分钟

前言

变量提升是指在所有的代码执行之前,把当前上下问下,浏览器把所有带var和function关键字的提前进行声明和定义。带function的声明和定义同时进行,带var的只声明,不定义。

IE低版本浏览器中(小于等于IE10),在变量提升阶段,遇到大括号或者判断体等,不论条件是否成立,都要进行变量提升。高版本浏览器中,虽然也会进行变量提升,但是对于函数只声明,不会再定义了。

但是在ES6新语法规范中,存在块级上下文(除对象的大括号中,出现let/const/function,都会把当前大括号形成一个私有的跨级上下文)。

所以现在的浏览器很悲催,因为既要兼容低版本语法规范,还要适应高版本语法规范,所以对于有冲突的规范,会采用一些兼容性的方式进行处理。

接下来,我们根据两道js题目,分析这种兼容性的变量提升。

代码块“{}”中的变量提升

第一题

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

在在新版本浏览器中,遇到大括号,会形成一个私有的跨级上下文,同时也会进行变量提升,但是对于函数只声明,所以自上而下执行的时候,会在全局上下文下,声明一个foo属性,但是值为undefined。在执行代码块里面的代码的时候,会把“function foo() {}”这一行代码之前对于foo的操作映射到全局一份,所以全局上下文下,foo值为一个函数,但是在私有上下文下,执行到“foo = 1”的时候,foo值为1。

第二题

{
    function foo() {}
    foo = 1;
    function foo() {alert('2')}
    console.log(foo); //=>1
}
console.log(foo); //=>1

在这一题中,代码自上而下执行的时候,形成全局上下文和私有块级上下文。全局下,foo变量提升但不声明,值为undefined,私有块级上下文下,foo变量提升,foo值为“function foo() {alert('2')}”。但是执行到“foo=1”的时候,更改私有块级上下文中foo的值为1,执行到“function foo() {alert('2')}”的时候,这一行代码之前对于foo的操作映射到全局一份,所以foo在全局下,值也变成1。

第三题

{
    function foo() {}
    foo = 1;
    function foo() {alert('2')}
    foo = 2;
    console.log(foo); //=>2
}
console.log(foo); //=>1

这一题和上一题一样,代码自上而下执行的时候,形成全局上下文和私有块级上下文。全局下,foo变量提升但不声明值,值为undefined,私有块级上下文下,foo变量提升,foo值为“function foo() {alert('2')}”。但是执行到“foo=1”的时候,更改私有块级上下文中foo的值为1,执行到“function foo() {alert('2')}”的时候,这一行代码之前对于foo的操作映射到全局一份,所以foo在全局下,值也变成1。执行到“foo = 2”的时候,更改私有块级上下文中foo的值为2,全局不做修改。

函数中的私有上下文

第一题

var x = 1;
function func(x, y = function anonymous1() {x = 2}) {
	x = 3;  // 私有x=3
	y();
	console.log(x); //=>2
}

func(5);
console.log(x); //=>1 

func函数执行,形成私有上下文EC(func) ,并给形参赋值,x=5,y=anonymous1函数,执行“x = 3”时,更改x的值,变为3。执行到“y()”代码时,形成私有上下文EC(anonymous1),并生成作用域链EC(anonymous1)-》EC(func),在anonymous1中,更改x的值为2,更具作用域链,向上查找,把EC(func)中的x的值变为2,全局作用域下,x值任然为1。

第二题

上一题比较简单,现在我们更改一下变量,让题目难一点。

var x = 1;
function func(x, y = function anonymous1() {x = 2}) {
	var x = 3;  // 块级x=3
	y();
	console.log(x); //=> x=3
}

func(5);
console.log(x); //=>1 

这题按我们之前的理解,将x的值改为2,所以在函数体内应输出2,然而实际输出3,为什么呢?

我根据查阅资料,询问别的程序员高手,还有代码实际运行的情况,发现一个很重要的现象,如果一个函数同时符合了这两个条件:

  1. 形参有赋值默认值;
  2. 函数体中有声明过变量 var/function...

此时的函数执行会形成两个上下文:

  1. 私有的上下文;
  2. 函数体所在大括号中的块级上下文。

函数体中遇到一个变量,我们首先看是否为块上下文中的变量,如果是接下来都操作块上下文中的变量和私有没关系,如果不是,操作的是私有的或者全局的...

我们debugger命令来分析这一情况,func(5)执行,形成两个作用域:

所以代码执行完“var x = 3;”的时候,更改块级中的x值为3。

执行完“y();”的时候,私有中的x变为2。 所以x输出3,因为块级上下文在最里层。作用域链是EC(block)-》EC(func)。

第三题

var x = 1;
function func(x, y = function anonymous1() {x = 2}) {
    // 私有上下文 x=5 y=anonymous1
	// 块级上下文 x=5 y=anonymous1  在还没有执行到指定代码之前,存储的值和私有上下文中的值是一样的
	var x = 3;  // 块级x=3
	var y = function anonymous2() {x = 4};
	y(); // anonymous2()  x = 4  块级上下文中的x=4
	console.log(x); //=> x=4
}

func(5);
console.log(x); //=>1 

根据之前的结论,func(5)执行,形成块级上下文和私有上下文,由于函数体内定义了var x和var y,所以最开始的块级上下文事x=5和y=anonymous1,私有上下文是x=5 y=anonymous1。

执行完“var x = 3;”,修改块级x=3:

执行完“var y = function anonymous2() {x = 4};”后,将块级有变为anonymous2,x变为4:

所以函数体内x输出4。

总结

es6中let和const没有变量提升,但是var和function存在变量提升,但是还要适应高版本语法规范,所以情况复杂,需要仔细分析。