草庐IT

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

会飞的一棵树 2023-03-28 原文

本篇文章通过图文为你介绍了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引擎解析过程概述


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

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

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()
}

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



案例二闭包

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

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

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

有关图文看懂JavaScritpt引擎V8与JS执行过程的更多相关文章

  1. ruby-openid:执行发现时未设置@socket - 2

    我在使用omniauth/openid时遇到了一些麻烦。在尝试进行身份验证时,我在日志中发现了这一点:OpenID::FetchingError:Errorfetchinghttps://www.google.com/accounts/o8/.well-known/host-meta?hd=profiles.google.com%2Fmy_username:undefinedmethod`io'fornil:NilClass重要的是undefinedmethodio'fornil:NilClass来自openid/fetchers.rb,在下面的代码片段中:moduleNetclass

  2. ruby-on-rails - 无法在centos上安装therubyracer(V8和GCC出错) - 2

    我正在尝试在我的centos服务器上安装therubyracer,但遇到了麻烦。$geminstalltherubyracerBuildingnativeextensions.Thiscouldtakeawhile...ERROR:Errorinstallingtherubyracer:ERROR:Failedtobuildgemnativeextension./usr/local/rvm/rubies/ruby-1.9.3-p125/bin/rubyextconf.rbcheckingformain()in-lpthread...yescheckingforv8.h...no***e

  3. ruby - 在没有 sass 引擎的情况下使用 sass 颜色函数 - 2

    我想在一个没有Sass引擎的类中使用Sass颜色函数。我已经在项目中使用了sassgem,所以我认为搭载会像以下一样简单:classRectangleincludeSass::Script::FunctionsdefcolorSass::Script::Color.new([0x82,0x39,0x06])enddefrender#hamlengineexecutedwithcontextofself#sothatwithintemlateicouldcall#%stop{offset:'0%',stop:{color:lighten(color)}}endend更新:参见上面的#re

  4. ruby - Chef 执行非顺序配方 - 2

    我遵循了教程http://gettingstartedwithchef.com/,第1章。我的运行list是"run_list":["recipe[apt]","recipe[phpap]"]我的phpapRecipe默认Recipeinclude_recipe"apache2"include_recipe"build-essential"include_recipe"openssl"include_recipe"mysql::client"include_recipe"mysql::server"include_recipe"php"include_recipe"php::modul

  5. ruby - 为什么 Ruby 的 each 迭代器先执行? - 2

    我在用Ruby执行简单任务时遇到了一件奇怪的事情。我只想用每个方法迭代字母表,但迭代在执行中先进行:alfawit=("a".."z")puts"That'sanalphabet:\n\n#{alfawit.each{|litera|putslitera}}"这段代码的结果是:(缩写)abc⋮xyzThat'sanalphabet:a..z知道为什么它会这样工作或者我做错了什么吗?提前致谢。 最佳答案 因为您的each调用被插入到在固定字符串之前执行的字符串文字中。此外,each返回一个Enumerable,实际上您甚至打印它。试试

  6. ruby-on-rails - Rails 中的推荐引擎 - 2

    我想为我的Rails网络应用程序提供推荐功能。特别是,我想向新注册的用户推荐他可能想要关注的其他用户。Rails中是否有用于此目的的引擎/gem?如果没有,我应该从哪里开始构建它?谢谢。 最佳答案 有Coletivogemhttps://github.com/diogenes/coletivo我试了一下。在MySQL上运行。Neo4jhttp://neo4j.org真的很容易实现一个“跟随谁”。事实上,大多数展示其能力的样本都涉及“跟随谁”。快速提示-只有在JRuby上运行时,Neo4j.rb才会很酷。如果不是-使用Neograph

  7. ruby - 检查是否通过 require 执行或导入了 Ruby 程序 - 2

    如何检查Ruby文件是否是通过“require”或“load”导入的,而不是简单地从命令行执行的?例如:foo.rb的内容:puts"Hello"bar.rb的内容require'foo'输出:$./foo.rbHello$./bar.rbHello基本上,我想调用bar.rb以不执行puts调用。 最佳答案 将foo.rb改为:if__FILE__==$0puts"Hello"end检查__FILE__-当前ruby​​文件的名称-与$0-正在运行的脚本的名称。 关于ruby-检查是否

  8. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  9. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  10. UE4 源码阅读:从引擎启动到Receive Begin Play - 2

    一、引擎主循环UE版本:4.27一、引擎主循环的位置:Launch.cpp:GuardedMain函数二、、GuardedMain函数执行逻辑:1、EnginePreInit:加载大多数模块int32ErrorLevel=EnginePreInit(CmdLine);PreInit模块加载顺序:模块加载过程:(1)注册模块中定义的UObject,同时为每个类构造一个类默认对象(CDO,记录类的默认状态,作为模板用于子类实例创建)(2)调用模块的StartUpModule方法2、FEngineLoop::Init()1、检查Engine的配置文件找出使用了哪一个GameEngine类(UGame

随机推荐