我相信这是你须要知道的JS运行机制和JS引擎(V8)内部

前言

前几天,我开始想写前端生态周边之浏览器幕后的文章。其实也是是对于零碎知识进行整合。但愿能给你们带来从0-1,而非模块 的认识。另一个初衷也是做为前端的视角到底应该了解浏览器的什么内容。(不少人想深刻要看下WebKit源码,V8源码我的感受稍微有点离谱 并不适合每一个前端同窗去实践 会让你感受到绝望)前端

但愿你们在认真阅读的过程当中可以纠正个人问题。快来打我脸!

今天来到了你们平常接触最多的模块 JS引擎相关的内容;本篇文章的内容包含:node

  1. JavaScript运行机制
  2. 简单了解一下引擎内部

术语概念 (熟悉的跳过,眼熟的回忆,不懂的认真看完后续有用到)

  1. 进程 cpu资源分配的最小单位 (进程可包含多个进程 这个能够暂时不须要理解)
  2. 线程 线程是cpu调度的最小单位
  3. ECS:执行环境栈,Execution Context Stack

JavaScript运行机制

概述

  • JS是单线程的脚本语言
  • JS为何设计为单线程语言 主要的缘由是由于与它的用途有关系,做为浏览器脚本语言,JS的主要用途是操做DOM。若是设计多线程,势必会带来操做冲突引起复杂的同步问题;
  • 为了解决单线程排队问题;出现了让你头疼的运行机制 包含主线程,任务队列,event-loop。
为了更好地理解Event Loop,请看下图

上图中,主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各类外部API,它们在"任务队列"中加入各类事件(onClick,onLoad)。只要栈中的代码执行完毕,主线程就会去读取"任务队列",依次执行那些事件所对应的回调函数。
执行栈中的代码,老是在读取"任务队列"以前执行。git

主线程 (执行栈)

执行栈(execution context stack)是JavaScript执行事件任务的线程。

请看下面这个gif图:github

  1. 当在全局代码中调用一个函数,程序将进入被调用的函数内部,并建立一个新的执行上下文,并将新建立的上下文压入执行栈的顶部。
  2. 调用函数内部调用了其余函数 会重复第一个步骤(程序进入调用函数内部,建立一个新的执行上下文并把它压入执行栈的顶部)
  3. 依次执行从栈顶开始: 执行位于栈顶的执行上下文,一旦当前上下文函数执行结束,它将被从栈顶弹出,并将上下文控制权交给当前的栈;继续执行直到栈空。

任务队列

任务队列(task queue)是进行存放异步任务运行结果事件 一种是同步任务(synchronous),另外一种是异步任务(asynchronous):
  1. 同步任务指的是在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。
  2. 异步任务指的是直接进入任务队列,而后等待知足条件(发出通知)而后最终进入主线程执行的任务。
请看下面这个gif图:


