变量提升
变量提升:在当前上下文中(全局/私有/块级),JS代码自上而下执行之前,浏览器会提前处理一些事情(可以理解为词法解析的一个环节,词法解析一定发生在代码执行之前)。会把当前上下文中所有带var和function关键字的进行提前的声明或者定义。带var的只会提前声明,带function会提前的声明和定义。变量提升的意义在于,能够在创建变量代码之前使用这个变量而代码不会报错。
举例说明
example1
console.log(a);//=>undefined
var a = 12;//=>创建值12,不需要再声明a了(变量提升阶段完成了,完成的事情不会重新处理)a=12赋值
a = 13;//全局变量a=13
console.log(a);//=>13
对于以上代码,
- 代码执行之前,全局上下文中的变量提升:var a; 默认值是undefined
- 代码自上而下执行,第一行输出undefined
- 第二行,创建值12,不需要再声明a了(因为变量提升阶段完成声明了,完成的事情不会重新处理),a=12赋值
- 第三行,创建值13,全局的a=13
- 第四行,输出13
example2
func();
function func() {
console.log('OK');
}
对于以上代码,
- 代码执行之前,全局上下文中的变量提升:func = 函数,函数在这个阶段赋值都做了。
- 代码执行,输出结果是OK
需要提出的是,在真实项目中建议使用函数表达式创建函数,以为这样在变量提升阶段只会声明func,不会赋值。
//func(); //Uncaught TypeError:func is not a function
var func = function (){
console.log('OK');
};
func();
一般规范化写法,还要把匿名函数具名化,把原来作为值的函数表达式匿名函数“具名化”(虽说是起了名字,但是这个名字不能在外面访问=>也就是不会在当前上下文中创建这个名字)。当函数执行,在私有的上下文中,会把这个具名化的名字作为私有上下文中的变量(值就是这个函数)来进行处理。
var func = function AAA(){
console.log('OK');
console.log(AAA);//=>输出当前函数
//AAA();//递归调用 而不用严格模式下都不支持的arguments.callee了
};
//AAA();//Uncaught ReferenceError:AAA is not defined
func();
example3
console.log(a);
a = 13;
console.log(a);
对于以上代码,
- EC(G)下的变量提升,由于没有带var或者带function的代码,所以不会有变量提升
- 执行第一行代码时候就会报错:Uncaught ReferenceError: a is not defined
example4
console.log(a);
let a = 12;
a = 13;
console.log(a);
对于以上代码,
- EC(G)下的变量提升,由于没有带var或者带function的代码,所以不会有变量提升。注意:ES6中的let/const不会进行变量提升
- 执行第一行代码时候就会报错:Uncaught ReferenceError: Cannot access 'a' before initialization 意思是,不能在let声明之前使用变量
**基于var或者function在全局上下文中声明的变量(全局变量)会映射到GO(全局对象window)上一份,作为它的属性;而且接下来是一个修改,另外一个也会跟着修改。**严格模式也是如此。
example5
//"use strict";
var a = 12;
console.log(a);//=>全局变量
console.log(window.a);//=>12 映射到GO上的属性a
window.a = 13;
console.log(a);//=>13 映射机制是一个修改另外一个也会修改
example6
if (!("a" in window)) {
var a = 1;
function func() {}
}
console.log(a);
对于以上代码,
- EC(G)全局上下文中的变量提升:无论条件是否成立,都要进行变量提升(细节点:条件中带function的在新版本浏览器中只会提前声明,不会再提前的赋值了)
- [老版本(ie10及以下)]var a; func= 函数;
- [新版本]var a; func; 全局上下文中声明一个a也相当于window.a;全局上下文中声明一个func也相当于window.func
- "a" in window 是检测a是否为window的一个属性
- (!("a" in window))=>!ture=>false,条件不成立
- 输出结果undefined
example7
fn();
function fn() { console.log(1); }
fn();
function fn() { console.log(2); }
fn();
var fn = function() { console.log(3); }
fn();
function fn() { console.log(4); }
fn();
function fn() { console.log(5); }
fn();
对于以上代码,
- EC(G)变量提升
- 声明加定义一个fn[=>1];重新定义fn[=>2];var fn已经声明过了,不再重新声明(var也不定义);再重新定义fn[=>4],;再重新定义fn[=>5]
- 变量提升结束时,全局上下文中有一个全局变量fn,值是输出5的函数(此时window.fn=>5)
- 代码执行
- 第一个fn();输出5
- 第二个fn();输出5
- 第三个fn();输出5
- var fn不用再处理了,但是赋值在变量提升阶段没有处理,此处需要处理,fn = window.fn =>3
- 第四个fn();输出3
- 第五个fn();输出3
- 第六个fn();输出3
example8
var foo = 1;
function bar() {
if (!foo) {
var foo = 10;
}
console.log(foo);
}
bar();
对于以上代码,具体的处理过程见下图:
example9-怪诞的面试题
var a = 0;
if (true) {
a = 1;
function a() {};
a = 21;
console.log(a);
}
console.log(a);
此题目在新老浏览器中的结果是不一样的。现在最新版本的浏览器:
- 需要向前兼容ES3/5规范:
- 判断体和函数体等不存在块级上下文,上下文只有全局和私有
- 不论条件是否成立,带function的都要声明且定义
- 还需要向后兼容ES6规范
- 存在块级作用域,大括号中出现let/const/function都会被认为是块级作用域
- 不论条件是否成立,带function的只提前声明,不会提前赋值了
此题的具体解析,见下图: