在我们前面理解了作用域之后,“作用域链”这个概念就产生了。那么作用域链是什么意思,它又是怎么形成的,跟哪些概念有关系,这就是我接下来几章想和大家探讨的内容:执行上下文、变量对象和作用域链。根据顺序我们也可以看出来,想要理解作用域链,执行上下文是我们碰到的第一个坎。

这一章我们就来讨论一下到底什么是执行上下文

1. 定义

当 JS 引擎开始执行预编译生成的代码时,就会进入到一个执行上下文环境(Executation Context - 简称 EC)。

在 ECMA 标准规范里并没有从技术角度去定义 EC 的具体类型和结构,这个是在实现 ECMAScript 引擎时需要考虑的问题。

但是在逻辑上,我们可以将活动的执行上下文看成一个栈结构。栈底部永远是全局上下文(global context),而顶部就是当前活动的执行上下文。执行到当前代码时,上下文入栈,执行完毕后,上下文出栈。

2. 可执行代码有几种

前面说到当引擎执行到可执行代码的时候,就会将当前上下文压入上下文栈中。那么可执行的代码又分为几种?

在这里,我们先假设定义执行上下文栈是一个数组:

EC = [];

第一种可执行代码 -- 全局代码:
全局类型代码是在加载外部的 js 文件或者本地 <script></script> 标签中的代码。
注意,在全局代码中,并不包含定义在全局环境 function 内的代码

程序启动后进入初始化全局环境:

EC = [
    globalContext
];

第二种可执行代码 -- 函数代码:
当定义的函数被执行时,就进入了函数代码,当前函数上下文被压入 EC 栈中。
注意,在函数代码中,也不包含定义在该函数内部环境 function 内的代码。

例如:

var a = 10;

function foo () {
  var b = 20;
  
  foo();
}

foo();

这个例子中的 EC 是什么样子的呢?

// 初始化
EC = [
  globalContext
];

// 第一次调用 foo 函数
EC = [
  <foo> functionContext,
  globalContext
];

// 在 foo 内递归调用自己
EC = [
  <foo> functionContext - recursively,
  <foo> functionContext,
  globalContext
];

// 继续递归调用自己
EC = [
  ......
  <foo> functionContext - recursively2,
  <foo> functionContext - recursively,
  <foo> functionContext,
  globalContext
];

// 递归会不断调用下去,因为没有结束条件,所以这是一个死循环
// 所以,EC 只会不断增加新的上下文,但是却不会退出

只有每次 return 的时候,才会退出当前执行上下文,相应上下文会从栈中弹出,栈指针会自动移动位置。

注意,当函数没有明确指明 return 什么的时候,默认 return undefined

如果有抛出的异常没有被截获的话,也有可能从一个或多个执行上下文中退出。当所有代码执行完以后,EC 中只会包含全局上下文(global context),当程序退出以后,全局上下文也会退出。

第三种可执行代码 -- eval 代码:
eval 函数在调用的时候会产生上下文。
例如:

eval('var a = 10');

(function foo () {
  eval('var b = 20');
}());

alert(a);    // 10
alert(b);    // ReferenceError,b is not defined 

这个例子中 EC 的变化如下:

// 初始化
EC = [
  globalContext
];

// eval('var a = 10');
EC = [
  evalContext,
  globalContext
];

// eval 执行完毕
EC = [
  globalContext
];

// 立即执行函数 foo
EC = [
  <foo> functionContext,
  globalContext
];

// eval('var b = 20');
EC = [
  evalContext,
  <foo> functionContext,
  globalContext
];

// eval 执行完毕
EC = [
  <foo> functionContext,
  globalContext
];

// foo 执行完毕
EC = [
  globalContext
];

这就是一个典型的逻辑调用上下文栈

在 setTimeout 和 setInterval 函数中的第一个参数也可以传入代码字符串,但是这个一般不会这么去用,所以这里也就不讨论了。

3. 结论

执行上下文环境是我们了解变量对象作用域链的基础,大家一定要好好理解(其实也并不难),下一节我们来讨论变量对象,相信会让大家有一定的收获。

欢迎关注我的公众号

微信公众号


leocoder
4.2k 声望425 粉丝

stay hungry, stay foolish