let存在变量提升吗?

  目录

javascript中关于let的一些小知识

let存在变量提升吗?

let是ES6推出的新语法,平时简单的用大家都明白,但是里边的原理还是很深的。
在MDN的文档中对于let的特性是这样说的:

  • let 声明的变量的作用域是块级的;
  • let 不能重复声明已存在的变量;
  • let 有暂时死区,不会被提升。
    我们来看一个小demo
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <script>
    Object.defineProperty(window, 'b', {
    value: 22
    });
    </script>
    <script>
    let b = 12
    console.log(b)
    </script>

代码执行报错:Uncaught SyntaxError: Identifier ‘b’ has already been declared

先给window创建b的属性,然后再去声明b,这个时候,b是个全局变量可以直接通过b访问,let 又不可以重复声明。此处可以解释的通。那么请看下面的代码

1
2
3
4
5
6
7
8
9
<script>
Object.defineProperty(window, 'b', {
value: 22
});
let b = 12
</script>
<script>
console.log(b)
</script>

如果把let b 提升到上面的script块就可以声明成功。如果说从上往下执行的话,b是已经存在的了,那么let b是不可以声明的成功的。

到这里就有些解释不通了。为什么window已经声明了b的属性,会成为一个单独的b变量。let 的时候却没有报错呢。这里就需要对let 是不是会变量提升划一个问号了。

先把这个问题放在这里,我们继续看下面这段代码。

1
2
3
4
5
6
7
8
<script>
Object.defineProperty(window, 'b', {
value: 22
});
console.log(window.b)
let b = 12
console.log(b)
</script>

window下已经声明了b的属性,而且let 一个b 打印出来分别是22和12。这里我们假设存在变量提升的话,那么js引擎在预解析的时候会先声明b对象。然后实际执行的时候,再给window创建b属性。到这里我们就可以解释的通了。既然存在变量提升的话,我是不是可以在声明之前打印一下b的值呢?

1
2
3
4
5
6
7
<script>
Object.defineProperty(window, 'b', {
value: 22
});
console.log(b)
let b = 12
</script>

报错 Uncaught ReferenceError: b is not defined
纳尼?难道let 真的不存在变量提升?因为鄙人有强迫症,本着不达目的不罢休的目的,问了度娘,找到了如下的参考资料。
要搞清楚提升的本质,需要理解 JS 变量的「创建create、初始化initialize 和赋值assign」。
我们来看看 var 声明的「创建、初始化和赋值」过程

1
2
3
4
5
6
7
<script>
function fn(){
var x = 1
var y = 2
}
fn()
</script>

在执行 fn 时,会有以下过程(不完全):

1.进入 fn,为 fn 创建一个环境。
2.找到 fn 中所有用 var 声明的变量,在这个环境中「创建」这些变量(即 x 和 y)。
3.将这些变量「初始化」为 undefined。
4.开始执行代码
5.x = 1 将 x 变量「赋值」为 1
6.y = 2 将 y 变量「赋值」为 2
也就是说 var 声明会在代码执行之前就将「创建变量,并将其初始化为 undefined」。
这就解释了为什么在 var x = 1 之前 console.log(x) 会得到 undefined。
接下来来看 function 声明的「创建、初始化和赋值」过程
假设代码如下:

1
2
3
4
5
6
7
<script>
fn2()

function fn2(){
console.log(2)
}
</script>

JS 引擎会有一下过程:
1.找到所有用 function 声明的变量,在环境中「创建」这些变量。
2.将这些变量「初始化」并「赋值」为 function(){ console.log(2) }。
3.开始执行代码 fn2()
也就是说 function 声明会在代码执行之前就「创建、初始化并赋值」。
接下来看 let 声明的「创建、初始化和赋值」过程
假设代码如下

1
2
3
4
5
6
<script>
{
let x = 1
x = 2
}
</script>

我们只看 {} 里面的过程:
1.找到所有用 let 声明的变量,在环境中「创建」这些变量
2.开始执行代码(注意现在还没有初始化)
3.执行 x = 1,将 x 「初始化」为 1(这并不是一次赋值,如果代码是 let x,就将 x 初始化为 undefined)
4.执行 x = 2,对 x 进行「赋值」
这就解释了为什么在 let x 之前使用 x 会报错:

1
2
3
4
5
6
7
<script>
let x = 'global'
{
console.log(x) // Uncaught ReferenceError: x is not defined
let x = 1
}
</script>

原因有两个
1.console.log(x) 中的 x 指的是下面的 x,而不是全局的 x
2.执行 log 时 x 还没「初始化」,所以不能使用(也就是所谓的暂时死区)
看到这里,你应该明白了 let 到底有没有提升:
1.let 的「创建」过程被提升了,但是初始化没有提升。
2.var 的「创建」和「初始化」都被提升了。
function 的「创建」「初始化」和「赋值」都被提升了。

至此 我们也可以解释

  • 为什么跨script标签提示重复声明:因为浏览器是从上往下执行js代码的,也就是说在第一段js执行完成之后,再去预解析第二段js代码,执行第二段代码。
  • 为什么在同一个script标签中可以定义b: 因为在js引擎预解析script的时候先创建了b。然后再执行Object.defineProperty为window增加b属性。所以此处的b和window.b不是一个变量。
  • 为什么在同一个script标签中可以定义b,但是在b声明之前会报错b没有定义:因为只是创建了b变量,并没有初始化。没有初始化,变量是不可以使用的