聊聊js的执行上下文javascript
EC : 执行上下文
ECS : 执行环境栈
VO : 变量对象
AO : 活动对象
scope chain :做用域链
javascript运行的代码环境有三种:java
全局代码:代码默认运行的环境,最早会进入到全局环境中 函数代码:在函数的局部环境中运行的代码 Eval代码:在Eval()函数中运行的代码
全局上下文是最外围的一个执行环境,web浏览器中被认为是window对象。在初始化代码时会先进入全局上下文中,每当一个函数被调用时就会为该函数建立一个执行上下文,每一个函数都有本身的执行上下文。来看一段代码:web
function f1() { var f1Context = 'f1 context'; function f2() { var f2Context = 'f2 context'; function f3() { var f3Context = 'f3 context'; console.log(f3Context); } f3(); console.log(f2Context); } f2(); console.log(f1Context); } f1();
这段代码有4个执行上下文:全局上下文和f1(),f2(),f3()属于本身的执行上下文。浏览器
全局上下文拥有变量f1(),f1()的上下文中有变量f1Context和f2(),f2()的上下文有变量f2Context和f3(),f3()上下文有变量f3Context。函数
在这咱们了解下执行环境栈ECS,一段代码全部的执行上下文都会被推入栈中等待被执行,由于js是单线程,任务都为同步任务的状况下某一时间只能执行一个任务,执行一段代码首先会进入全局上下文中,并将其压入ECS中,执行f1()会为其建立执行上下文压入栈顶,f1()中有f2(),再为f2()建立f2()的执行上下文,依次,最终全局上下文被压入到栈底,f3()的执行上下文在栈顶,函数执行完后,ECS就会弹出其上下文,f3()上下文弹出后,f2()上下文来到栈顶,开始执行f2(),依次,最后ECS中只剩下全局上下文,它等到应用程序退出,例如浏览器关闭时销毁。this
总结:(执行上下文就用EC替代)spa
1. 全局上下文压入栈顶 2. 执行某一函数就为其建立一个EC,并压入栈顶 3. 栈顶的函数执行完以后它的EC就会从ECS中弹出,而且变量对象(VO)随之销毁 4. 全部函数执行完以后ECS中只剩下全局上下文,在应用关闭时销毁
你们再看一道道题:.net
function foo(i) { if(i == 3) { return; } foo(i+1); console.log(i); } foo(0);
你们明白执行上下文的进栈出栈就应该知道结果为何是2,1,0线程
ECS栈顶为foo(3)的的上下文,直接return弹出后,栈顶变成foo(2)的上下文,执行foo(2),输出2并弹出,执行foo(1),输出1并弹出,执行foo(0),输出0并弹出,关闭浏览器后全局EC弹出,因此结果为2,1,0指针
刚才提到VO,咱们来了解什么是VO
建立执行上下文时与之关联的会有一个变量对象,该上下文中的全部变量和函数全都保存在这个对象中。
进入到一个执行上下文时,此执行上下文中的变量和函数均可以被访问到,能够理解为被激活
谈到了上下文的建立和执行,咱们来看看EC创建的过程:
创建阶段:(函数被调用,可是还未执行函数中的代码) 1. 建立变量,参数,函数,arguments对象 2. 创建做用域链 3. 肯定this的值 执行阶段:变量赋值,函数引用,执行代码
执行上下文为一个对象,包含VO,做用域链和this
executionContextObj = { variableObject: { /* 函数中的arguments对象, 参数, 内部的变量以及函数声明 */ }, scopeChain: { /* variableObject 以及全部父执行上下文中的variableObject */ }, this: {} }
具体过程:
1. 找到当前上下文调用函数的代码 2. 执行代码以前,先建立执行上下文 3. 建立阶段: 3-1. 建立变量对象(VO): 1. 建立arguments对象,检查当前上下文的参数,创建该对象下的属性和属性值 2. 扫描上下文的函数申明: 1. 每扫描到一个函数什么就会在VO里面用函数名建立一个属性, 为一个指针,指向该函数在内存中的地址 2. 若是函数名在VO中已经存在,对应的属性值会被新的引用覆盖 3. 扫描上下文的变量申明: 1. 每扫描到一个变量就会用变量名做为属性名,其值初始化为undefined 2. 若是该变量名在VO中已经存在,则直接跳过继续扫描 3-2. 初始化做用域链 3-3. 肯定上下文中this的指向 4. 代码执行阶段 4-1. 执行函数体中的代码,给VO中的变量赋值
看代码理解:
function foo(i) { var a = 'hello'; var b = function privateB() {}; function c() {} } foo(22);
调用foo(22)时建立上下文包括VO,做用域链,this值
以函数名做为属性值,指向该函数在内存中的地址;变量名做为属性名,其初始化值为undefined
注意:函数申明先于变量申明
fooExecutionContext = { variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c: pointer to function c(), a: undefined, b: undefined }, scopeChain: { ... }, this: { ... } }
建立阶段结束后就会进入代码执行阶段,给VO中的变量赋值
fooExecutionContext = { variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c: pointer to function c(), a: 'hello', b: pointer to function privateB() }, scopeChain: { ... }, this: { ... } }
function foo() { console.log(f1); //f1() {} console.log(f2); //undefined var f1 = 'hosting'; var f2 = function() {} function f1() {} } foo();
调用foo()时会建立VO,初始VO中变量值等有一系列的过程,全部变量初始化值为undefined,因此console.log(f2)的值为undefined。而且函数申明先于变量申明,因此console.log(f1)的值为f1()函数而不为hosting
1. 调用函数时会为其建立执行上下文,并压入执行环境栈的栈顶,执行完毕 弹出,执行上下文被销毁,随之VO也被销毁 2. EC建立阶段分建立阶段和代码执行阶段 3. 建立阶段初始变量值为undefined,执行阶段才为变量赋值 4. 函数申明先于变量申明