文中先发于微信公众号【一个老程序员】
前面的小伙伴们大约都清楚,js
里的var
自变量存有变量提升,在es6
之后伴随着let
变量的发生,变量提升难题得以解决。那样变量提升的原理是什么?es6
又是如何处理变量提升问题?下面我们就来一同探索回答:
我们先去了解好多个定义,执行上下文、自变量自然环境、句法自然环境。(文中不属于闭包、this
偏向等诸多问题)
执行上下文
当一段js
编码强制执行时,js
模块首先会对它进行编译程序,并建立执行上下文。执行上下文分为三种:全局性执行上下文、函数公式执行上下文、eval
执行上下文
-
全局性执行上下文 在
js
实行全局性编码时,js
模块会创建一个全局性执行上下文,全局性执行上下文在网页页面生命周期内仅有一份。即每一个js
文档,只有一个全局性前后文。 -
函数公式执行上下文 当实行一个
js
函数公式时,js
模块会建立一个函数执行上下文,当函数公式实行结束后,函数执行上下文能被消毁。一个函数被频繁启用,会建立好几个执行上下文。 -
eval
执行上下文 应用eval
函数公式实行一段js
编码时,会创建一个eval
的执行上下文。
当js
文档实行时,都会先建立全局性执行上下文,并压进调用栈,当启用js
函数公式时,会创建函数执行上下文,并压进调用栈。当函数公式实行完以后,函数公式执行上下文便是从栈中移除。如下列程序代码实行:
var a = "123"
function func1() {
var b = "123"
console.log(b)
func2()
}
funcgion func2() {
const c = "456"
console.log(c)
}
func1()
执行上下文中实际上还包括了其他几个目标,一个自变量自然环境主体和一个句法自然环境目标。那接下来我们来看一下什么叫自变量环境与句法自然环境
自变量自然环境
自变量自然环境存在执行上下文中,其实质是一个目标,自变量环境里存放是指此修饰符内界定的自变量、函数公式信息内容等相关信息。如全局性执行上下文里的自变量自然环境存放是指全局性自变量解析函数信息内容。函数公式执行上下文里的自变量自然环境则储存的是函数的参数、静态变量等相关信息。
实际上,js
代码在实施前还有一个编译的过程,在编译过程中,var
自变量和function
函数公式一部分能被js
模块放进到自变量环境里,而且自变量能被默认为undefined
。在实施阶段,js
模块会到自变量环境里搜索说明的自变量解析函数。这便是我们说的“变量提升”,这也就是为什么函数公式还可以在函数完成以前启用。
例:
console.log(a)
var a = "123"
function func1() {
console.log(a)
}
func1()
之上程序代码执行顺序是:
-
js模块先通过编译程序,然后把
a
自变量和func1
放进到自变量环境里,然后把a
自变量设为undefined
-
进到实施阶段,实行第一行代码
console.log(a)
,这时从自变量环境里取下a
数值为undefined
,因此打印出结果显示undefined
。 -
实行第二行编码
var a = "123"
,将自变量环境下的a
变量赋值为字符串数组123
。 -
实行最终一行代码
func1()
,js模块从自变量环境里找到相对应的func1
,并实施里边代码console.log(a)
,打印出结果显示123
因此之上编码输入参数为
undefined
123
尽管在a
申明以前打印出a
自变量,可是却没有出错。
句法自然环境
在ES6
以前,js
上只适用全局性修饰符解析函数修饰符,并不支持块级作用域。ES6
以后,js
引进了let
和const
关键词,进而克服了变量提升难题从而使js推动了块级作用域。
实际上说let
和const
并没有变量提升并有误,当js
编码被编译时,let
和const
自变量编码能被存放句法环境里。这时let
和const
自变量早已被提高了,但也就是建立被提高,复位和取值没有被提高,若是在取值之前去读写能力该自变量,就会出错,这便是我们说的“短暂性过流保护”。
那完成块级作用域的基本原理是什么呢?实际上在句法环境里,保护了一个修饰符栈,栈底是函数外层自变量(let
和const
说明的自变量),进入一个修饰符块后,就会将该修饰符里的自变量入栈;当修饰符里的执行命令完毕之后,该修饰符的信息便会在栈顶弹出来。
大家举一个下列事例来阐述
例:
function fun()
{
let a = 1
{
let a = 2
let b = 3
console.log(a)
console.log(b)
}
console.log(a)
console.log(b)
}
fun()
如下图:
- 当
fun
函数公式被编译时,外层a
自变量最先被建立,并储放至句法自然环境修饰符栈中,这时函数公式内部块级作用域里的自变量不被建立。 - 当函数公式实行至修饰符块时,
let a
和let b
又被建立划入栈储放至栈顶。并把a
取值为2
,将取值为3
。 - 当实行至
console.log(a)
和console.log(b)
时,js模块先从栈顶寻找a
和b
数值直接打印出2
和3
。 - 当修饰符块实行完毕之后,修饰符块里的自变量信息内容从栈中枪出。
- 然后实行
console.log(a)
寻找是指栈底的a
自变量,直接打印出1
。然后实行console.log(b)
,因为在句法环境与自变量环境里也找不到b
自变量,因此就会出错b is not defined
。
假如同一个函数中不一样修饰符存有同样的自变量(如上事例的a),那样变量的搜索次序是怎么样的呢?
- 在句法自然环境修饰符栈的栈顶的自变量内容中开始查找
- 假如寻找该自变量,则直接回到该自变量在这里修饰符块里的值,假如找不到将从栈顶向下先后搜索。
- 从句法环境下的栈顶到栈底也没找到,将从自变量环境里搜索。
汇总:
说到这里,我觉得应该能够回答一下文章内容逐渐所提出来的几个问题:
变量提升的原理是什么?
在js代码编译程序环节,var
自变量和function
函数公式能被js模块放进到自变量环境里,而且var
自变量能被默认为undefined
。需注意,var
自变量仅有建立和复位被提高,取值没有被提高;而function
的建立、复位和取值均能被提高。因此在变量的声明以前,该自变量数值是undefined
,而函数公式则需要在申明以前正常的启用。
let
、const
是如何解决变量提升问题?
在js代码编译程序环节,let
和const
自变量能被js模块放进到句法环境里。与var
一样,let
和const
自变量又被提高了。但也只是建立被提高,变量的复位和取值并没有被提高,若是在取值以前读写能力该自变量,就会造成短暂性过流保护。