变量提升
在这一节中,我们来讨论一个问题:JS代码是按顺序执行的吗
showName()
console.log(myname)
var myname = '小包'
function showName() {
console.log('函数showName被执行');
}
这段代码的输出是什么?
函数showName被执行
undefined
这与我们的预期可能有点出入,接下来我们来介绍一下这个过程
变量提升
我们先来看JS中的声明和赋值
对于上面的第三行代码var myname = '小包',我们可以将其分解一下:
var myname //声明部分
myname = '小包' //赋值部分
可以看到,变量的声明和赋值可以拆开看成两部分
我们再来看看函数的声明和赋值
function foo(){
console.log('foo')
}
var bar = function(){
console.log('bar')
}
第一个函数foo是一个完整的函数声明,也就是没有涉及赋值操作
第二个函数是先声明变量bar,再把函数赋值给bar
这就是变量和函数的声明与赋值,通过这个例子,我们可以引出变量提升:
所谓的变量提升,是指在JavaScript代码执行过程中,JavaScript引擎把变量的声明部分和函数的声明部分提升到代码开头的“行为”。变量被提升后,会给变量设置默认值,这个默认值就是我们熟悉的undefined。
JS代码的执行流程
从刚才的变量提升的解释里面,我们很容易认为变量和函数的声明会提升到代码的顶部
其实不然,变量和函数声明在代码中的位置是不会改变的,而是在编译阶段被JS引擎放入内存中
所以一段JS代码需要先通过JS引擎编译,编译完成后才进入执行阶段:
编译阶段
那么编译阶段和变量提升存在什么关系呢?
我们先把开头的那段代码分为两部分:
-
第一部分:变量提升部分的代码
var myname = undefined function showName() { console.log('函数showName被执行') } -
第二部分:执行部分的代码
showName() console.log(myname) myname = '极客时间'
这样我们就可以把JS的执行流程细化:
可以看到,经过编译后,会生成两部分内容:执行上下文和可执行代码
执行上下文是JS执行一段代码时的运行环境,具体细节下一节再进行分析
现在只需要知道在执行上下文中存在一个变量环境的对象,该对象保存了变量提升内容,可以简单理解为这么一个结构:
VariableEnvironment:
myname -> undefined,
showName -> function: {console.log(myname)}
对环境对象有一个了解之后,我们对开头的代码再进行分析:
showName()
console.log(myname)
var myname = '小包'
function showName() {
console.log('函数showName被执行');
}
-
第一行和第二行,由于这两行代码不是声明操作,所以JS引擎不会做任何处理
-
第三行,由于这行是经过
var声明的,因此JS引擎将在环境对象中创建一个名为myname的属性,并使用undefined进行初始化 -
第四行,JS引擎发现一个通过
function定义的函数,所以他将函数定义存储到堆中,并在环境对象中创建了一个showName属性,并将该属性值指向堆中函数的位置,这样就生成了变量环境对象 -
接下来,JS引擎把声明以外的代码编译成字节码,可以类比如下代码
showName() console.log(myname) myname = '小包'
执行阶段
JS引擎开始执行可执行代码,按照顺序一行一行的执行,下面我们再来分析一下:
- 当执行到
showName函数的时候,JS引擎便开始在变量环境中查找该函数,由于变量环境中存在该函数的引用,所以JS引擎便开始执行该函数 - 接下来打印
myname的信息,JS引擎继续再变量环境中查找该对象,由于变量环境存在myname变量,其值为undefiend,所以将其输出 - 最后,在执行第三行,将
'小包'赋值给myname变量,赋值后变量环境中的myname属性值改变为'小包'
这就是代码的编译和执行流程,实际上这个流程非常复杂,我们将在后续在进行分析
代码中出现相同的变量或者函数
通过上述分析,不难得出结论:
一段代码如果定义了两个相同名字的函数,那么最终生效的是最后一个函数