请简述下js引擎的工作原理?js是如何构造抽象语法树?
工作原理
请简述下js引擎的工作原理,js是怎样处理事件的eventloop,宏任务源tasks和微任务源jobs分别有哪些?js是如何构造抽象语法树(AST)的?
js引擎只执行同步任务, 异步任务会有工作线程来执行,当需要进行异步操作(定时器、ajax请求、dom事件注册等), 主线程会发一个异步任务的请求, 相应的工作线程接受请求; 当工作线程完成工作之后, 通知主线程;主线程接收到通知之后, 会执行一定的操作(回调函数)。主线程和工作线程之间的通知机制叫做事件循环。
- 调用栈 (call stack): 主线程执行时生成的调用栈
- 任务队列 (task queue): 工作线程完成任务后会把消息推到一个任务队列, 消息就是注册时的回调函数
当调用栈为空时, 主线程会从任务队列里取一条消息并放入当前的调用栈当中执行, 主线程会一直重复这个动作直到消息队列为空。 这个过程就叫做事件循环 (event-loop)。
ES6新引入了Promise标准,同时浏览器实现上多了一个microtask微任务概念。在ECMAScript中,microtask称为jobs
,macrotask可称为task
。
- macrotask宏任务tasks,也就是上面说到的任务队列的任务。执行栈上的每个任务都属于宏任务,主线程执行完执行栈的任务,从任务队列取新的任务。宏任务执行时不会中断,会一次性执行完,为了及时渲染数据,主线程执行完一个宏任务之后,会执行一次渲染。
task-》渲染 -》宏任务 -》渲染 …..
- microtask微任务jobs,可以看成是插队需要及时处理的任务,会在当前主线程task任务执行后,渲染线程渲染之前,执行完当前积累所有的微任务。
task-》jobs -》渲染 -》宏任务 -》jobs -》渲染 …..
语法解析
将抽象语法树之前要先了解下NLP中文法的概率。任何一种语言,具体说就是DSL,都有自己的一套文法,用来表示这套语言的逻辑规范。不同的文法写出来的语法表达式也不一样。我们根据语法表达式来解析语言,就可以形成一个AST抽象语法树。然后可以作进一步处理。我常用的是PEG解析表达式语法。可以很轻松的写出语法的每一条产生式规则,来构造生成AST。所谓AST可以理解成按照一定语法结构组成的词汇流,每个词汇有特定的语法含义,比如说这是一个声明,这个一个操作符等等。
上面这个图是苹果最早做的KHTML渲染引擎中的KJS(javascript引擎),他是基于AST来实现的JavaScript语言解析的,先通过词法分析得到JSTokens流,然后经过语法分析得到抽象语法树,然后经过字节码生成器,转换成字节码。字节码经过JavaScript虚拟机JIT编译成机器码,然后执行。这是最初的设计架构,后来苹果公司基于此重构出了webkit渲染引擎,google基于webkit单独维护,称为blink渲染引擎,chrome的JS引擎改造为V8引擎。参考:[简述Chromium, CEF, Webkit, JavaScriptCore, V8, Blink][9]
举个例子常用的babel插件的原理就是基于babylon词法语法分析器生成抽象语法树,将代码文本转换成按照特定语法组合的token流集合,然后经过babtlon-traverse这个组件来负责处理遍历语法树,访问每个token节点,通过对token的处理,可以生成我们需要的AST语法树,然后再通过babylon-generator这个组件来做代码生成,根据AST生成代码。比如可以将 箭头函数 转换成 function函数。
浏览器中,通过开发者调试工具分析就能看到,下载完js脚本后,首先浏览器要先解析代码=》初始化上下文环境=》执行代码,整个是evaluate script的过程,解析代码的过程也是编译js的过程所以看最前面第一步就是compile script,将js代码编译成字节码(这一块涉及到浏览器js引擎的优化,v8引擎是编译成字节码,后面经过JIT解析执行(这个参考 [你不知道的LLVM编译器][10] 可以提升效率做动态优化), 这个类似于java、C#这些需要将源代码编译成中间语言,然后在虚拟机执行,javascript编译成字节码后面也是在虚拟机执行),然后就开始执行脚本。