4

图文看懂JavaScritpt引擎V8与JS执行过程 - 会飞的一棵树

 1 year ago
source link: https://www.cnblogs.com/flytree/p/16438995.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

浏览器原理

浏览器内核与js引擎

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

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

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

浏览器 内核 js引擎
Safari WebKit javaScriptCore
Chrome Blink V8
firefox Gecko SpiderMonkey...

浏览器渲染过程概述

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

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

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

V8引擎的执行

V8引擎解析过程概述

1249408-20220702220057925-973662019.svg


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进程有一个堆内存。这是存储静态数据的地方,包括方法/函数框架、原始值和指向对象的指针。

1249408-20220702231759250-1725796526.png

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

1249408-20220702231610810-247708999.png

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,其可以不为对象,只要其能存储环境记录,其包含的内容也有些差异。

结合代码示例进行分析

js
var name = "shinna_mashiro";
foo(666);
function foo(num){
    console.log(m);
    var m = 10;
    var n = 20;
    function bar(){
        console.log(name)
    }
    bar()
}

这是通过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被实例化时就会创建它,但是不能被访问,所以提升不了。(暂时性死区)

1249408-20220702233044378-1790950433.png


1249408-20220703000226994-1525867840.png


1249408-20220702233426002-14414353.png

案例二闭包

js
function makeAdder(count){
    return function(num){
        retrun count + num;
    }
}
var add10 = makeAdder(10);
console.log(add10(5));

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

1249408-20220702234259139-2129948383.png

部分参考及补充:
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

__EOF__


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK