阅读 62

浅谈js执行上下文

image.png

1.基本概念

  • 什么是执行上下文?当函数在运行的时候,会创建一个执行环境,这个执行环境就叫执行上下文(execution context).

  • 数据是如何存储的?在执行上下文中会创建一个变量对象(variable object),基础数据类型的变量名和值会保存在变量对象中,引用类型的值保存在堆中,变量对象中存储的是内存地址,内存地址指向堆中具体的值,我们可以通过操作对象的引用地址来操作对象.

  • 基础数据类型和引用数据类型分类:基本数据类型有7种,分别是Undefined、Null、Boolean、Number、String、BigInt(es10新增)和Symbol(es6新增),引用数据类型有1种Object.

    1.1. 数据的存储,下面笔者通过demo和图来描述一下数据具体是如何存储的

    function data () {
        var a = 1;
        var b = {
            name: 'jack'
        }
        var c = [1, 2, 3];
    }
    
    // 执行上下文
    let ExecutionContext = {
        // 变量对象
        VO: {
            a: 1,
            b: '0x1111',
            c: '0x2222'
        }
    }
复制代码

image.png

1.2. 数据的操作,基本数据类型赋值是值的拷贝,复制的是值本身;

    var a = 1;
    var b = a;
    b = 2;
    console.log(a); // 1
    var ExecuteContext = {
        VO: { a: 1 }
    };

    ExecuteContext.VO.b = ExecuteContext.VO.a;
    ExecuteContext.VO.b = 2;
    console.log(ExecuteContext.VO.a); // 1
复制代码

image.png

引用数据类型赋值,复制的是引用地址指针;

    var m = { a: 1, b: 2 };
    var n = m;
    n.a = 10;
    console.log(m.a); // 10
    var ExecuteContext = {
        VO: { m: { a: 1, b: 2 } }
    };

    ExecuteContext.VO.n = ExecuteContext.VO.m;
    ExecuteContext.VO.n.a = 10;
    console.log(ExecuteContext.VO.m.a); // 10
复制代码

image.png

2. 执行上下文的分类

  • js代码在执行的时候会进入一个执行上下文,可以理解为当前代码的运行环境

  • 在js中运行环境主要分为全局执行上下文环境和函数执行上下文环境

    • 全局执行上下文只有一个,在客户端中一般由浏览器创建,也就是我们熟悉的window对象,我们可以通过this直接访问到它。(window === this ?)
    • window对象同时也是var声明的全局变量的载体,我们通过var创建的全局变量,都可以通过window直接访问。

3. 执行上下文栈

  • 在js执行过程中会产生多个执行上下文,js引擎会由栈来管理这些执行上下文
  • 执行上下文栈也叫调用栈(call stack),执行上下文栈用于存储代码执行期间创建的所有上下文,具有LIFO(Last in first out)后进先出,先进后出的特点
  • 栈底永远是全局上下文(window),栈顶为当前正在执行的上下文
  • 当一个函数执行时会生成一个新的执行上下文并放入调用栈,执行完毕后会自动出栈
    function one() {
    var a = 1;
    debugger;
    function two() {
        var b = 1;
        debugger;
        function three() {
            var c = 1;
            debugger;
        }
        three();
        debugger;
    }
    two();
    debugger;
}
one();
var globalExecuteContext = {
    VO: { setTimeout: 'setTimeout' }
}
var executeContextStack = [globalExecuteContext];
var oneExecuteContext = {
    VO: { a: 1 }
}
executeContextStack.push(oneExecuteContext);
var twoExecuteContext = {
    VO: { b: 2 }
}
executeContextStack.push(twoExecuteContext);
var threeExecuteContext = {
    VO: { c: 3 }
}
executeContextStack.push(threeExecuteContext);
console.log(executeContextStack);

executeContextStack.pop();
executeContextStack.pop();
executeContextStack.pop();
复制代码

4. 执行上下文的生命周期

  • 一个新的执行上下文的生命周期有两个阶段:创建阶段和执行阶段

    • 创建阶段

      • 创建变量对象
      • 确定作用域连
      • 确定this指向
    • 执行阶段

      • 变量赋值
      • 函数赋值
      • 代码执行
  • 变量对象(VO)

    • 变量对象会保存变量声明(var)、函数参数(arguments)、函数定义(function)

      • 变量对象会首先获得函数的参数变量和值
      • 获取所有用function进行的函数声明,函数名为变量对象的属性名,值为函数对象,如果属性已经存在,值会用新值覆盖
      • 再依次将所有的var关键字进行变量声明,每找到一个变量声明,就会在变量对象上建一个属性,值为undefined,如果变量名已经存在,则会跳过,并不会修改原属性值,let声明的变量并不会在此阶段进行处理
  • 活动对象(AO)

    • 在函数的调用栈中,如果当前执行上下文处于函数调用栈的顶端,则意味着当前上下文处于激活状态,那么此时变量对象称为活动对象(Activation Object), 从VO(变量对象)转变成AO(活动对象)

    • 活动对象包含变量对象所有的属性,并且包含this指针

function one(m) {
    function two() {
        console.log('two');
    }
}
one(1);

//执行阶段 VO=>AO
let VO = AO = {
    m:1,
    two: () => { console.log('two'); },

}

// 执行上下文
let oneEC={
    VO,
    this: window,
    // 作用域链
    scopeChain:[VO,globalVO] 
}
复制代码
  • 全局上下文的变量对象

    • 在浏览器里,全局对象为window
    • 全局上下文的变量对象为window,而且这个变量对象不能激活变成活动对象
    • 只要浏览器窗口打开,全局上下文会一直存在,所有的上下文都可以直接访问全局上下文变量对象上的属性
    • 只有全局上下文的变量对象允许通过VO的属性名称来间接访问,在函数上下文中是不能直接访问VO对象的
    • 未进入执行阶段前,变量对象中的属性都不能访问,但是进入到执行阶段之后,变量对象转变成了活动对象,里面的属性就都能被访问了,对于函数上下文来讲,活动对象和变量对象其实都是同一个对象,只是处于执行上下文的不同生命周期

End


本文更多的是像记录笔记,有不对的地方,望各位大佬多多指正。

文章分类
前端
文章标签