重点:
- [x] 理解执行上下文(执行环境)在创建阶段的活动。
一、什么是执行上下文
执行上下文(execution context),可以理解为是当前代码的执行环境
二、执行上下文的类型
1. 在执行JS程序时,每遇到一段可执行代码,都会创建一个执行上下文。JS中的可执行代码分为三种:
- 全局代码
- 函数代码
- eval代码
2. 每种可执行代码对应一种执行上下文,因此JS中执行上下文也被分为三类:
- 全局执行上下文
- 函数执行上下文
- eval执行上下文(eval不推荐使用,因此后文不再阐述eval执行上下文)
3. 全局执行上下文
- 全局执行上下文只有唯一的一个,它与ECMAScript的宿主环境有关,比如在web浏览器中,全局执行上下文表示的是Window对象;
- 当应用程序退出的时候,比如关闭网页或者浏览器,全局执行上下文才会被销毁
4. 函数执行上下文
- 每个函数都有自己的执行上下文,且每被调用一次都会产生一个新的函数执行上下文,即使是调用自身的函数,也是如此;
- 因此也可以得出:函数执行上下文的个数没有限制。
- 当函数执行完后,其对应的函数执行上下文就会被销毁。
5. 执行上下文的特点
- 单线程:不能同时执行多个;
- 同步执行:按顺序执行;
- 全局执行上下文只有一个,在应用程序退出时被销毁;
- 函数执行上下文的数目没有限制,在函数执行完后被销毁;
- 每次某个函数被调用时,就会有新的执行上下文产生,即使是调用的自身函数,也是如此。
三、执行上下文栈
1. JS管理执行上下文的方式
一个JS程序会产生多个执行上下文,JS通过栈(先进后出、后进先出)的存取方式来管理执行上下文,我们可以称其为执行栈。
2. 执行栈的工作过程
- 入栈:程序执行进入一个执行环境时,它的执行上下文就会被创建,并被压入执行栈中,因此全局上下文是第一个入栈的,处于栈底;
- 执行:处于栈顶的是当前正在执行的上下文;
- 出栈:当处于栈顶的上下文执行完后,它就会被销毁(全局上下直到应用程序退出才会被销毁),并出栈,控制权交由下一个执行上下文(如果有闭包会阻止该操作,也就是执行上下文不会被销毁)。
四、执行上下文的生命周期
执行上下文是有生命周期的,主要分为两个阶段(对照执行上下文栈)
1、创建阶段
- 建立作用域链
- 确定this指向
- 创建变量对象
- 变量对象:用于存储执行上下文中定义的变量和函数声明,每个执行上下文都有一个与之关联的变量对象,比如在web浏览器中,全局上下文的变量对象为Window对象;
- 创建变量对象主要有三个阶段:
-
变量声明:在当前上下文中每找到一个变量声明,就会在变量对象上添加一个同名属性,该属性只声明不赋值,如果变量对象上已经存在与该属性同名的其他属性,该属性不会对其他同名属性的值产生影响;
<!-- 变量提升:使用var定义的变量会产生变量提升,原因就在于此 --> console.log(a); // undefined var a; console.log(b); // undefined var b = 2;
-
函数声明:每找到一个函数声明,同样也会在变量对象上添加一个以函数名命名的属性,且属性值为该函数的引用,如果变量对象上已经存在与函数名同名的属性,那么它会被覆盖;
<!-- 函数提升:当变量声明与函数声明同名的时候,函数声明会覆盖变量声明; 当有多个同名的函数声明时,后面的函数声明会覆盖之前的函数声明。 --> foo(); // wuwuwuwu function foo() { console.log('wuwuwuwu'); } <!-- 使用函数字面量的形式创建函数时,此时f为一个普通变量,只是它的值为函数的引用, 因此在上下文执行阶段会被添加到变量对象上且值为undefined --> f(); // TypeError: f is not a function,因此此时f为undefined var f = function () { console.log('hahaha'); }
-
函数的形参:当进入某个函数执行上下文时,如果该函数有形参,则在该函数上下文的变量对象上添加一个与形参同名的属性,且该属性值为实参的值,对于没有传递的参数,其值为undefined。(在函数执行上下文的创建阶段,如果该函数内部有其他变量或函数,同样会进行变量声明、函数声明)
-
- 以某个函数的执行上下文为例说明:
当调用foo(22)时,创建阶段的变量对象为:
2、执行阶段
- 当创建阶段完成后,就会进入到执行阶段。此时变量对象会转换为活动对象,活动对象上的属性允许被外界访问。
- 变量对象和活动对象是同一对象,在执行上下文不同生命周期的不同称呼。