• 图文看懂JavaScritpt引擎V8与JS执行过程


    本篇文章通过图文为你介绍了V8引擎大概的执行过程,你可以了解到代码是从扫描器Scaner变成tokens,从解析器Parser变成AST,从解释器变成字节码等等。以及JavaScript代码在执行的过程中,它在内存的情况是如何变化的,让你从更加底层的角度去理解你的js代码是如何运行的。了解这些后你就能从更加底层的角度去理解var的变量提升,闭包的形成等了。

    浏览器原理

    浏览器内核与js引擎

    浏览器内核又称“排版引擎”,“渲染引擎”,“浏览器引擎”,叫法很多,简单来说干的活就是将代码(HTML,XML,CSS,图片等)解析排版布局后输出到显示器让你看到。

    JavaScript引擎是一个专门处理JavaScript脚本的虚拟机,一般会附带在网页浏览器之中。

    主流浏览器内核与js引擎:

    浏览器内核js引擎
    SafariWebKitjavaScriptCore
    ChromeBlinkV8
    firefoxGeckoSpiderMonkey…

    浏览器渲染过程概述

    输入网址,服务器返回html,浏览器内核开始解析html,遇到link 等之类则会暂停,去下载对应的css或者js。

    1. 首先hmtl会被解析为dom树;
    2. 然后css会被解析为cssom规则树;
    3. 根据dom树和cssom规则树构建渲染树。
    4. 浏览器根据渲染数据进行布局(回流),此阶段浏览器计算各节点在页面中确切位置和大小,也称自动重排。
    5. 布局后进行绘制,将内容显示在屏幕上。

    渲染引擎不会等所有html解析完成后再去,构建render tree,而是解析完一部分就显示一部分。以提高用户体验。

    V8引擎的执行

    V8引擎解析过程概述

    在这里插入图片描述

    BLinK内核遇到js代码后,会以流的形式传递给v8,然其开始工作:

    • 首先接收到流后,会有扫描器Scanner对其进行词法分析将代码转化为tokens
    • 然后解析器parser将其转换为AST抽象语法树。
    • 再由解释器ignition(图中闪电部分)生成字节码再进行执行。

    Parser再探:

    Parser解析的时并不会进行全量解析(全部解析1.耗时间;2.解析后的字节码需放入内存耗内存),而是有延迟解析的策略,也就是一种按需解析给方案,( 理解:首先会Perpaser会解析出所需的最少限度的内容,比如内部有未调用的函数,则解析出函数声明,当调用时则paser对该函数进行完整的解析 )。

    Ignition再探:

    Ignition关注的是减少 V8 的内存开销,会进行执行前的优化工作。它会将AST进行分析将多次调用的函数标记为热点函数 交由TurboFan进行编译生成优化后的机器码(优化,方便快速调用)执行。而单次调用的函数则会被生成字节码再做执行。所以它也会有编译过程的,所以也有人对JS是否是解释型语言有争议。而正如最新的MDN上的文档说的JavaScript是一种具有函数优先的轻量级,解释型或即时编译型的编程语言,应该是最准确的吧。

    V8内存模型

    V8的内存主要分为堆和栈两部分,用以执行代码,和JVM有点类似😂。
    堆: 这是最大的内存块,也是垃圾回收(GC)发生的地方。
    栈: 每个V8进程有一个堆内存。这是存储静态数据的地方,包括方法/函数框架、原始值和指向对象的指针。
    image

    当然这只是简化版,实际的情况也会比这复杂得多(如下):
    image

    GC垃圾回收

    • 引用计数:对象有引用指向它,引用就+1,引用为0就进行回收。但其会产生循环引用。
    • 标记清除:早期V8中堆内存采用的一种清除算法,此会有一个根对象,如V8中全局对象。垃圾回收器会定时从根开始去找引用的对象,没有引用的对象就会回收。可以很好解决循环引用的问题。

    JavaScript在内存中的执行过程

    执行前准备:

    -> 首先,js引擎在执行代码之前会在在堆内存中创建一个全局对象GO(Global Object):

    1. 该对象在所有作用域可访问
    2. 会有 Date,Math,SetTimeOut,SetInterval,String,Array,Number
    3. 内置window属性指向它本身

    -> 然后,JavaScript引擎会在内部创建执行上下文栈ECS(Execution Context Stack),用于执行代码调用。

    开始执行:

    -> 首先会创建一个全局执行环境GEC(Global Execution Context),它包含:在paser转成AST的过程中,将全局定义的变量,函数加入到GO中,初始为undefined。(变量作用域提升:全局定义的变量,函数会先入GO再执行)。

    并将其入栈到ECS中。然后逐行执行,进行变量赋值,函数执行操作。

    -> 在执行到一个函数时会创建函数执行上下文FEC(Fuction Execution Context),并压入执行上下文栈ECS,它包含三部分:

    1. 在解析函数成为AST树结构时,会创建AO(Activation Object)包含:形参,arguments,函数定义(函数代码),函数指向对象,定义边量
    2. 作用域链:VO(在函数中就是AO对象) + 父级作用域
    3. this绑定的值。

    准备执行【创建GO 创建ECS 解析全局变量,函数(若变量初始为undefined,若函数则创建函数对象进行存储)】-> 执行代码【遇到函数调用 -> 创建其函数的AO对象 -> 创建其函数执行上下文 -> 执行函数内部代码】

    注:在最新的ECMA标准中,变量对象VO,该为了变量环境VE,其可以不为对象,只要其能存储环境记录,其包含的内容也有些差异。

    结合代码示例进行分析

    案例一

    var name = "shinna_mashiro";
    foo(666);
    function foo(num){
        console.log(m);
        var m = 10;
        var n = 20;
        function bar(){
            console.log(name)
        }
        bar()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这是通过var声明的变量,而通过let,const声明的变量ECMA262对它们是这么描述的:The variables(let 或 const)are created when their containing Lexical Environment is instantiate but may not be accessed in any way until the variable’s LexicalBinding is evaluated. 这些变量会被创建在包含他们的词法环境(VE -> VO)被实例化时,但是是不可以访问的,直到词法绑定被执行。也就是在FEC创建的时候,VE被实例化时就会创建它,但是不能被访问,所以提升不了。(暂时性死区)

    image
    image
    image

    案例二闭包

    function makeAdder(count){
        return function(num){
            retrun count + num;
        }
    }
    var add10 = makeAdder(10);
    console.log(add10(5));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    可以看到在代码执行完后,闭包结构中,会一直还有引用在GO中,所以此时不会对其内存进行回收。
    image

    部分参考及补充:
    1.Visualizing memory management in V8 Engine (JavaScript, NodeJS, Deno, WebAssembly):https://deepu.tech/memory-management-in-v8/
    2.全面分析总结JS内存模型:https://segmentfault.com/a/1190000021996331
    3.V8引擎详解:https://juejin.cn/post/6844904146798116871
    4.JavaScript到底是解释型语言还是编译型语言?:https://segmentfault.com/a/1190000013126460
    5.Blazingly fast parsing, part 2: lazy parsing: https://v8.dev/blog/preparser

  • 相关阅读:
    DSPE-PEG-NH2,CAS:474922-26-4 磷脂-聚乙二醇-氨基饱和18C磷脂
    安装极狐GitLab Runner并测试使用
    超长时间序列数据可视化的6个技巧
    数据治理-数据质量监控
    wait/notify——熟悉java线程间通信机制之等待/通知机制
    (附源码)基于SpringBoot和Vue的厨到家服务平台的设计与实现 毕业设计 063133
    使用conda管理虚拟环境
    【金融项目】尚融宝项目(十)
    使用命令行创建仓库
    脂质体包裹二硫化钼量子点liposome-MoS2|二硫化钼纳米片/硫化铜纳米粒子|介孔二氧化硅包裹二硫化钼纳米颗粒
  • 原文地址:https://blog.csdn.net/weixin_40425481/article/details/125581414