前言
变量提升是指在所有的代码执行之前,把当前上下问下,浏览器把所有带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,为什么呢?
我根据查阅资料,询问别的程序员高手,还有代码实际运行的情况,发现一个很重要的现象,如果一个函数同时符合了这两个条件:
- 形参有赋值默认值;
- 函数体中有声明过变量 var/function...
此时的函数执行会形成两个上下文:
- 私有的上下文;
- 函数体所在大括号中的块级上下文。
函数体中遇到一个变量,我们首先看是否为块上下文中的变量,如果是接下来都操作块上下文中的变量和私有没关系,如果不是,操作的是私有的或者全局的...
我们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存在变量提升,但是还要适应高版本语法规范,所以情况复杂,需要仔细分析。