JS — (5) 执行上下文及其属性(作用域链、活动对象)

JS引擎解析代码,不是一行一行去解析的,而是一段一段地分析执行。且当执行一段代码时,会进行一些准备工作:变量提升、函数提升、创建作用域链、创建执行上下文。【这些准备工作,用专业点的说法,就是执行上下文

<1> JS的可执行代码类型:
  • 全局代码
  • 函数代码
  • eval代码
<2> 执行上下文栈 && 全局上下文 && 函数上下文

每个函数都有自己的函数上下文,那那么多函数就对应有很多的函数上下文,那我们是如何来管理这些函数上下文的呢?—- 上下文栈!!!

  • 执行上下文栈的栈底永远是全局上下文【globalContext】

         - 最先遇到的就是全局代码,所以初始化的时候首先就会向执行上下文栈压入一个全局执行上下文,我们用 globalContext 表示它,并且只有当整个应用程序结束的时候,ECStack 才会被清空,所以程序结束之前, ECStack 最底部永远有个 globalContext
    
  • 后面,每执行一个函数,就会为该函数创建函数执行上下文,并把该函数执行上下文压入执行上下文栈中;并且,当函数执行完毕后,又会把该函数的执行上下文从执行上下文栈中弹出!!

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

 // 对应的执行上下文栈的变化如下:
ECStack.push(<checkscope> functionContext);
ECStack.push(<f> functionContext);
ECStack.pop();
ECStack.pop();

区别:

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

 // 对应的执行上下文栈的变化如下:
ECStack.push(<checkscope> functionContext);
ECStack.pop();
ECStack.push(<f> functionContext);
ECStack.pop();
<3> 每个执行上下文的重要属性:【变量对象、作用域链、this】

对于每个执行上下文,都有三个重要属性:

  • 变量对象(Variable object,VO)
  • 作用域链(Scope chain)
  • this
(1) 变量对象

变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。

  • 全局上下文的变量对象就是全局对象 window,用VO表示!!!
  • 函数上下文的变量对象用活动对象 AO 表示!!!

活动对象和变量对象其实是一个东西,只是变量对象是规范上的或者说是引擎实现上的,不可在 JavaScript 环境中访问,只有到当进入一个执行上下文中,这个执行上下文的变量对象才会被激活,所以才叫 activation object 呐,而只有被激活的变量对象,也就是活动对象上的各种属性才能被访问。

执行上下文的不同阶段,活动对象的变化:

执行上下文的代码会分成 两个阶段 进行处理:分析和执行,我们也可以叫做:

  1. 进入执行上下文
function foo(a) {
  var b = 2;
  function c() {}
  var d = function() {};

  b = 3;

}

foo(1);

////// 此时的 AO

AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: undefined,
    c: reference to function c(){},
    d: undefined
}
  1. 代码执行 【会顺序执行代码,并根据代码,修改活动对象的值】
AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: 3,
    c: reference to function c(){},
    d: reference to FunctionExpression "d"
}

经典例题:

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

foo(); // ???  报错,"a" 并没有通过 var 关键字声明,所有不会被存放在 AO 中。

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

(2) 作用域 && 作用域链

作用域

作用域是指程序源代码中定义变量的区域。规定了如何查找变量,也就是确定当前执行代码对变量的访问权限

静态(词法)作用域【JS】 vs 动态作用域

  • 静态(词法)作用域,函数的作用域在函数定义的时候就决定了。

  • 动态作用域,函数的作用域是在函数调用的时候才决定的。

  • 静态作用域下的,函数作用域是定义该函数所在的作用域;而动态作用域下的,函数作用域是调用该函数时所在的作用域!!!!

var value = 1;

function foo() {
    console.log(value);
}

function bar() {
    var value = 2;
    foo();
}

bar();

// 结果是 1

执行 foo 函数,先从 foo 函数内部查找是否有局部变量 value,如果没有,就根据书写的位置,查找上面一层的代码, 也就是 value 等于 1,所以结果会打印 1。

作用域链

作用域链 由多个上下文的变量对象组成的链表。 当查找变量时,会先从当前执行上下文的变量对象中查找;如果没有找到,就去父级的执行上下文的变量对象中找,一直找到全局上下文的变量对象,即 window 身上,如果都没有找到,就会报错!!!!

1) 函数创建时的作用域链

即因为 JS 是静态作用域,所以函数的作用域在函数定义时就已经创建了,这是因为,函数内部有一个属性 [[scope]] ,当函数创建的时候,就会保存所有父变量对象到其中,你可以理解 [[scope]] 就是所有父变量对象的层级链.

 
function foo() {
    function bar() {
        ...
    }
}

// 函数创建时,各自的[[scope]]为:
foo.[[scope]] = [
  globalContext.VO
];

bar.[[scope]] = [
    fooContext.AO,
    globalContext.VO
];

2) 函数激活(执行)时的作用域链

当函数执行时,该函数执行上下文的变量对象,将被创建并且激活,然后会被挂在其作用域链的前端!!!

<4> 函数执行上下文中作用域链和变量对象的创建过程

var scope = "global scope";
function checkscope(){
    var scope2 = 'local scope';
    return scope2;
}
checkscope();
1) checkscope() 被创建:保存父级作用域链到内部属性[[scope]]
checkscope.[[scope]] = [
    globalContext.VO 
];
2) checkscope() 被执行:创建 checkscope 函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈
ECStack = [
    checkscopeContext,
    globalContext
];
3) 根据[[scope]]属性,创建作用域链【在执行上下文中】:复制函数[[scope]]属性创建作用域链
checkscopeContext = {
    Scope: checkscope.[[scope]],
}
4) 创建变量对象 AO
checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope2: undefined
    },
    Scope: checkscope.[[scope]],
}
5) 将变量对象 AO 压入 checkscope 作用域链顶端
checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope2: undefined
    },
    Scope: [AO, checkscope.[[scope]] ]
}
6) 准备工作做完后,开始执行:并随着函数执行,修改 AO 的属性值
checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope2: 'local scope'
    },
    Scope: [AO, checkscope.[[scope]] ]
}
7) 函数执行完毕后,将其上下文从上下文栈中弹出!!!!
ECStack = [
    globalContext
];

All in all:

【函数被定义时】

  • 作用域就已经确定了!!会保存其父级的作用域链到函数内部的 [[scope]] 属性中!!

【函数被执行前的准备工作】

  • 会先创建其函数执行上下文,并把该函数执行上下文押入到执行上下文栈中
  • 在该函数上下文中创建 作用域链(直接复制函数中的 [[scope]] 属性)
  • 创建 变量对象 AO ,并将变量对象AO放入作用域链的最前端!!

【准备工作完成,开始执行函数】

  • 在执行函数的过程中,不断修改AO对象的属性值

【函数执行完毕】

  • 该函数执行上下文会从执行上下文栈中弹出!!!

原文链接:https://juejin.cn/post/7224765991895777337 作者:浩翔正在Rap

(0)
上一篇 2023年4月23日 上午11:02
下一篇 2023年4月23日 上午11:12

相关推荐

发表回复

登录后才能评论