最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Node V8 作用域相关

    正文概述 掘金(字节跳动ADFE团队)   2021-08-26   439

    一. 前言

    对于一门编程语言来说,变量的存储和访问,是最基本的功能之一。今天,将向大家深入介绍一下,JS 程序在运行的时候,如何找到变量,这需要一套良好的规范来规范,这套规则,就成为了作用域

    二. V8 JS 代码运行的步骤

    在介绍作用域的知识点之前,还需要先介绍一下,V8 JS 代码的运行流程。网上有较多的 V8 工作运行流程图,举例如下:

    Node V8 作用域相关

    其中后面的几步骤,像 Ignition 的字节码、TurboFan 优化编译器,主要是 V8 为了优化性能而做的,在本文不做过多展开。本文主要专注于前面几步骤,可以看到,V8 在拿到 JS 的 source code 后,会首先解析生成 AST 抽象语法树,然后后面再经过若干编译步骤,生成机器码并被运行。其中的作用域规则确定,是在解析成 AST 这一步骤的时候,就已经被确定了

    下面以 V8 源码内的 hello-world 程序为例,对流程进行讲解:

    Node V8 作用域相关

    在介绍上图的代码流程之前,先介绍一些概念:

    isolate:

    • 一个 Isolate 是一个独立的虚拟机。对应一个或多个线程。但同一时刻 只能被一个线程进入。
    • 所有的 Isolate 彼此之间是完全隔离的, 它们不能够有任何共享的资源。
    • 如果不显式创建 Isolate, 会自动创建一个默认的 Isolate

    handleScope:

    • 表示JS对象的生命周期的范围。
    • 在 V8 中,内存分配都是在 V8 的 Heap 中进行分配的,JavaScript 的值和对象也都存放在 V8 的 Heap 中。而 Handle 即是对 Heap 中对象的引用。V8 为了对内存分配进行管理,GC 需要对 V8 中的所有对象进行跟踪。HandleScope 就是用来管理 Handle 的
    • Handle 分为 Local 和 Persistent 两种。Local 是局部的,创建一个指向 JS 对象的本地引用,它同时被 HandleScope 进行管理。 persistent,创建一个指向 JS 变量的持久引用,类似与全局的,不受 HandleScope 的影响,其作用域可以延伸到不同的函数。

    context:

    • 可以理解为「执行上下文」或者「 执行环境」
    • 每当程序的执行流进入到一个可执行的代码时,就进入到了一个执行环境中

    三者关系的示意图如下:

    Node V8 作用域相关

    回到上述一开始的源码截图里,大概做了以下几步工作:

    1. 定义了一个 isolate
    2. 在 isolate 下,定义了一个handle_scope。handle_sope 的生命周期,决定了下面所有 v8::Local 的声明周期的有效性
    3. 定义了一个 context,并切换进入
    4. 编译 JS 源码成字节码
    5. 在当前 context 中,运行上一步编译出的字节码

    Node V8 作用域相关

    三. 作用域与执行上下文相关

    在上面第二节中,简单介绍了 V8 执行 JS 代码的流程。再简单概括一下,JavaScript属于解释型语言,JavaScript 的执行分为 「解释 」和 「执行 」两个阶段,如下:

    解释阶段:

    • 词法分析
    • 语法分析
    • 作用域规则确定

    执行阶段:

    • 创建执行上下文 context
    • 执行函数代码
    • 垃圾回收
    1. 两者区别

    很多同学容易混淆,「作用域 」和 「执行上下文」 这两个概念,的确他们两有一定的相关性,但又有区别:

    • 可以把作用域抽象理解成,是根据名称查找变量的一套规则,这套规则用来管理 js 引擎根据标识符名称如何查找变量。而一系列的嵌套作用域就形成了作用域链(你不知道的 JavaScript 中定义)

    • 而执行上下文,如上一节中描述,是在函数运行之前,V8 创建的函数运行环境

    • 作用域在 AST 解析阶段就确定,不会改变;而执行上下文,是在执行阶段才确定,可能发生改变。举个例子:

      var a = 10; function fn() { var b = 20; function bar() { console.log(this.b); // 200 console.log(a + b); // 30 } return bar; } var x = fn(), b = 200; x();

    上面的打印的结果为 200、30,是因为对于 this.b 来说,this 的指向,就是执行上下文中确定的;而 bar 函数中的 b 值,是在 AST 解析 bar 函数定义时,就已经明确 bar 函数的作用域链,为 bar -> fn -> 全局,所以 b 变量会沿着作用域链寻找,找到 fn 中的定义,值为 20

    • 一个作用域下,可能包含若干个上下文环境。因为在一个函数作用域里,每次在调用别的函数前,都要先创建调用函数所需的执行上下文。但是调用函数的次数是不定的,需要在运行时才能确定
    1. 函数执行流程介绍

    为了更深入的介绍作用域与执行上下文的原理,我们以一个函数的执行为例子,进行详细描述,并对其中一些概念再进行介绍:

    var scope = "global scope";
    function checkscope() {
        var scope = "local scope";
        function f() {
            return scope;
        }
        return f();
    }
    checkscope();
    
    1. 执行全局代码,创建全局执行上下文,全局上下文被压入执行上下文栈

      ECStack = [ globalContext ];

    1. 全局上下文初始化(初始化全局环境的变量对象 VO,确定全局环境的 Scope,绑定全局环境的 this)

      globalContext = { VO: { global: window, scope: undefined, checkscope: reference to function checkscope }, Scope: [globalContext.VO], this: globalContext.VO }

    1. checkScope 函数执行前阶段。初始化的同时,checkscope 函数被创建,保存全局环境的作用域链,到函数 checkscope 的内部属性 [[scope]] 中

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

      globalContext = { VO: { global: window, scope: "global scope", checkscope: reference to function checkscope }, Scope: [globalContext.VO], this: globalContext.VO }

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

      ECStack = [ checkscopeContext, globalContext ];

    3. 初始化 checkscope 函数执行上下文。会有以下几步:

      1. 用 arguments 创建活动对象 checkscopeContext.AO
      2. 利用 checkscopeContext.AO 与 checkscope.[[scope]],形成checkscope 函数执行环境的作用域链 checkscopeContext.Scope
      3. 绑定 this 到 undefined(非严格模式下会绑定到全局对象)

      checkscopeContext = { AO: { arguments: { length: 0 }, scope: undefined, f: reference to function f(){} }, Scope: [AO, globalContext.VO], this: undefined }

    1. f 函数执行前阶段。更新 f.[[scope]], checkscopeContext.AO.scope 等赋值

      f.[[scope]] = [ checkscopeContext.AO, globalContext.VO ];

      checkscopeContext = { AO: { arguments: { length: 0 }, scope: "local scope", f: reference to function f(){} }, Scope: [AO, globalContext.VO], this: undefined }

    2. 执行 f 函数,创建 f 函数执行上下文,f 函数执行上下文被压入执行上下文栈

      ECStack = [ fContext, checkscopeContext, globalContext ];

    3. f 函数执行环境初始化(参考第 e 步)

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

    4. f 函数中代码执行。需要对 scope 进行 RHS 查找。查找从作用域链中当前活动对象,开始沿着作用域链向上查找

    1. f 函数执行完毕,返回"local scope"。f 函数上下文从执行上下文栈中弹出

      ECStack = [ checkscopeContext, globalContext ];

    2. checkscope 函数在执行完 f 处,获取 f 执行的返回值 "local scope",函数继续向下执行

    3. checkScope 执行完毕,返回获取到的返回值 "local scope"。checkScope 函数上下文,从执行上下文栈中弹出

      ECStack = [ globalContext ];

    4. 代码执行流回到全局执行环境中调用 checoscope 处,拿到 checkScope 返回值并继续向下执行

    5. 直到程序终止,或者页面关闭。全局上下文出栈并销毁

    作者:陆瀚陶


    起源地 » Node V8 作用域相关

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元