深入理解js代码执行过程----详解函数提升和变量提升
console.log(myname);//undefined
var myname = 'why';
foo(123);
function foo(num) {
console.log(m);//undefined
var m = 10;
var n = 20;
console.log("foo")//foo
}
以上代码输出结果如下:
由此引发思考,为什么myname
在未执行到var myname = 'why';
就可以使用了呢?
为什么foo函数可以在定义前调用呢?
在JavaScript中,前者的现象称为变量提升
,而后者称为函数提升
。
接下来,让笔者详细解析这段代码的执行过程是如何进行的。
首先js引擎会对这段代码进行编译。
在编译阶段
(编译阶段会忽略函数执行语句,只编译声明和定义语句),会首先创建一个执行上下文栈(Exection context Stack)(也可以称为调用栈),简称ECStack。
在ECStack中会先创建一个全局执行上下文GEC(global exection context),在GEC中存储着一个变量对象简称VO对象(variable Object),它指向堆内存中的全局对象简称GO对象(Global Object)
这个GO存储着js各种各样的原生类(ES5中称为构造函数)以及其他的API,比如Array,Function,Object,setTimeout,sessionStorage(仅限浏览器)等等,在浏览器执行环境中,该对象就是我们熟知的window,而在node执行环境中,它被称为global。
除了原生的各种API,就是我们新创建的变量myname,此时myname被赋值为undefined,而foo是函数,当编译到这段函数定义时,并不会给foo简单的赋值为undefined,而又在堆内存开辟一块空间用于存储foo函数对象,foo函数对象里面存着 父级作用域以及foo函数体
来到执行阶段,首先执行console.log(myname),此时GO中的myname为undefined,
这就是变量提升,虽然执行console.log(myname)的时候还没有给myname赋任何值,但是在编译阶段就将myname赋值为undefined
当执行到 var myname = ‘why’时,GO中的myname会被赋值为’why’,
当执行foo(123)时,则会去GO查找foo,得知是一个函数,又会在栈里开辟一个foo的函数执行上下文,存着一个VO指向AO(Activation Object活跃对象),此时未真正执行,先编译foo函数,所以在堆内存中开辟了一块空间存储foo的AO对象,里面的num、m、n都为undefined
当编译完foo函数时来到执行阶段,
//foo函数体
function foo(num) {
console.log(m);
var m = 10;
var n = 20;
console.log("foo")
}
console.log(m),m未赋值前为undefined,接着m赋值为10,n赋值为20,最后打印"foo"
这就是函数提升,原理与变量提升相同,在代码编译阶段就已经存在foo函数了,所以执行foo(123)可以在GO中找到相对应的函数体,从而创建新的函数执行上下文。
当执行完foo(123)后,foo的函数执行上下文会被弹出,foo的AO对象没有被引用之后,就会被js引擎回收,至此,这段代码的执行流程就走完了
如果执行完foo(123),又跟着个foo(321),同样会再次创建一个新的foo函数执行上下文,并创建新的foo的AO对象