JS变量提升机制
变量提升机制
变量提升
当栈内存(作用域)形成,JS代码自上而下执行之前,浏览器首先会把所有带“VAR/FUNCTION”关键字的进行提前的“声明”或者“定义”,这种预先处理机制称之为“变量提升”
- 声明: var a (默认undefined)
- 定义: a = 12(定义其实就是赋值操作)
变量提升阶段
- 带“VAR”的只声明未赋值
- 带“FUNCTION”的声明和赋值都完成了
变量提升只发生在当前作用域(例如:开始加载页面的时候只对全局作用域下的进行提升,因此此时函数中存储的都是字符串而已)在全局作用域下声明的函数或者变量是“全局变量”,同理在私有作用域下声明的变量是“私有变量”【带VAR/FUNCTION的才是声明】
浏览器很懒,做过的事情不会重复执行第二遍,也就是,当代码执行遇到创建函数这部分代码后,直接跳过即可(因为在提升阶段就已经完成函数的赋值操作了)
私有作用域形成后,也不是立即执行代码,而是先进行变量提升(变量提升前,先形参赋值)
在ES3/ES5语法规范中,只有全局作用域和函数执行的私有作用域(栈内存),其他不生成栈内存
console.log(a);
var a = 12;
b = 13;
function sum(){
var total = null;
}
sum()
->undefined
带VAR和不带VAR的区别
在全局作用域下声明一个变量,也相当于给WINDOW全局对象设置了一个属性,变量的值就是属性值(私有作用域中的声明的私有变量和WINDOW没啥关系)
console.log(a);
console.log(window.a)
console.log('a' in window);
-> undefined
-> undefined
-> undefined
全局变量和window中的属性存在“映射机制”一个改变另一个也改变
var a = 12;
console.log(a);
console.log(window.a);
a = 13;
console.log(window.a);
window.a = 14;
console.log(a);
-> 12
-> 12
-> 13
-> 14
可以使用属性名 in 对象,检测属性名是否隶属于这个对象
console.log(a);
console.log(window.a);
console.log('a' in window);
a = 12;
console.log(a)
console.log(window.a)
-> a is not defined
-> undefined
-> false
-> 12
-> 12
全局变量和WINDOW中的属性存在“映射机制”一个改变另一个也改变
console.log(a);
console.log(window.a);
console.log('a' in window);
a = 12;
consoel.log(a);
console.log(window.a);
-> 报错:a is not defined
注释掉报错的第一行
-> undefined
->false
-> 12
-> 12
-> 12
连续赋值运算下的VAR
var a = 12, b = 13;
这样写是带VAR的
var a = b = 12;
这样写b是不带VAR的
私有作用域域中带VAR和不带也有区别
- 带VAR的在私有作用域变量提升阶段,都声明未私有变量,和外界没有任何的关系
- 不带VAR不是私有变量,会向它的上级作用域查找,看是否为上级的变量,不是,继续向上查找,一直找到WINDOW为止(这种查找叫做:“作用域链”),也就是我们在私有作用域中操作的非私有变量,是一直操作别人的。
var a = 12, b = 12;
function fn(){
console.log(a, b);
var a = b = 13;
console.log(a,b);
}
fn();
console.log(a,b);
-> undefined 12
-> 13 , 13
-> 12 ,13
变量提升阶段, 私有变量a被提升,声明a为私有变量并赋值为undefined
var a = b = 13;
把私有的A赋值为13,通过作用域链查找B并赋值为13
函数的提升
- FUNCTION:声明的函数在变量提升阶段被声明
- 匿名函数之函数表达式: var fn = function(){...},左边被提升,未执行到该行时,fn为undefined
在条件的变量提升
在当前作用域下,不管条件是否成立都要进行变量提升
- 带VAR的还只是声明
- 带FUNCTION的在老版本浏览器渲染机制下,声明+定义都处理,但为了迎合ES6中的块级作用域,新版浏览器对于在条件判断中的函数,不管条件是否成立,都只是先声明,没有定义,类似VAR
重名处理
带VAR和FUNCTION关键字声明相同的名字,这种也算是重名了(其实是一个FN,只是存储值的类型不一样)
var fn = 12;
function fn(){};
关于重命名的处理:
如果名字重复了,不会重新声明,但是会重新定义(重新赋值)(不管是变量提升还是代码执行阶段皆是如此)
LET
LET创建的变量不存在变量提升问题,且不会与window产生映射
在相同作用域中,基于LET不能声明相同名字的变量(不管用什么方式在当前作用于下声明了变量,再次使用LET创建变量都会)
虽然没有变量提升机制,但是在当前作用域代码自上而下执行之前,浏览器会做一个重复性检测:
自上而下查找当前作用域下所有变量,一旦发现有重复的,直接抛出异常,代码也不会在执行了