首先明确几个概念:
想当初自己看到这几个概念的时候是一 (m) 脸(d)懵 (z) 逼(z),但是不得不说这几个概念对以后深入学习 JS 有很大的帮助。来不及解释了,赶紧上车~
每次当控制器转到 ECMAScript 可执行代码的时候,就会进入到一个执行上下文。
那什么是可执行代码呢?
可执行代码的类型
全局代码( Global code )
函数代码( Function code )
Eval 代码( Eval code )
这里仅仅引入 EC 这个概念,后面还有关于 EC 建立细节的介绍。
我们用 MDN 上的一个例子来引入函数执行栈的概念
- function foo(i) {
- if (i < 0) return;
- console.log('begin:' + i);
- foo(i - 1);
- console.log('end:' + i);
- }
- foo(2);
- // 输出:
- // begin:2
- // begin:1
- // begin:0
- // end:0
- // end:1
- // end:2
这里先不关心执行结果。磨刀不误砍柴功,先了解一下函数执行上下文堆栈的概念。相信弄明白了下面的概念,一切也就水落石出了
我们都知道,浏览器中的 JS 解释器被实现为单线程,这也就意味着同一时间只能发生一件事情,其他的行为或事件将会被放在叫做执行栈里面排队。下面的图是单线程栈的抽象视图:
当浏览器首次载入你的脚本,它将 默认进入全局执行上下文 。如果,你在你的全局代码中调用一个函数,你程序的时序将进入被调用的函数,并创建一个新的执行上下文,并将新创建的上下文压入执行栈的顶部。
如果你调用当前函数内部的其他函数,相同的事情会在此上演。 代码的执行流程进入内部函数,创建一个新的执行上下文并把它压入执行栈的顶部。浏览器总会执行位于栈顶的执行上下文,一旦当前上下文函数执行结束,它将被从栈顶弹出,并将上下文控制权交给当前的栈 。这样,堆栈中的上下文就会被依次执行并且弹出堆栈,直到回到全局的上下文。
看到这里,想必大家都已经深谙上述例子输出结果的原因了,这里我大概绘了一个流程图来帮助理解。
这里为什么要用一个 / 呢?按照字面理解,AO 其实就是被激活的 VO,两个其实是一个东西。下面引用知乎上的一段话,帮助理解一下。 原文链接
变量对象 (Variable object) 是说 JS 的执行上下文中都有个对象用来存放执行上下文中可被访问但是不能被 delete 的 函数标示符 、 形参 、 变量声明 等。它们会被挂在这个对象上,对象的属性对应它们的名字对象属性的值对应它们的值但这个对象是规范上或者说是引擎实现上的不可在 JS 环境中访问到活动对象
激活对象 (Activation object) 有了变量对象存每个上下文中的东西,但是它什么时候能被访问到呢?就是每进入一个执行上下文时,这个执行上下文儿中的变量对象就被激活,也就是该上下文中的函数标示符、形参、变量声明等就可以被访问到了
EC 建立的细节
1、创建阶段【当函数被调用,但未执行任何其内部代码之前】
2、执行阶段
我们可以将每个执行上下文抽象为一个对象,这个对象具有三个属性
- ECObj: {
- scopeChain: {
- /* 变量对象(variableObject)+ 所有父级执行上下文的变量对象*/
- },
- variableObject: {
- /*函数 arguments/参数,内部变量和函数声明 */
- },
- this: {}
- }
解释器执行代码的伪逻辑
1、查找调用函数的代码。
2、执行代码之前,先进入创建上下文阶段:
3、激活 / 代码执行阶段:
VO --- 对应上述第二个阶段
- function foo(i){
- var a = 'hello'
- var b = function(){}
- function c(){}
- }
- foo(22)
当我们调用 foo(22) 时,整个创建阶段是下面这样的
- ECObj = {
- scopChain: {...
- },
- variableObject: {
- arguments: {
- 0 : 22,
- length: 1
- },
- i: 22,
- c: pointer to
- function c() a: undefined,
- b: undefined
- },
- this: {...
- }
- }
正如我们看到的,在上下文创建阶段,VO 的初始化过程如下( 该过程是有先后顺序的:函数的形参 ==>> 函数声明 ==>> 变量声明 ):
对于函数的形参没有什么可说的,主要看一下函数的声明以及变量的声明两个部分。
1、如何理解函数声明过程中 如果变量对象已经包含了相同名字的属性,则替换它的值 这句话?
看如下这段代码:
- function foo1(a){
- console.log(a)
- function a(){}
- }
- foo1(20)//'function a(){}'
根据上面的介绍,我们知道 VO 创建过程中,函数形参的优先级是高于函数的声明的,结果是函数体内部声明的 function a(){} 覆盖了函数形参 a 的声明,因此最后输出 a 是一个 function
2、如何理解变量声明过程中 如果变量名和已经声明的函数名或者函数的参数名相同,则不会影响已经存在的属性 这句话?
- //情景一:与参数名相同
- function foo2(a){
- console.log(a)
- var a = 10
- }
- foo2(20) //'20'
- //情景二:与函数名相同
- function foo2(){
- console.log(a)
- var a = 10
- function a(){}
- }
- foo2() //'function a(){}'
下面是几个比较有趣的例子,当做加餐小菜,大家细细品味。这里给出一句话当做参考:
函数的声明比变量优先级要高,并且定义过程不会被变量覆盖,除非是赋值
- function foo3(a) {
- var a = 10
- function a() {}
- console.log(a)
- }
- foo3(20) //'10'
- function foo3(a) {
- var a
- function a() {}
- console.log(a)
- }
- foo3(20) //'function a(){}'
AO --- 对应第三个阶段
正如我们看到的,创建的过程仅负责处理定义属性的名字,而并不为他们指派具体的值,当然还有对形参 / 实参的处理。一旦创建阶段完成,执行流进入函数并且激活 / 代码执行阶段,看下函数执行完成后的样子:
- ECObj = {
- scopeChain: {...
- },
- variableObject: {
- arguments: {
- 0 : 22,
- length: 1
- },
- i: 22,
- c: pointer to
- function c() a: 'hello',
- b: pointer to
- function privateB()
- },
- this: {...
- }
- }
对于下面的代码,相信很多人都能一眼看出输出结果,但是却很少有人能给出为什么会产生这种输出结果的解释。
- (function() {
- console.log(typeof foo); // 函数指针
- console.log(typeof bar); // undefined
- var foo = 'hello',
- bar = function() {
- return 'world';
- };
- function foo() {
- return 'hello';
- }
- }());
1、为什么我们能在 foo 声明之前访问它?
回想在 VO 的创建阶段,我们知道函数在该阶段就已经被创建在变量对象中。所以在函数开始执行之前,foo 已经被定义了。
2、Foo 被声明了两次,为什么 foo 显示为函数而不是 undefined 或字符串?
我们知道,在创建阶段,函数声明是优先于变量被创建的。而且在变量的创建过程中,如果发现 VO 中已经存在相同名称的属性,则不会影响已经存在的属性。
因此,对 foo() 函数的引用首先被创建在活动对象里,并且当我们解释到 var foo 时,我们看见 foo 属性名已经存在,所以代码什么都不做并继续执行。
3、为什么 bar 的值是 undefined?
bar 采用的是函数表达式的方式来定义的,所以 bar 实际上是一个变量,但变量的值是函数,并且我们知道变量在创建阶段被创建但他们被初始化为 undefined ,这也是为什么函数表达式不会被提升的原因。
总结:
1、 EC 分为两个阶段,创建执行上下文和执行代码。
2、每个 EC 可以抽象为一个对象,这个对象具有三个属性,分别为:作用域链 Scope , VO|AO ( AO , VO 只能有一个)以及 this 。
3、函数 EC 中的 AO 在进入函数 EC 时,确定了 Arguments 对象的属性;在执行函数 EC 时,其它变量属性具体化。
4、 EC 创建的过程是由先后顺序的:参数声明 > 函数声明 > 变量声明
参考
javascript 执行环境,变量对象,作用域链
What is the Execution Context & Stack in JavaScript
函数 MDN
来源: http://www.open-open.com/lib/view/open1492054673406.html