变量提升
参考文章:链接
定义
其实就是在JavaScript中,执行上下文(特别是创建和执行阶段)工作方式的一种认识。变量提升出现在用var声明的变量或者函数中,另外就是function声明的函数中。即使是在块级作用域中声明的
通俗来说,变量提升是指在 JavaScript 代码执行过程中,JavaScript 引擎把变量的声明部分和函数的声明部分提升到代码开头的行为。变量被提升后,会给变量设置默认值为 undefined。
- var声明的变量以及函数的默认值为undifined。 这种情况加再调用函数会报错,因为undifined不是一个函数。
- function 声明的函数被变量提升之后是可以直接调用的。
那么为什么会有变量提升呢
- es6之前是不支持块级作用域的。没有块级作用域,将作用域内部的变量统一提升无疑是最快速、最简单的设计,不过这也直接导致了函数中的变量无论是在哪里声明的,在编译阶段都会被提取到执行上下文的变量环境中,所以这些变量在整个函数体内部的任何地方都是能被访问的,这也就是 JavaScript 中的变量提升。
- 让函数可以在执行时预先为变量分配栈空间;
- 容错性更好,变量提升可以在一定程度上提高JS的容错性,看下面的代码
a = 1;
var a;
console.log(a); // 1
变量提升带来的问题
- 用var声明的变量,如果全局和块级都有相同名称的变量,在块中会优先调用块级的,即使这个变量是变量提升后的结果,也就是undifined
var x = 10;
function test() {
console.log(x); // 输出:undefined 用let声明的话这里输出10
var x = 20;
console.log(x); // 输出:20
}
test();
- 变量没有被销毁
function foo(){
for (var i = 0; i < 5; i++) {
}
console.log(i);
}
foo()
//使用其他的大部分语言实现类似代码时,在 for 循环结束之后,i 就已经被销毁了,但是在 JavaScript 代码中,i 的值并未被销毁,所以最后打印出来的是 5。这也是由变量提升而导致的,在创建执行上下文阶段,变量 i 就已经被提升了,所以当 for 循环结束之后,变量 i 并没有被销毁。
Javascript是如何同时支持块级作用域和变量提升的
- 通过在内存中分配一个专门储存块级变量的区域 我们称之为“词法环境”,而var声明的变量储存在“变量环境中”
比如以下代码
function fn(){
var a = 1
let b = 2
{
let b = 3
var c = 4
let d = 5 //执行到这里时
console.log(a)
console.log(b)
console.log(d)
}
console.log(b)
console.log(c)
}
fn()
执行在最深的块级中的情况下
JavaScript就是通过这种结构来控制的。
- 词法环境中有个类似于栈的结构,进入深一级块级结构时会分配一块新内存用来储存这个块级结构中的变量,每有一个新的变量就储存起来。利用这个栈结构来判断变量使用的优先级。
- 变量使用的优先级就是,在词法环境中的栈顶开始找,依次向栈低寻找变量,如果还是寻找不到就区变量环境中找。
- 退出块级结构时就将栈顶的内存块弹出,回到上一级块级结构中。
暂时性死区
当程序的控制流程在新的作用域进行实例化时,在此作用域中用 let 或者 const 声明的变量会先在作用域中被创建出来,但此时还未进行词法绑定,所以是不能被访问的,如果访问就会抛出错误。因此,在这运行流程进入作用域创建变量,到变量可以被访问之间的这段时间,就称之为暂时死区。
比如:
var name = 'JavaScript';
{
name = 'CSS';
let name;
}
// 输出结果:Uncaught ReferenceError: Cannot access 'name' before initialization
//在这段代码中,块级作用域中第四行上方的区域称为暂时性死区
在 let 和 const关键字出现之前,typeof运算符是百分之百安全的,现在也会引发暂时性死区的发生,像import关键字引入公共模块、使用new class创建类的方式,也会引发暂时性死区,究其原因还是变量的声明先与使用。