介绍三个概念
- 执行环境
- 全局执行环境
- 在浏览器中全局执行环境就是window对象,因此所有全局变量和全局函数都是作为window对象的属性和方法创建的。
- 全局环境只有当程序退出或者浏览器网页关闭后才会被销毁
- 局部执行环境
- 每个函数都有自己的执行环境,当js执行流进入一个函数时,函数的环境会被推入一个环境栈中,当函数执行完毕后,栈将其环境弹出,该环境即被销毁。
- 全局执行环境
- 变量对象(Variable object)
- 简称VO, 非函数执行环境被创建时都会对应创建一个VO对象,该对象保存着当前环境中定义的所有变量和函数
- 活动对象 (Activation object)
- 简称AO, 函数的执行环境是在调用时创建的,在创建函数执行环境的同时会对应创建一个AO带有arguments属性的AO对象,该对象将代替VO对象来保存当前函数环境中的变量,参数,函数,所以在函数执行环境中VO就是AO
js执行流
- 第一步,当程序加载时全局执行环境就被创建,并且创建一个VO对象来保存当前全局变量与全局函数,在刚刚创建的VO对象中,变量的值被保存为Undefined,函数属性的值保存为对应的函数体
- 第二步,当程序执行到第16行的时候,变量g执行赋值操作,VO对象中的属性g的值发生变化
- 第三步,当程序向下执行,遇到fn1函数,由于fn1函数没有被调用,所以程序直接来到第34行,发现fn2函数被调用并传参,那么就进入fn2函数内部并创建fn2执行环境,由于在函数执行环境中,VO是无法直接访问的,所以当fn2环境被创建时,会同时创建一个AO对象,该对象默认带有一个arguments属性,这个属性保存了函数调用时传入的实参,该AO对象同时也会保存fn2函数的形参num3并赋值,保存局部变量x,x的值为Undefined,
到这里可以看出AO与VO的区别,
- 非函数执行环境对应创建一个VO对象,
- 函数执行环境对应创建一个默认带有arguments属性的AO对象,
- AO是函数执行环境下的VO,两者的作用是相同的
第四步,当代码运行到第41行x被赋值,AO赋值操作与VO完全一样,所以就不画图了,代码继续向后执行,遇到fn1函数调用,并且发现fn2执行环境中没有可执行语句了,那么程序将进入fn1函数,并将fn2执行环境与fn2的AO对象销毁,程序进入fn1后执行的操作与进入fn2时的操作一样,创建fn1执行环境,并创建fn1环境下的AO对象,保存fn1执行环境下的arguments,形参,变量和函数;
- 第五步,当程序执行到第35行,遇到child函数调用,那么程序进入child函数中,首先创建child执行环境,然后创建对应的AO对象,由于child没有接收到任何参数,所以AO中的arguments为空数组,形参值为Undefined,b属性值在在AO初始时为Undefined,遇到赋值语句是将会改变,由于child执行环境是包含在fn1执行环境中,所以只有当child执行环境销毁之后,fn1执行环境才会销毁,最终只保留下全局执行环境,直到浏览器页面关闭全局环境才会被销毁
总结
- 当程序开始执行,自动创建一个全局执行环境和一个VO对象
- 当函数被调用时,在函数中会创建一个函数执行环境和一个AO对象,AO与VO功能相同
- 当函数执行环境被创建后,js会创建一个执行环境栈,将当前执行环境入栈,当该函数执行结束之后,其对应的执行环境会从栈中抛出,并销毁
- 类似于下图
参考资料
- 深入理解javascript原型和闭包(13)-【作用域】和【上下文环境】
- JavaScript的执行上下文
- JavaScript高级程序设计(第3版)第四章-73页
- JavaScript高级程序设计(第3版)第七章-178页,179页