sayHi()
console.log(myname)
var myname = 'yy'
function sayHi() {
console.log('Hi')
}
// 执行结果:
// Hi
// undefined
相信学过 JavaScript 的都知道这个执行结果的原理:JS的变量提升特性
注意:这里必须使用
var
来定义变量,如果使用let
或者 const 来定义的变量不会有变量提升,会报错未定义。函数声明方式也具有变量提升特性。
所谓的变量提升,是指在 JavaScript 代码执行过程中,JavaScript 引擎把变量的声明部分和函数的声明部分提升到代码开头的“行为”。变量被提升后,会给变量设置默认值,这个默认值就是我们熟悉的 undefined
上图是我们模拟的变量提升的效果,从概念的字面意义上看变量和函数声明都会被移动到代码的最前面,但,这并不准确。实际上变量和函数声明在代码里的位置是不会改变的,一段 JavaScript 代码在执行之前需要被 JavaScript 引擎编译,编译完成后才会进入执行阶段。执行流程细化图如下
一段 JavaScript 代码,经过编译后,会生成两部分:执行上下文(Execution context
)和可执行代码。
执行上下文是 JavaScript 执行一段代码时的运行环境,比如调用一个函数,就会进入这个函数的执行上下文,确定该函数在执行期间用到的诸如 this
、变量、对象以及函数等。
在执行上下文中存在一个变量环境的对象(Variable Environment
),该对象中保存了变量提升的内容。下面来分析这个变量环境对象是如何生成的:
var
声明的,因此 JavaScript 引擎将在环境对象中创建一个名为 myname
的属性,并使用 undefined
对其初始化function
定义的函数,所以它将函数定义存储到堆(HEAP
)中,并在环境对象中创建一个名为 sayHi
的属性,然后将该属性值指向堆中函数的位置。就这样生成了变量环境对象。接下来 JavaScript 引擎会把声明以外的代码编译为字节码(可以类比可执行代码内容)。现在有了执行上下文和可执行代码,接下来就到了执行阶段了。
JavaScript 引擎会按照顺序一行一行地执行“可执行代码”,下面是整个执行过程:
sayHi
函数时,JavaScript 引擎便开始在变量环境对象中查找该函数,由于变量环境对象中存在该函数的引用,所以JavaScript 引擎便开始执行该函数,打印 Hi
结果myname
信息,JavaScript 引擎查找到该变量值在变量环境对象中的值为 undefined
,打印 undefined
结果yy
赋值给 myname
变量,赋值后变量环境对象中的 myname
属性值变为 yy
那么如果代码中出现了重名的函数或者变量,JavaScript 引擎会如何处理呢?思考一下,如下代码输出的内容会是什么?
function sayHi() {
console.log('Hi')
}
sayHi()
function sayHi() {
console.log('Hello')
}
sayHi()
如果你理解了上面所说的 JavaScript 的执行机制:先编译,再执行。那么你一定能理解这段代码的输出结果
// Hello
// Hello
sayHi
函数,会将函数定义存储到堆(HEAP
)中,并在环境对象中创建一个名为 sayHi
的属性,然后将该属性值指向堆中函数的位置sayHi
函数,此时变量环境中已存在 sayHi
属性,此时将 sayHi
属性重新赋值指向堆中第二个函数的位置,这样变量环境中就只存在第二个函数了再来看一个实例
sayHi() // Hello
var sayHi = function() {
console.log('Hi')
}
// sayHi() // Hi
function sayHi() {
console.log('Hello')
}
sayHi
属性并初始化值为 undefined
sayHi
属性指向 “Hello函数”sayHi()
函数等于执行 “Hello函数”,输出 Hello
sayHi
赋值为 “Hi函数”如果在
sayHi
函数声明前执行sayHi()
函数,就会打印出Hi
,因为变量和函数提升后,就进行就是执行阶段,代码会一行一行往下执行,到这里时,sayHi
属性已经是 “Hi函数”了
模拟变量提升过程如下:
// 变量提升部分
var sayHi = undefined
function sayHi() {
console.log('Hello')
}
// 可执行代码部分
sayHi() // Hello
sayHi = function() {
console.log('Hi')
}
//sayHi() // Hi