读书笔记-javascript中一段代码执行过程

本文深入探讨JavaScript执行上下文的概念,包括执行上下文栈、作用域链、变量对象及活动对象等内容,帮助读者理解JavaScript代码执行流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

看了冴羽的JavaScript深入系列,这篇文都是参照冴羽的文章和高程写的,写给自己看,若有意了解一下,请移步JavaScript深入系列

这部分结合高程(P73~74、P178~179),展开了对执行上下文(变量对象、作用域链、this*)、执行上下文栈、作用域(静态作用域/词法作用域、动态作用域)等的学习

一些概念

作用域

代码中定义变量的区域

静态作用域、动态作用域
  • 静态作用域(又称词法作用域):
    函数的作用域在函数定义时决定,javascript就是采用的静态作用域
  • 动态作用域:
    函数的作用域是在函数调用的时候才决定的
var value = 1;
function foo() {         //函数定义
    console.log(value);
}
function bar() {
    var value = 2;
    foo();              //函数调用 
}
bar();                  //1
可执行代码(executable code)

可执行代码:全局代码、函数代码、eval代码

执行上下文(Execution Context,EC)

当执行到一段可执行代码的时候,就会进行准备工作(e.g.变量提升、函数声明提升),这里的“准备工作”,称为执行上下文(ECStack底部永远有个globalcontext全局上下文)

执行上下文栈(Execution context stack,ECStack)

JavaScript 引擎创建了执行上下文栈来管理执行上下文

当执行一个函数的时候,就会创建相应的执行上下文,将其压入执行上下文栈(进入准备工作),当函数执行完毕的时候,就会将函数的执行上下文从栈中弹出。

function fun3() {
    console.log('fun3');
}
function fun2() {
    fun3();
}
function fun1() {
    fun2();
}
fun1();
ECStack                 //globalContent全局上下文
ECStack.push(fun1.EC);  //将fun1.EC上下文压入ECStack中;ESCtack中:fun1.EC,globalContent;

ECStack.push(fun2.EC);  //将fun2.EC上下文压入ECStack中;ESCtack中:fun2.EC,fun1.EC,globalContent;

ECStack.push(fun3.EC);  //将fun3.EC上下文压入ECStack中;ESCtack中:fun3.EC,fun2.EC,fun1.EC,globalContent;

console.log('fun3');    //执行fun3中的代码
ECStack.pop();          //fun3执行完毕,将fun3.EC从ECStack中弹出
ECStack.pop();          //fun2执行完毕,将fun2.EC从ECStack中弹出
ECStack.pop();          //fun1执行完毕,将fun1.EC从ECStack中弹出

执行可执行代码会创建相应执行上下文,将其推入执行上下文栈(进入准备工作),执行,执行上下文栈将执行上下文弹出

执行上下文有三个属性:变量对象、作用域链、this

变量对象:与执行上下文有关的数据作用域
作用域链:所有执行上下文中的变量对象组成的链表
this:???(没有看懂..在后面的学习中会加深去理解)

作用域链的作用:保证对执行上下文中有权访问的变量和函数的有序访问

变量对象

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

全局上下文中的变量对象就是全局对象

变量对象(Variable Object)和活动变量AO(Activation Object)
  • AO是VO的一种状态,在执行过程中,当进入某一执行上下文(准备工作),变量对象VO被’激活’,此时通常用活动对象AO来表示变量对象VO
  • 只有当变量对象被激活(VO–>AO)时,AO上的属性才能被访问
  • 活动对象是在进入函数上下文时刻被创建的,它通过函数的 arguments 属性初始化。arguments 属性值是 Arguments 对象。e.g:函数上下文的变量对象初始化只包括 Arguments 对象
执行过程(函数代码为例)

一段代码:

function foo(a) {
  var b = 2;
  function c() {}
  var d = function() {};
  b = 3;
}
foo(1);
  • 进入执行上下文(准备工作)
    • 创建VO/AO,VO中包括函数的所有形参(值为实参或者undefined)、函数声明、变量声明(值为undefined)
VO={
    arguments: {
        0: 1,
        length: 1
    },                    //只有一个形参
    a: 1,                 //形参:实参/undefined
    b: undefined,         //变量声明(值默认为undefined)
    c: reference to function c(){},       //函数声明
    d: undefined          //变量声明
}
  • 代码执行
    • 顺序执行代码,根据定义修改VO的值
VO = {
    arguments: {
        0: 1,
        length: 1
    },                  
    a: 1,                 
    b: 3,                 //代码执行后给b赋值
    c: reference to function c(){},          
    d: reference to FunctionExpression "d"   
}

