学习过前端的同学们应该都对变量提升的概念不陌生,这对于没有接触过JavaScript开发的同学来说是一种极其反常识的现象。当时对这种现象并没有什么理解,只是了解并记住了这种现象。到了今天,我想重新描述我的理解,当然,未必准确,纯当抛砖引玉。
首先来看一段代码:
console.log(test)
var test = 'Hello world'
foo()
function foo(){
console.log('test')
}
按照我当初初学时的理解,这段代码必然会报错啊,怎么能够在变量声明之前就使用变量呢?函数也是一样的。可是实际情况却是foo
函数输出了test,而第一行的console.log
则输出了undefined
。
这个结果说明了两个结论,一是变量或者函数是能够在被声明之前就被使用,二是未声明的变量的值是undefined
。
怎样来理解这两个结论呢?首先从行为层面来分析,我们可以将var test = 'Hello world'
这个语句拆分成两部分,一部分是var test = undefined
,另一部分是test = 'Hello World'
,也可以管第一个部分叫声明,第二个部分叫赋值。
那么上面的代码我们可以重新写为:
var test = undefined
function foo(){
console.log('test')
}
console.log(test)
test = 'Hello world'
foo()
这就是按照传统理解重新组织的代码,当然我们可以这样来理解,但事实却并不是这样的,代码在执行过程中并不会被这样重新组织。但是却有一个类似的过程。
JavaScript代码在执行之前也是需要编译的,那么一段代码经过编译之后会生成执行上下文跟可执行代码两个部分。而在执行上下文之中有一个变量环境(Variable Environment)的对象,这个对象里就存储变量提升的内容。
接下来分析一下最上面的代码是如何生成变量环境对象的:
首先第一、三行代码不是声明语句,JavaScript引擎不会做任何处理。
第二行是声明语句,于是JavaScript引擎会在变量环境对象中新建一个test
的属性,并使用undefined对其初始化。
第四行是一个函数声明,JavaScript引擎会将函数的定义存到堆中,然后在变量环境对象中新建一个foo
属性,然后该属性存储函数定义在堆中的位置。
以上所说的过程是在编译过程中完成的,接下来看执行过程
执行到第一行时,JavaScript引擎会在变量环境对象中寻找test
属性,此时对象中存在一个test
属性且其值为undefined
。
执行到第二行,此时便会去更新变量环境对象中test
的值为Hello world
执行到第三行,JavaScript引擎就会在变量环境对象中查找foo
这个函数,由于对象中存在该函数的引用,于是便执行函数,输出test。
可能有人会有困惑,如果出现了两个名称相同的变量或函数怎么办?例如:
function foo(){
console.log('test')
}
foo()
function foo(){
console.log('hello world')
}
foo()
这段代码执行的结果是输出两行hello,world。原因也很好理解,在编译过程中,JavaScript引擎先发现第一个foo
函数,会将其定义存到堆中,而发现第二个foo
函数时也会如法炮制,这样的结果,就是第一个函数在变量环境对象中的定义被第二个覆盖,于是执行的结果便都是第二个函数的结果了。