对javascript执行上下文的理解
什么是执行上下文?
javascript中代码的运行环境分为一下三种:
1.全局级别的代码,这个是默认代码的运行环境,一旦代码被载入,引擎最先进入的就是这个环境。
2.函数级别的代码,当执行一个函数时,运行函数体中的代码。
3.eval的代码,在eval函数内运行的代码。
为了方便于大家理解,我们可以将执行上下文看做当前代码的运行环境或者作用域。
举个栗子:
在图中一共有4个执行上下文。
紫色的代表全局的上下文。绿色的代表person函数内的上下文。蓝色以及橙色代表person函数内的另外两个函数的上下文。
全局的上下文只有一个,可以被任何其他的上下文所访问到,我们可以在person/firstName/lasrName的上下文中访问到全局上下文中的sayHello变量。
函数上下文的个数是没有任何限制的,每到调用执行一个函数时,引擎就会自动新建出一个函数上下文,也就是局部作用域。我们可以在该局部作用域中声明私有变量等,在外部的上下文中是无法直接访问到该局部作用域内的元素的。
对执行上下文的理解可以归纳总结一下几点:
1.单线程
2.同步执行
3.唯一的一个全局上下文
4.函数的执行上下文的个数没有限制
4.每次某个函数被调用,就会有个新的执行上下文为其创建,即使是调用的自身函数,也是如此。
执行上下文的建立过程:
现在我们已经知道,每当调用一个函数时,一个新的执行上下文就会被创建出来(局部作用域),在javascript引擎内部,这个上下文的创建过程具体分为两个阶段:
1.建立阶段(发生在当调用一个函数时,但是在执行函数体内的具体代码以前)
这个阶段做的操作有:
- 建立变量,函数,arguments对象,参数
- 建立作用域
- 确定this的值
2.代码的执行阶段
- 变量赋值,函数引用,执行其他代码
实际上可以把执行上下文看做一个对象,其中包含了以上3个属性:
executionContextObj = { variableObject: {}, //函数中的arguments对象, 参数, 内部的变量以及函数声明 scopeChain: {}, //variableObject 以及所有父执行上下文中的variableObject this: {} }
结合例子详细的分析:
建立阶段具体过程如下:
1.找到当前上下文中的调用函数的代码
2.在执行被调用的函数体中的代码以前,开始创建执行上下文
3.进入第一个阶段-建立阶段:
- 建立variableObject对象:
- 建立arguments对象,检查当前上下文中的参数,建立该对象的属性以及属性值
- 检查当前上下文中的函数声明:
每找到个一个函数声明,就在variableObject下面用函数建立一个属性,属性值就是 指向该函数在内存中的地址的一个引用,如果上述函数名已经存在于variableObject下,那么对应的属性值会被新的引用所覆盖。
- 初始化作用域链
- 确定上下文中的this的指向对象
4.代码执行阶段
执行函数体中的代码,一行一行的运行,给variableObject中的变量属性赋值
下面举个栗子:
function foo (i) { var a = 'hello'; var b = function privateB () {}; function c () {}; }; foo(22);
在调用foo(22)的时候,建立阶段如下:
executionContextObject = { variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c: pointer to function c(), a: undefined, b: undefined }, scopeChain: {...}, this: {...} }
由此可见,在建立阶段,除了arguments,函数的声明,以及参数被赋予了具体的属性值,其他的变量属性默认的都是undefined.。
一旦上述建立阶段结束,引擎就会进入代码的执行阶段,在执行阶段,变量属性会被赋予具体的值
executionContextObject = { variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c: pointer to function c(), a: 'hello', b: pointer to function privateB() }, scopeChain: {...}, this: {...} }
局部变量作用域提升的缘由
变量的提升:在函数中声明的变量以及函数,其作用域提升到函数顶部,换句话说就是已进入函数体,就可以访问到其中声明的变量以及函数。
这个的原因就是:在函数被调用的时候,会新创建一个执行上下文,在执行上下文的建立阶段,会先处理arguments.参数,接着是函数声明,最后是变量的声明。
举个栗子:
(function() { console.log(typeof foo); // function pointer console.log(typeof bar); // undefined var foo = 'hello'; var bar = function() { return 'world'; }; function foo() { return 'hello'; } })();
这里有个要要注意下就是声明的foo函数可以在声明的foo变量之前访问到,
这是因为上下文的建立阶段先是处理arguments.参数,接着是函数声明,最后是变量的声明