在进入执行上下文时,首先会处理函数声明,其次会处理变量声明,如果如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。

作用域链

当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。其本质上是一个指向变量对象的指针列表,只引用但不实际包含变量对象。

执行函数过程中作用域链的创建与变化

函数有一个内部属性 [[scope]],当函数创建的时候,就会保存所有父变量对象到其中,可以理解 [[scope]] 就是所有父变量对象的层级链

  • 函数创建
function foo() {
    function bar() {}
}

函数创建时,各自的[[scope]]为:

foo.[[scope]] = [
  globalContext.VO
];
bar.[[scope]] = [
    fooContext.AO,
    globalContext.VO
];
  • 函数激活
    当函数激活时,进入函数上下文,创建 VO/AO 后,就会将活动对象添加到作用链的前端。
    这时候执行上下文的作用域链,我们命名为 Scope:
Scope = [AO].concat([[Scope]]);

至此,作用域链创建完毕。

this

并没有看懂,打算后面再来细究

一段js代码在执行过程中经历了什么

前面的都是为了这一问题展开的,终于可以整合起来分析一段代码了
冴羽JavaScript深入之执行上下文中给出了两个思考题并分析前一段的执行过程,下面参照前面的知识写的第二题的执行过程:

第二题的代码:

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

过程:
1. 执行全局代码,创建全局执行上下文,全局上下文被压入执行上下文栈

ECStack = [
        globalContext
    ];

2 . 全局上下文初始化

globalContext = {
        VO: [global, scope, checkscope],
        Scope: [globalContext.VO],
        this: globalContext.VO
    }

3.初始化的同时,checkscope 函数被创建,保存作用域链到函数的内部属性[[scope]]

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

4..执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈

ECStack ={
    checkscopeContext,
    globalCintext
}

5.初始化checkscope函数,有四个步骤

  • 复制函数[[scope]]属性,创建作用域链
  • 用arguments创建活动对象
  • 初始化活动对象(加入形参,函数声明,变量声明)
  • 将活动对象压入函数作用域链顶端
    执行函数代码,给定义变量赋值,同时创建执行上下文中的函数(若执行的函数代码中存在有可执行代码)—-这道题中没有
checkscopeContext = {
        AO: {
            arguments: {
                length: 0
            },
            scope: undefined,
            f: reference to function f(){}
        },
        Scope: [AO, globalContext.VO],
        this: undefined
    }

6.checkscope 函数执行完毕,checkscope 执行上下文从执行上下文栈中弹出

 ECStack = [
        globalContext
    ];

7.执行可执行代码(f函数代码),创建f函数的执行上下文,并将其推入执行上下文栈中

ECStack = [
        fContext,
        globalContext   
];

8.f函数执行上下文初始化,与第5步相同

 fContext = {
        AO: {
            arguments: {
                length: 0
            }
        },
        Scope: [AO, **checkscopeContext.VO** , globalContext.VO],
        this: undefined
    }

9.f函数执行,返回scope
10.checkscope 函数执行完毕,checkscope 执行上下文从执行上下文栈中弹出

ECStack = [
       globalContext
   ];

注:过程并不是一步执行完后才进入下一步,步与步之间是有关联的。

执行过程总结

执行可执行代码 –> 创建相应的执行上下文EC –> 执行上下文被压入执行上下文栈(ECStack) –> 执行上下文初始化(有函数进行函数创建- [[ scope ]])–> 函数执行(又回到第一步)

其中,执行上下文的初始化经历了四步:

  1. 复制函数[[scope]]属性创建作用域链
  2. 用arguments对象创建活动对象
  3. 初始化活动对象(加入形参,函数声明,变量声明)
  4. 将活动对象推入函数作用域链顶端

补充区分

Scope和[[scope]]

函数的创建和执行过程关于作用域链,每个函数都有一个[[scope]]属性,每个执行上下文(执行环境)都有作用域链Scope

  • 函数创建:保存父变量对象VO到[[scope]]中,即父变量对象的层级链
  • 函数执行:-》进入上下文,创建AO,将AO添加到作用域链的前端
    Scope = [AO].concat([[scope]]);
动态关系

执行上下文栈ECS中包含执行上下文EC,
EC中包含变量对象VO、作用域链Scope和this,

  • 其中变量对象VO中又包含arguments和执行环境中的变量和函数
  • 作用域链Scope中又包含链中各层级变量对象(作用域链的本质是一个指向变量对象的指针列表)

各个部分会随着程序的执行动态改变

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值