理解执行上下文和其生命周期

简介: 理解执行上下文和其生命周期

理解执行上下文和其生命周期


代码一旦变多,我们就会尝试写函数、拆文件、拆模块,从而让代码更容易。

将庞大的问题拆分成一个个小问题的思想,叫分治(突然想到皇帝的政策)。

同理,JS 引擎在执行阶段,会将把庞大的执行任务划分成不同的执行上下文,降低执行的复杂度。

执行上下文是啥

执行上下文,简言之,“执行代码的环境”。结合分类、生命周期更易懂些。

执行上下文分为 3 类:

  • 全局上下文 —— 全局代码所处的环境,不在函数中的代码都在全局执行上下文中
  • 函数上下文 —— 在函数调用时创建的上下文
  • eval上下文 —— 笔者不用eval,读者也别用,问就是没用过~

执行上下文的生命周期:

  • 创建阶段 —— 执行上下文的初始化状态,此时一行代码都还没有执行,只是做了一些准备工作(进栈)
  • 执行阶段 —— 逐行执行脚本里的代码
  • 消失阶段 -- 出栈

全局上下文的生命周期

JS 脚本运行起来之后,会第一时间创建全局上下文

全局上下文创建阶段做的事情:

  • 创建全局对象(Window/Global)
  • 创建 this ,并让它指向全局对象
  • 给变量和函数安排内存空间,默认给变量赋值为 undefined;将函数声明放入内存
  • 创建作用域链

执行阶段就开始执行代码。

而之前所谓的变量提升,只是变量的创建过程(在上下文创建阶段完成)和真实赋值过程(在上下文执行阶段完成)的不同步带来的一种错觉。

函数上下文的生命周期

和全局上下文大同小异。

函数上下文在函数调用的时候就会被创建。

  • 创建 arguments
  • 创建 this ,并让它指向调用对象
  • 给变量和函数安排内存空间,默认给变量赋值为 undefined;将函数声明放入内存
  • 初始化作用域链

执行阶段就开始执行代码。 执行完毕,就出栈了。

上面说到了初始化作用域链,怎么初始化?

执行上下文栈

JS 引擎在执行过程中,会为我们自动创建” 执行上下文栈 “(也叫调用栈)。

全局上下文执行栈始终在最底层。

调用函数的时候,函数上下文就会进栈,执行完就会出栈,也就是该上下文就结束了。

作用域链的生命周期

参考大神写的深入作用域链作用域链的生命周期:

  • 全局上下文创建阶段,声明函数的时候,就保存了根据词法所生成的作用域链
  • 函数调用之后,函数上下文被创建(进栈)
  • 函数上下文在创建阶段,会复制这个作用域链,作为自己作用域链的初始化,然后根据环境生成变量对象,将这个变量对象,添加到这个复制的作用域链
  • 函数上下文在执行阶段,会将当前作用域链的相关值更新
  • 函数执行完,函数上下文出栈,刚刚复制的作用域链失效

至于为什么会有两个作用域链,是因为在函数创建的时候并不能确定最终的作用域的样子,为什么会采用复制的方式而不是直接修改呢?应该是因为函数会被调用很多次吧。

举个例子:

var scope = "global scope";
function checkscope() {
  var scope2 = "local scope";
  return scope2;
}
checkscope();

全局上下文在创建阶段:

stacks = [globalContext];
checkscope.[[scope]] = [
    globalContext.VO
];

执行checkscope()这行代码的时候:

// checkscopeContext进栈
stacks = [checkscopeContext, globalContext];

checkscopeContext 在创建阶段:

// 先复制
checkscopeContext = {
    Scope: checkscope.[[scope]]
}
// 然后用 arguments 创建活动对象,随后初始化活动对象,加入形参、函数声明、变量声明
checkscopeContext = {
    AO: {
      arguments: { length: 0 },
      scope2: undefined
    },
    Scope: checkscope.[[scope]]
}
// 再将活动对象放在checkscope 作用域链顶端
checkscopeContext = {
    AO: {
        arguments: { length: 0 },
        scope2: undefined
    },
    Scope: [AO, [[Scope]]]
}

checkscopeContext 在执行阶段:

// 修改 AO 的属性值
checkscopeContext = {
  AO: {
    arguments: { length: 0 },
    scope2: "local scope"
  },
  Scope: [AO, [[Scope]]]
};

执行完之后,checkscopeContext 出栈

stacks = [globalContext];

为啥,作用域在嵌套的情况下,外部作用域是不能访问内部作用域的变量呢?

因为当 JS 引擎位于外部上下文的时候,表明内部上下文已经出栈了,其维护的自己的作用域链也消失了,自然访问不到其变量。

理解闭包作用域

闭包里,虽然父作用域在父函数执行完就出栈了,但是,因为父函数执行的时候,闭包函数本身也保存了一份父函数的作用域数据,所以当闭包函数执行的时候,仍可访问到父函数的作用域数据。

先看这个例子,参考大神的深入闭包

var scope = "global scope";
function checkscope() {
  var scope = "local scope";
  function f() {
    return scope;
  }
  return f;
}
// checkscope执行完之后,checkscopeContext是肯定出栈的
var foo = checkscope();
// 那么在foo执行的时候,为什么能访问
foo();

checkscope执行完之后,checkscopeContext是肯定出栈的。

那为什么foo执行的时候,还能访问checkscopeContext

联系之前的作用域生命周期来理解这个问题:

// 全局作用域在创建阶段:
fContext.[[scope]] = [
    checkscopeContext.VO,
    globalContext.VO
];
// checkscopeContext在运行阶段:
fContext.[[scope]] = [
    // 此处保留了checkscopeContext的AO,也就是保留了其arguments/参数值/变量值/定义的函数
    checkscopeContext.AO,
    globalContext.VO
];
// fContext在执行阶段:
fContext = {
    // 优先访问自己的AO,找不到,仍然可以访问checkscopeContext.AO
    Scope: [AO, checkscopeContext.AO, globalContext.VO],
}


目录
相关文章
|
2月前
|
存储 程序员 C++
C++程序局部变量:生命周期与作用域的探讨
C++程序局部变量:生命周期与作用域的探讨
27 1
|
2月前
|
存储 程序员
变量作用域与生命周期
变量作用域与生命周期
|
2月前
|
存储 安全 IDE
C/C++ 作用域,生命周期,执行线程的概念
C/C++ 作用域,生命周期,执行线程的概念
22 2
|
2月前
|
缓存 JavaScript
onActivated 生命周期的使用和介绍
onActivated 生命周期的使用和介绍
87 3
|
2月前
|
JavaScript 前端开发
生命周期钩子函数
生命周期钩子函数
20 1
|
2月前
|
前端开发 JavaScript Python
学不懂生命周期函数正常吗?
学不懂生命周期函数正常吗?
|
10月前
IT服务生命周期
IT服务生命周期
181 0
|
消息中间件 存储 自然语言处理
兄台: 作用域、执行上下文了解一下
• 作用域(Scopes) • 词法环境(Lexical environments) • 作用域链 • 执行上下文 • 调用栈
|
JavaScript 调度 开发者
生命周期函数-组件运行和销毁阶段的钩子函数|学习笔记
快速学习生命周期函数-组件运行和销毁阶段的钩子函数
189 0
生命周期函数-组件运行和销毁阶段的钩子函数|学习笔记
变量的生命周期
变量的生命周期
59 0