先谈谈作用域
- 什么是作用域:
就是某个变量有起作用的范围- 词法作用域和动态作用域:
词法作用域:在变量声明的时候,它的作用域就已经确定了;
动态作用域:在程序运行的时候,由程序的当前上下文(执行环境)决定的;- js属于词法作用域
词法作用域的访问规则:
先在当前作用域中查找,如果找到就直接使用,如果没有找到,那么就到上一级作用域中查找,如果还没有找到那么就重复这个查找过程,直到全局作用域。
JS中的作用域
- script标签构成的全局作用域;
- 在js中,函数是唯一一个可以创建作用域的对象;
|
|
变量和函数的提升
js的执行过程
预解析阶段,变量和函数的提升(声明)
具体的执行阶段:
变量和函数的提升:
js代码是一个从上至下逐步解析的过程,在这个过程中,之前会把所有的变量和函数提前声明。
|
|
上面一段js代码,会先把var a与fun函数提前声明,所以代码实际可模拟成先这段代码
|
|
经过上面的解析,结果便可以理解了。
具体会出现的一些问题和几种情况:
1.变量和变量同名的情况,后面的变量会把前面的变量覆盖。
|
|
2.函数和函数同名的情况,后面的函数会覆盖前面的函数。
|
|
3.函数和变量同名,可以理解为:函数声明提升,而变量的声明不提升(实际上变量也提升了,但是会被函数覆盖)。
|
|
4.变量提升是分作用域的
|
|
5.如果函数是函数表达式定义,那么在做函数声明提升的时候,仅仅只会把var变量的名字提升到当前作用域中
|
|
深入理解 Hoisting
上面描述的是关于ES5中的声明提升,即使用var声明的变量的声明提升。 有人说在ES6中用
let和const声明的变量不会发生声明提升,那究竟是不是这样呢?
我们先来看一段代码。
|
|
运行这段代码会报错,把上面代码中的let换成var就不会报错,会输出undefined, 那这是不是
就说明用let声明的变量不会发生声明提升呢?
我们先假设用let声明的变量不会发生声明提升,看看会发生什么?
|
|
按照我们预先的假设,如果用let声明变量不会发生声明提升,这里console语句输出的结果应该是1。
但是这里会报ReferenceError的错误。这说明了用let声明的变量不会发生声明提升,这一结论是错误的!
那么用let声明的变量究竟会不会发生声明提升的现象呢?
答案是会发生声明提升,用let/const/class声明的变量均会发生声明提升,既然用let声明的变量会出现声明提升的现象,
那之前的报错(ReferenceError)又怎么解释呢?
区别就在于var和let声明的变量在发生声明提升时,初始化的行为不同导致的,用var声明的变量会初始化为undefined,
而用let声明的变量会保持为未初始化的状态。也就是这样:
|
|
这也就解释了为什么会报错的那个问题,到这里我们就对变量的声明提升有了深入的理解。那么声明是TDZ呢?
什么是TDZ?
ECMScript标准里并没有给出TDZ(全称Temporal Dead Zone, 暂时性死区)定义,这JS社区里提出的一种说法。
TDZ指被声明和被初始化之间的这段时间。我们来看一个例子:
|
|
所以在内部的变量初始化之前访问a变量是会报错的,就因为有了TDZ我们更容易发现bug。为了更好的理解TDZ我们再看一个例子:
|
|
再看下面一个例子
|
|
上面的代码中,x = x 形成了一个单独作用域(context),等到初始化结束,这个作用域就会消失,实际执行的是let x = x,由于暂时性死区的原因,这行代码会报错。
引用