EXIT

00:00:00

JS进阶-执行上下文

什么是执行上下文?

执行上下文是 JavaScript 引擎在执行代码时创建的一个环境,用于管理代码运行时的变量、函数、作用域链等信息。当JS引擎解析到可执行代码片段时,会优先做一些执行前的准备工作,这个准备工作完成的结果就叫做执行上下文.也就是每当JS代码执行的时候,它总是在执行上下文中运行的.

执行上下文有哪几种类型?

执行上下文主要分为三种类型:

  • 全局执行上下文:最基础的执行上下文环境,它会创建全局对象(浏览器中为window,严格模式为undefined)并让this等于这个全局对象.一个程序中只能有一个全局执行上下文.

  • 函数执行上下文:每当一个函数被调用时,就会为当前函数创建一个新的函数执行上下文,每个函数都有它自己的执行上下文,关键是该执行上下文是在函数被调用的时候才会创建的.当有多个函数被嵌套调用时,将会有**执行上下文栈(调用栈)**进行管理.

    “箭头函数不生成独立执行上下文,完全复用外层 this / arguments,调用栈不会因此多一层。”

  • **eval函数执行上下文:**eval函数内部的代码也会有它自己的执行上下文.

执行上下文的生命周期?

执行上下文的生命周期可以大致分为三个阶段: 创建阶段 -> 执行阶段 -> 销毁阶段

-> 创建阶段

执行上下文是在代码执行过程中进行创建的.不是“提前遍历”出来的,而是“执行到调用语句”那一刻才当场创建、压栈;创建完后立即进入函数体,整个执行期都呆在这个上下文里,直到函数返回弹出。

执行上下文在创建阶段会进行三件事:

  • 确定this值: 在全局执行上下文中,this为全局对象.在函数执行上下文中,this值取决于函数如何被调用.如果被引用对象调用,则this指向该引用对象.否则就是全局对象.

  • 创建变量环境组件: 主要是var关键字声明的变量和函数声明.(函数执行上下文中还会有参数arguments) 变量提升

  • 创建词法环境组件: 主要是ES6之后出现的关键字let const声明的变量. 暂时性死区. 后来,出于一致性,函数声明也会被同步拷贝到词法环境.

    词法环境内部有两个组件:

    • 环境记录器:存储变量和函数声明的实际位置
    • 外部环境引用:可以访问父级词法环境,如果当前在函数执行上下文中,则还包含argument和其length

-> 执行阶段

执行变量赋值,函数调用,代码执行.

执行上下文中变量获取优先从词法环境中获取.

-> 回收阶段

当前执行上下文出栈并等待虚拟机回收.

执行上下文栈(调用栈)?

在代码执行期间,JS引擎会使用调用栈来管理执行上下文.该栈遵循后进先出的规则.引擎会始终执行位于栈顶的函数,当该函数执行结束后,会将栈顶的执行上下文弹出,并继续执行下一个上下文.

例如:

1 function outer() { 2 let a = 1; 3 console.log('inner', g); 4 function middle() { 5 let b = 1; 6 console.log('middle', a); 7 function inner() { 8 let c = 1; 9 console.log('inner', a, b, c, x); 10 } 11 inner(); // ③ 12 } 13 middle(); // ② 14 } 15 let g = 1; 16 outer(); // ① 17 var x = 100;

首先压入栈的是全局执行上下文:

-> 调用栈: 全局

-> 在执行到outer()代码前,全局执行上下文内部环境如下所示:

{ 变量环境:[ function outer() {...}, ], 词法环境:[ g:1, ] }

执行到outer函数后,会开启一个函数执行上下文并压入栈顶:

-> 调用栈: 全局 -> outer执行上下文

-> outer执行上下文内部环境如下所示:

{ 变量环境:[ function middle() {...}, ], 词法环境:[ a:1, [父级执行上下文环境] ] }

执行到middle函数后,会开启一个函数执行上下文并压入栈顶:

-> 调用栈: 全局 -> outer执行上下文 -> middle执行上下文

-> middle执行上下文内部环境如下所示:

{ 变量环境:[ function inner() {...}, ], 词法环境:[ b:1 [父级执行上下文环境] ] }

此时执行代码 console.log('middle', a); 变量a会先在当前middle执行上下文内部查找,未查找到则去父级执行上下文中查找.

同理inner()...

函数调用完毕后,调用栈挨个弹出.

... 最终结果就是:

inner 1 middle 1 inner 1 1 1 undefined

闭包情况下的执行上下文

闭包就是外层函数的执行上下文已经出栈销毁了,但是它的变量环境仍然被内层函数的作用域链引用,仍然存活在内存堆里,这就是闭包.

例如:

function outer() { let a = 0; function inner() { console.log(++a); } return inner; } const counter = outer(); counter(); // 1 counter(); // 2

调用栈为:全局global

此时全局执行上下文为:

{ 变量环境:function outer{...} }

--- 然后开始执行outer函数,进入outer函数内部:

调用栈为:全局global -> outer

此时outer执行上下文为:

{ 变量环境:function inner{...}, 词法环境:a:1,父级执行环境global }

outer函数执行完毕,将inner函数返回出去,此时调用栈为:全局global,按理来说outer执行上下文销毁,但是其变量环境function inner内部执行时,a还需要从父级执行环境outer中获取,所以父级执行环境中的a不能清除.始终占用一部分内存.

--- 然后开始执行counter函数.counter = inner

调用栈为:全局global -> counter(inner)

counter(inner)的执行上下文为:

{ 词法环境:父级执行环境outer }

执行 console.log(++a)语句时,a需要从父级执行环境中获取outer,因此形成闭包.

uid:q3U7if
VOIDIS.ME
  1. no-like
  2. message
  3. Bilibili
  4. Github
  5. RSS
  6. sun