写在前面:前段时间参加了金山的前端面试,面试过程中经常get不到面试官的点,有时候即使理解了面试官的意思,却又不能准确的表达出来。让我意识到当前最大的问题是表达沟通能力不足。所以期望通过撰写学习文档的方式,来提升自己的表达和总结的能力。
执行上下文
基本概念
执行上下文是指当前Javascript代码被解析和执行时所在环境的抽象概念,JavaScript 任何代码的运行都是在执行上下文中。
执行栈
js代码运行在执行栈中,执行栈也叫做调用栈,具有后进先出的栈结构,用于存储js代码在执行过程中创建的上下文环境。
<script>
baz();
foo();
function baz() {
console.log('in baz, we canot get bar: ', bar);
}
</script>
<script>
function foo() {
console.log('in foo, wen can get bar: ', bar);
}
foo();
var bar = 1;
baz();
</script>
以上面js文件执行为例,来简单阐述调用栈在函数执行过程中的状态。
- 在js引擎读取到页面js代码时,创建全局执行上下文,放到执行栈中。从第一段js文本中可以看出,此时全局对象中只包含baz这个函数变量。
- 创建完全局执行上下文之后,开始执行这段js代码,碰到baz(),将创建baz函数执行上下文。在函数执行过程中,我们尝试访问变量bar,但是在整个作用域链(下文解析作用域链概念)中都没有这个变量的定义。所以这时候会抛出referenceError错误。
Uncaught ReferenceError: bar is not defined
。然后终止函数执行,将baz执行上下文移除。 - 由于碰到异常,第一段js代码提前结束。
- 此时,继续解析第二段js代码,由于全局上下文已经被创建,则继续向这个上下文中添加环境变量。包括
bar = undefined; foo: fun
- foo()执行foo函数,创建foo函数执行上下文,并执行,由于这时候bar并未被赋值,所以输出的bar值为undefined。执行完毕,移除foo上下文。
- 继续执行
bar = 1
赋值操作,然后执行函数baz。将创建baz执行上下文,因为只存在一个全局执行上下文,所以此时baz的外部环境对象的引用包括已经赋值的bar变量,所以会输出1。执行完毕,移除baz上下文。 - 当页面销毁时,移除全局执行上下文。
上下文分类
- 全局执行上下文 js引擎首次读取页面js文件时,会创建一个全局的执行上下文并推入执行栈。任何定义在全局中的变量都存在在这个上下文中。函数的运行也是基于这个全局执行上下文。
- 函数执行上下文 函数执行上下文只有在函数调用阶段才会被创建并放到调用栈中,函数执行完毕就会从调用栈中释放。在全局作用域无法访问函数作用域中定义的变量,就是因为函数中的变量只在函数调用生命周期中存在。(闭包情况例外,下文解释)
- eval函数执行上下文 运行在 eval 函数中的代码也获得了自己的执行上下文,Javascript不推荐使用eval函数。
如何工作
执行上下文分为两个阶段,创建阶段和执行阶段。
创建阶段
创建阶段主要完成三件事情。
- this绑定(这解释了为什么this的值取决于函数的调用方式)
- 创建词法环境。
- 创建变量环境
this绑定
根据当前函数的执行环境来绑定函数的this值,比如作为对象属性调用,this的值将被绑定到对象实例上。
词法环境
定义:词法环境是一种规范类型,基于 ECMAScript 代码的词法嵌套结构来定义标识符与特定变量和函数的关联关系。词法环境由环境记录(environment record)和可能为空引用(null)的外部词法环境组成。
词法环境的组成 词法环境分为两个部分:环境记录;对外部环境的引用。
- 环境记录是指存储变量和函数声明的实际位置
- 对外部环境引用意味着他可以访问其他词法作用域,闭包原理基于此。
词法环境分为两种:
1)全局环境:在全局执行上下文中,是一个没有外部环境的词法环境。其外部引用为null。它拥有一个全局对象(window 对象)及其关联的方法和属性(例如数组方法)以及任何用户自定义的全局变量,this 的值指向这个全局对象。
2)函数环境:用户在函数中定义的变量被存储在环境记录中,其外部引用可以是函数环境也可以是全局环境。对于函数环境而言,还包含一个arguments对象,该对象包含函数参数的引用,是一个类数组结构,如
function foo(a, b) {
var c = a + b;
}
foo(2, 3);
// arguments 对象
Arguments: {0: 2, 1: 3, length: 2}
环境变量分为两种
- 声明性环境记录 存储变量、函数和参数。一个函数环境包含声明性环境记录。
- 对象环境记录 用于定义在全局执行上下文中出现的变量和函数的关联。全局环境包含对象环境记录。
变量环境
变量环境也是词法法环境,用于存储var声明的变量。
何谓声明提升
执行上下文在创建阶段,会扫描并解析变量和函数声明。其中函数声明存储在环境中,而变量会被设置为 undefined(在 var 的情况下)或保持未初始化(在 let 和 const 的情况下)。这就是所谓的声明提升
执行阶段
在此阶段,完成对所有变量的分配,最后执行代码。如果找不到let变量的值,将会为其分配undefined。
小结
理解函数执行上下文,可以让我们知道一段js代码按照什么样的顺序被执行和调用。有助于我们理解声明提升,闭包,函数作用域,this绑定等等概念。概括起来就是:
- js引擎在js首页被解析的时候,会创建一个全局执行上下文,这个全局上下文贯穿页面的整个生命周期。
- 函数被调用的时候,会创建函数的执行上下文,包含环境记录(收集函数声明和变量定义)和环境上下文引用(外部环境--闭包或者全局上下文)。函数执行完毕,函数上下文就会被移出调用栈。
- 执行上下文有两个阶段,创建阶段会绑定this,收集函数声明和变量定义以及外部环境引用。执行阶段会进行变量赋值与函数调用。