(学过C++语言都知道main做为主入口。不了解的不要紧此处main函数可作忽略)~~正则表达式

  1. 进入全局执行上下文
  2. console.log("start");进入主线程 (压入栈的顶部)
  3. Timer1 执行中进入主线程发现是WEBapi 待timeout时间完成进入队列
  4. Timer2 执行中进入主线程发现是WEBapi 待timeout时间完成进入队列
  5. console.log("end);进入主线程执行算法

    执行顺序为 2-5-4-3

Event Loop

event-loop能够理解为一个处理机制。
主线程任务执行==主线程从"任务队列"中读取事件==执行...,这是个循环的过程。这种运行机制称为Event Loop(事件循环)

对照上面的动画 简单理解为主线程为空时就从任务队列读取待执行的事件(timer1,timer2)进入主线程进行执行。express

JS引擎(V8)概述

JavaScript 引擎 是一个程序或者执行 JavaScript 代码的解释器。一个JavaScript 引擎能够做为一个单独的解释器实现或者经过某种方式将 JavaScript 编译为字节码的即时编译器。json

常说的JavaScript 引擎:后端

  1. JavaScriptCore 表明浏览器Safari
  2. Rhino 表明浏览器Mozilla Firefox
  3. Chakra 表明浏览器Internet Explorer(IE)
  4. V8 表明浏览器 Chrome 开源,用 C++ 实现的
下面介绍一下V8的一些内部实现,优点。

JS执行内部过程

  1. JS代码转换为AST语法树表示。
// 函数
 function greet() {
   console.log("wlove");
 }
 // AST树 json
 {"type":"Program","start":0,"end":47,"body":[{"type":"FunctionDeclaration","start":0,"end":46,"id":{"type":"Identifier","start":9,"end":14,"name":"greet"},"expression":false,"generator":false,"async":false,"params":[],"body":{"type":"BlockStatement","start":17,"end":46,"body":[{"type":"ExpressionStatement","start":23,"end":44,"expression":{"type":"CallExpression","start":23,"end":43,"callee":{"type":"MemberExpression","start":23,"end":34,"object":{"type":"Identifier","start":23,"end":30,"name":"console"},"property":{"type":"Identifier","start":31,"end":34,"name":"log"},"computed":false,"optional":false},"arguments":[{"type":"Literal","start":35,"end":42,"value":"wlove","raw":"\"wlove\""}],"optional":false}}]}}],"sourceType":"module"}

  1. AST转换为字节码
    有了解的同窗应该知道以前的V8直接是转换机器码。可是机器码占空间很大,若是v8 缓存机制将 全部 js 代码编译成机器码缓存下来,这样会致使缓存占用的内存、磁盘空间很大。并且退出 Chrome 再打开时序列化、反序列化缓存时间成本也很高。
    在时间,空间成本都很高的状况下 引入了字节码。
  2. 字节码解释器TurboFan 内部也存在不少工做内容简单列取几点:api

    1. 字节码处理程序生成
    2. 字节码生成
    3. 解释器寄存器分配
    4. Context链
    5. 异常处理
    6. JS代码解释执行
    7. ....
  3. JIT (Just In Time) 混合使用编译器和解释器的技术。编译器启动速度慢,执行速度快。解释器的启动速度快,执行速度慢。而JIT技术就是取俩者之长 (Ignition(字节码解释器) + TurboFan (JIT编译器) 的组合;后面的应用也是愈来愈广 )
  4. 虚拟机(垃圾回收,内存管理等)

    V8 使用了分代和大数据的内存分配,在回收内存时使用精简整理的算法标记未引用的对象,而后消除没有标记的对象,最后整理和压缩那些还未保存的对象,便可完成垃圾回收。
    内存分配:

    • 年轻分代:为新建立的对象分配内存空间,常常须要进行垃圾回收。为方便年轻分代中的内容回收,可再将年轻分代分为两半,一半用来分配,另外一半在回收时负责将以前还须要保留的对象复制过来。
  • 年老分代:根据须要将年老的对象、指针、代码等数据保存起来,较少地进行垃圾回收。
  • 大对象:为那些须要使用较多内存对象分配内存,固然一样可能包含数据和代码等分配的内存,一个页面只分配一个对象。

    内存(垃圾)回收:

    1. 年轻分代中的对象垃圾回收主要经过Scavenge算法进行垃圾回收。
    2. 因考虑在年老分代中存活对象居多,因此主要采用了Mark-Sweep(标记清除)标记清除和Mark-Compact(标记整理)相结合的方式进行垃圾回收。
  1. 代码执行

工做过程编译和运行

V8引擎编译阶段:

主要类以下所示:

  1. Script:Script类 包含JS代码,和编译后的本地代码。(此处为编译入口)
  2. Compiler:编译器类:Script类调用编译生成代码 包括生成AST,本地代码等。
  3. AstNode:抽象语法树node类(做为节点基类,包含不少子类为后续生成代码作辅助)
  4. AstVisitor:抽象语法树访问类,主要用来遍历异构的抽象语法树;
  5. FullCodeGenerator:访问类可调用来遍历AST来为JS生成本地可执行代码。

编译过程大概为:

  1. Script类调用Compiler类为其生成AST和本地代码。
  2. Compile函数先使用Parser类生成AST,再使用FullCodeGenerator类来生成本地代码。
  3. FullCodeGenerator使用多个后端来生成与平台相匹配的本地汇编代码。

V8引擎运行阶段:

主要类以下所示:

  1. Script此处为运行入口 编译以后生成的本地代码;
  2. Execution:JS运行过程当中的辅组类;包含一些函数 如call函数;
  3. JSFunction:须要执行的JavaScript函数表示类;
  4. Runtime:运行这些本地代码的辅组类,主要提供运行时所需的辅组函数,如:属性访问、类型转换、编译、算术、位操做、比较、正则表达式等;
  5. Heap:运行本地代码须要使用的内存堆类;
  6. MarkCompactCollector:垃圾回收机制的主要实现类,用来标记、清除和整理等基本的垃圾回收过程;
  7. SweeperThread:负责垃圾回收的线程。


执行过程大概为:

  1. V8 当函数被调用会检测是否有本地代码若是有进行调用。
  2. V8 当函数被调用会检测是否又本地代码若是没有将会生成本地代码。
  3. 执行编译后代码构建JS对象须要Runtime类来辅组建立对象,并须要从Heap类分配内存。
  4. 最后将不用的空间进行标记清除和垃圾回收。
固然V8内部还有不少例如隐藏类,扩展机制等等。有兴趣能够看一下 《WebKit技术内幕》 若是想要读源码推荐从 代码量还少的V8 开始阅读。加油少年。

最后

分享一下最近: 工做很忙很忙(对接外部资源,整合内部,作技术探索研发...);生活很忙很忙(有一点就是运动必定要坚持下来..) 而后呢写做也会坚持下来(每周1-3篇)。你们有任何想了解的内容随时留言 只要我知道的就会马上分享出来帮助你们。若是不知道我能够去学。嘿嘿

下一篇待定吧 加油 前程似锦 靓仔靓女!!!

上文图片部分来源网络 侵权请联系删除 谢谢。
相关文章
相关标签/搜索