什么是变量提升?
JavaScript 代码执行过程中,JavaScript 引擎把变量的声明部分和函数的声明部分提升到代码开头的“行为”,变量被提升后,会给变量设置默认值,这个默认值就是我们熟悉的 undefined;
变量提升提升的是什么?
提升的是变量和函数的声明,注意变量提升只是提升变量和函数的声明,并不会直接执行赋值初始化的过程,还有非声明的语句,Javascript 是直接转换成字节码,待后续执行阶段执行;
Javascript 的执行流程是怎样的?
实际上变量和函数声明在代码里的位置是不会改变的,他们会在编译阶段被 JavaScript 引擎放入内存中,将那些用 var 声明的变量设置为活动对象的属性,默认值为“undefined”,并且将那些以 function 定义的函数也添加为活动对象的属性,而且他们的值正是函数的定义,匿名函数将不被解析。变量初始化过程即赋值过程发生在解释执行期,而不是编译期
- 语法分析
Js 引擎检查代码是否有低级的语法错误
- 预编译(编译结束会产生以下两部分内容)
在内存中开辟一些空间,存放一些数据
- 执行上下文(执行一段代码的运行环境)
- 变量环境对象(Viriable Environment Object)
- 变量提升的内容,如:
foo2();
var foo = 1;
function foo2(){
console.log(foo);
}
/* 上面代码经过编译后如下 */
VariableEnvironment:
foo -> undefined,
foo2 -> function : {console.log(foo)}- 词法环境(实现块级作用域时需要在词法环境中操作变量)
- 可执行代码
- 解释执行(解释一行执行一行)
- 同名覆盖
function foo(){
console.log('我是a');
}
foo();
function foo(){
console.log('我是2b');
}
foo();打印结果:
- 更多例子
/* 1、变量提升之同名值覆盖 */
function foo(){
console.log('我是a');
}
foo();
function foo(){
console.log('我是2b');
}
foo();
/* 2、变量提升之函数声明方式 */
foo2();
/* 用函数表达式进行的变量声明 */
var foo2 = function() {
console.log(2)
}
/* 函数声明 */
function foo2() {
console.log(1)
}
/*
分析过程
编译阶段:
1、第一个 foo2 变量是用函数表达式进行的变量声明,会提升变量foo2并赋值为undefined
2、第二个 foo2 是函数声明,会整体提升,并将函数的引用类型保存在堆空间中,将堆空间的地址赋值给提升的foo2函数
执行阶段:
预计打印结果为1
*/
/* 3、变量和函数提升优先级 */
console.log(foo3);
foo3();
var foo3 = 3;
function foo3(){
console.log(10);
}
console.log(foo3);
foo3 = 6;
foo3();
/*
分析过程
编译阶段:
1、第 1、2 行代码不涉及声明,编辑阶段直接生成字节码
2、第 3 行对 foo3 变量声明,需要提升,并赋值为undefined => var foo3 = undefined;
3、第 3 行对 foo3 进行函数声明,需要提升,并且与前面的 foo3 同名,故 foo3 值被覆盖为该函数在堆内存中的地址
执行阶段:
1、第 1 行打印结果预计为 foo3 的函数体
2、第 2 行执行 foo3 的函数,打印为 10
3、第 2 行 foo3 再次被覆盖为 3,第 7 行的结果为 3
4、第 8 行 foo3 再次被覆盖为 6
5、第 9 行 由于此时 foo3 为值 6 执行 foo3() 会报错
*/变量提升会导致的一些问题
- 外层变量在不知不觉中被内部代码覆盖
- 该销毁的变量未被销毁
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>作用域</title>
</head>
<body>
<h1 id="title">
<span>作用域</span>
</h1>
<script>
/* 变量提升导致的问题之一:外层变量在不知不觉中被内部代码覆盖 */
var str = "我是全局变量";
function foo(){
console.log(str);
if(true){
var str = "我是函数作用域中的变量"
}
console.log(str);
}
foo();
/*
执行过程分析:
1、函数调用前,在预编译阶段创建全局执行上下文,并压入调用栈底
2、执行 foo 函数调用,预编译 foo 函数内部代码,创建函数执行山下文并入压入调用栈中
3、所以根据作用域链的查找规则,在 foo 中(22 行)的打印会从当前执行上下文先查找 str 变量,
发现当前执行上下文中有此变量并且,由于此时 str 经过了变量提升提升为 str -> undefined,
所以第一个答应结果为 undefined
4、由于 Js 在 ES6 之前没有块级作用域,所以 str 会经过 if 块中被赋值,所以第二个打印结果为“我是函数作用域中的变量”
*/
/* 变量提升导致的问题之二:该销毁的变量未被销毁 */
function foo(){
for (var i = 0; i < 7; i++) {
}
console.log(i);
}
foo();
/*
执行过程分析:
1、函数调用前,在预编译阶段创建全局执行上下文,并压入调用栈底
2、执行 foo 函数调用,预编译 foo 函数内部代码,创建函数执行山下文并入压入调用栈中
3、foo for 循环中的 变量 i 被提升至 foo 的函数执行上下文顶部,即使在 for 循环结束后 变量 i 仍然存在于 foo 的作用域中没被销毁
*/
</script>
</body>
</html>