(一) js执行上下文

1. 执行上下文

执行上下文 (上下文), 其实就是js代码运行时所处的环境

简而言之: 上下文就是一个运行时环境

我们知道, js中的变量有全局变量和局部变量之分

  • 全局变量: 定义在 全局环境
  • 局部变量: 定义在 函数内部

只有有了全局环境, 才能有函数环境

对于全局环境, 我们又称之为 全局上下文, 对于函数内部环境, 称之为 函数上下文

  • 全局上下文是打开浏览器就会自动创建的

  • 函数上下文是在函数被调用的时候创建的

在上下文创建以后, 并不会立即执行我们写的js代码, 而是会先进行一个名为 **预编译 **的过程, 根据上下文的不同, 预编译又可以分为:

  • 全局预编译
  • 函数预编译

对于以上概念, 可以用一个例子说明:

​ 假设城市是最高层 (忘掉省,国等概念), 我们要去一个陌生的城市 A , 在A城市中的 B 公司 进行工作 (执行代码), 那么城市A和公司B对我们而言, 是全新的生活环境 (上下文环境), 对于公司而言, 肯定是得有城市, 才能有公司, 因为我们假设城市是最高层, 因此 城市A就是 全局环境 (全局上下文), 公司B就是 局部环境(函数上下文)

​ 既然城市A和公司B是全新的环境, 那我们对于新环境是不是得先 熟悉一段时间 (预编译), 当我们 (js代码) 先来到 城市A (全局环境), 要先对 城市A进行一个熟悉 (全局预编译), 熟悉得差不多之后, 我们就可以直接去要工作的B公司 (执行全局代码), 来到B公司以后, 又要对公司进行一个熟悉 (函数预编译), 熟悉差不多之后, 就可以在公司干活了 (函数代码执行)

​ 工作了一段时间后, 我们发现, 这里不适合我们, 于是打道回府, 离开了公司B (函数上下文释放), 离开了城市A (全局上下文释放 => 浏览器关闭)


2. 预编译

预编译是上下文创建之后, js代码执行前的一段时期, 在这个时期, 会对js代码进行预处理

那么进行什么样的处理呢 ?

在解决这个问题之前, 先引入一个新的概念 变量对象VO (Variable Object)

2.1 变量对象VO

每个执行上下文都有一个与之相关联的变量对象 (Variable Object, 简称 VO), 当前执行上下文中所有的变量函数都保存在其中

2.2 全局预编译

全局上下文创建之后, 会进行全局预编译, 全局预编译会进行如下预处理:

  • 寻找变量声明, 变量名作为VO对象的属性名, 属性值置为 undefined
  • 寻找函数声明, 函数名作为VO对象的属性名, 属性值为函数本身
  • 如果函数名与变量名冲突, 函数声明会将变量声明覆盖

预编译结束以后, 再进行逐行代码执行

什么意思?

示例

console.log(a)			// function a () { }
var a = 100;
function a () { }
console.log(a)     // 100

代码执行之前: 
首先会创建全局执行上下文, 并有与之对应的变量对象VO被创建
VO {}
然后寻找变量声明: var a;   变量名作为VO对象的属性名, 属性值置为 `undefined`
VO: {a:undefined}
寻找函数声明: function a () { }  函数名作为VO对象的属性名, 属性值为函数本身
但是此时函数名与变量名冲突, 函数声明会将变量声明覆盖,因此
VO: {a: function a () { }}
预编译至此结束, 开始代码执行
首先打印 a    function a () { }
接着为a赋值为100
函数声明, 跳过
再次打印a, 上面a被赋值为100, 因此输出100

2.3 函数预编译

上面我们说过: 函数执行上下文是在函数被调用的时候创建的

只要函数不被调用, 那么就不会进行函数的预编译

而函数预编译与全局预编译, 唯一的不同之处就在于: 函数可能会有形参

还有一点区别就是: VO在函数预编译阶段被叫做AO (活动对象), 两者本质一样, 只不过换了个说法

函数预编译的过程:

  • 寻找变量声明, 变量名作为AO对象的属性名, 属性值置为 undefined
  • 寻找形参, 形参名作为AO对象的属性名, 属性值置为 undefined
  • 如果形参名与变量名冲突, 形参会将变量声明覆盖
  • 将实参的值赋予形参, 即替换 AO对象中形参的属性值
  • 寻找函数声明, 函数名作为AO对象的属性名, 属性值为函数本身
  • 如果函数名与变量名冲突, 函数声明会将变量声明覆盖

示例

function a(b, c) {
  console.log(b);          		// 1
  var b = 0
  console.log(b);			 // 0
  var b = function () {
    console.log('bbbb')
  }	
  console.log(c);			// undefined
  console.log(b);			// function () {console.log('bbbb')}	
}
a(1)

例题

function foo() {
    console.log(a);
    a = 1;
}

foo(); // ???

function bar() {
    a = 1;
    console.log(a);
}
bar(); // ???

3. 执行上下文栈

栈: LIFO 先进先出的一种数据结构

我们知道, 函数里面可以嵌套函数, 不同的函数调用又会形成不同的执行上下文, 这些不同的上下文环境我们同一放进执行上下文栈中进行管理, 栈底为全局上下文, 每当有一次函数调用, 形成的函数上下文就会被push进栈顶, 函数执行完, 该函数所对应的函数上下文将会被pop出上下文栈

示例

function fn1() {
  function fn2() {
    console.log(1);
  }
  fn2()
}
fn1()

上述代码共在上下文栈中push了几个上下文?

首先有一个全局的上下文, 接着进行全局预编译, 接着fn1被调用, 会创建fn1的上下文, 压入执行上下文栈, 然后进行fn1的预编译, 接着调用fn2, 创建fn2的上下文,压入执行上下文栈, 然后fn2预编译, 接着执行fn2的打印语句, fn2执行完毕后, 对应的执行上下文从执行上下文栈中pop出, 此时fn1也被执行完, 对应的执行上下文也从上下文栈中pop出


理解上下文对后面作用域链和闭包的理解起到了重要作用

posted @ 2021-07-26 13:57  只猫  阅读(905)  评论(1编辑  收藏  举报