Chrome浏览器引擎 Blink & V8
{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"背景"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这篇文章是我的前端技术系列文章中浏览器工作原理栏目中的第二篇。浏览器引擎(也被称作布局引擎或渲染引擎)是浏览器的重要组成部分。浏览器引擎最重要的工作就是将HTML文本和其他页面中的资源转换成可以与用户产生交互的页面。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"除了浏览器引擎外,布局引擎和渲染引擎是另外两个相关的概念,理论上,两个引擎可以独立实现,但在实际情况中,往往很少将二者分开实现。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"除了包含布局和渲染引擎外,浏览器引擎还遵循"},{"type":"link","attrs":{"href":"https://en.wikipedia.org/wiki/Content_Security_Policy","title":null},"content":[{"type":"text","text":"文档安全策略(Content Security Policy)"}]},{"type":"text","text":"以保证站点间相互独立。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在运行JavaScript代码的功能上,基本上主流的浏览器都使用独立的引擎,起初JavaScript语言只被用于在浏览器中使用,但现在JavaScript几乎可以在任何地方使用,这需要JavaScript引擎可以独立于浏览器单独使用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而像"},{"type":"link","attrs":{"href":"https://en.wikipedia.org/wiki/Electron_(software_framework)","title":null},"content":[{"type":"text","text":"Electron framework"}]},{"type":"text","text":"这样的技术就是整合Chromium的渲染引擎和Nodejs而实现的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我想通过这篇文章把V8中的技术尽可能的详述,涵盖的内容会比较多,可反复阅读 :)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"常见的浏览器引擎"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"浏览器引擎是Web平台技术中一系列标准(HTML、CSS、ECMAScript、WebGL、Web Storage等等)的具体实现,不同的浏览器引擎在遵循同样的标准下,还实现了额外的功能。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Gecko是Mozilla的浏览器引擎,在Firefox中使用,"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"SpiderMonkey是Firefox的JavaScript引擎。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Apple为Safari浏览器创造了Webkit引擎,Webkit引擎内置了"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"JavaScriptCore引擎。虽然Apple允许在IOS设备上可以使用其他的浏览器代替Safari,但所用通过App Store分发的浏览器必须使用Webkit引擎。例如,Opera Mini浏览器在IOS设备上使用Webkit引擎,而在其他设备上使用Blink引擎。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Google起初使用Webkit作为Chrome浏览器的引擎,后来以Webkit引擎为基础创造了Blink引擎,所有基于Chromium开源浏览器衍生的产品都使用blink引擎。而大名鼎鼎的V8引擎就是Chromium-based浏览器的JavaScript引擎。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Microsoft维护着自己的EdgeHTML引擎,作为老的Trident引擎的替代方案。新的Edge的浏览器已经开始使用Chromium的Blink引擎了,而EdgeHTML引擎只在window 10上的"},{"type":"link","attrs":{"href":"https://en.wikipedia.org/wiki/Universal_Windows_Platform","title":null},"content":[{"type":"text","text":"Universal Windows Platform"}]},{"type":"text","text":"中被使用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c2/c2d5a491c0b230edf39337130cd12f67.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"天下合久必分,分久必合,随着Edge也加入了Blink的阵营,基本上Webkit内核及Webkit内核的衍生Blink已经统治了浏览器市场。到目前,单单Chrome的市场占有率已有六成。接下来,就让我们来聊聊Blink和V8引擎。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"Chromium & Blink"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"宽泛的说,Blink实现了在浏览器页签中所有的渲染工作,其中包括:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"实现了Web平台中的标准,例如HTML标准,包括DOM、CSS等。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"内置了V8引擎用于运行JavaScript。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"从网络堆栈中获取资源"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"构建DOM树"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"计算样式和布局"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"内置了"},{"type":"link","attrs":{"href":"https://chromium.googlesource.com/chromium/src/+/HEAD/cc/README.md","title":null},"content":[{"type":"text","text":"Chrome Compositor"}],"marks":[{"type":"underline"}]},{"type":"text","text":"和绘制图形的能力"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"借助"},{"type":"link","attrs":{"href":"https://chromium.googlesource.com/chromium/src/+/HEAD/content/public/README.md","title":null},"content":[{"type":"text","text":"Content public APIs"}],"marks":[{"type":"underline"}]},{"type":"text","text":",Blink可以被内置在很多诸如Chromium,Android WebView和Opera这样的应用中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"进程/线程架构"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"进程"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Chromium拥有一套多进程架构。Chromium有一个浏览器进程和多个带有沙盒能力的渲染进程。Blink则运行在渲染进程中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"从安全的角度考虑,让不同的站点保持相互隔离是非常重要的,这被称作"},{"type":"text","marks":[{"type":"strong"}],"text":"站点隔离(Site Isolation)"},{"type":"text","text":"。理论上讲,一个渲染进程应该最多只能负责一个站点的渲染工作。但实际上,当用户打开很多页签时,渲染进程与站点1对1的关系会占用大量的内存。所以一个渲染进程可能会被多个iframe或页签所共享,也就是说一个页面中的多个iframe可能被多个渲染进程渲染,而在不同页面中的多个iframe也可能被同一个渲染进程渲染。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"所以,在iframe,页签和渲染进程间并不存在一对一的关系。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由于Blink运行在渲染进程中的沙盒中,当Blink需要访问文件或播放视频或者访问用户信息(cookie、password等)时必须与浏览器进程通信。这种不同进程间的通信方式被"},{"type":"link","attrs":{"href":"https://chromium.googlesource.com/chromium/src/+/master/mojo/README.md","title":null},"content":[{"type":"text","marks":[{"type":"underline"}],"text":"Mojo"}]},{"type":"text","text":"实现。随着Chromium不断向服务化架构演进,Blink可以通过Mojo来降低消息传递过程中对发送方和接收方对于具体实现的依赖(服务可能在多个进程中,也可能在同一个进程中,消息传递方式不同)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/24/240541c94b8227856e0c4062a8f9db7e.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Mojo"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Mojo是一系列库的集合,用于提供一种进程内或跨进程的通信方案,其中包含了与平台无关的通用的IPC方案、消息IDL格式化和可以与不同语言集成的绑定库。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d9/d990029007862acd8e71392f766b48ee.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Message pipe"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一个"},{"type":"text","marks":[{"type":"strong"}],"text":"消息通道(Message pipe)"},{"type":"text","text":"建立其两个"},{"type":"text","marks":[{"type":"strong"}],"text":"端点(endpoint)"},{"type":"text","text":"之间的通道。每一个端点都有一个用于收消息的队列,同时还可以向另一个端点发送消息,而消息通道是双向的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Mojom"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Mojom文件描述了消息的类型。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有了消息通道和消息类型,通道中的一个端点可以被指定成"},{"type":"codeinline","content":[{"type":"text","text":"Remote"}],"marks":[{"type":"strong"}]},{"type":"text","text":",它可以发送Mojom文件中定义好类型的消息。另一个端点则可以被指定成"},{"type":"codeinline","content":[{"type":"text","text":"Receiver"}],"marks":[{"type":"strong"}]},{"type":"text","text":",用于接收消息。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"线程"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Blink包含一个主线程,多个Worker线程,还有一些其他的线程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"几乎所有重要的工作都运行在主线程上。包括运行JavaScript(除了Workers),DOM生成,CSS样式和布局计算等,所以交互性能的优化关键主要围绕主线程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Blink会为Web workers,Service workers创建出独立的线程。虽然运行的都是JavaScript,但主线程与worker线程的运行环境是不共享的,需要通过消息来传递数据。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Blink和V8也可能会创建出其他的用于音视频,数据库和垃圾回收(GC)等功能的线程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对于线程间通信,会使用PostTask提供的api。除了真的因为性能的原因,使用共享内存的方式实现通信并不被推荐,这也是Blink不使用线程锁(MutexLocks)的原因。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/89/89ab6c79bbfa58c540f5ebc72be974b5.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"Page, Frame, Document, DOMWindow"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"概念"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一个"},{"type":"text","marks":[{"type":"strong"}],"text":"页面(Page)"},{"type":"text","text":"代表一个浏览器页签,一个渲染进程可能负责渲染多个页面。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一个"},{"type":"text","marks":[{"type":"strong"}],"text":"框(Frame)"},{"type":"text","text":"代表主框或者一个iframe,一个"},{"type":"text","marks":[{"type":"strong"}],"text":"页面至少包含一个框。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一个"},{"type":"text","marks":[{"type":"strong"}],"text":"DOMWindow"},{"type":"text","text":"代表JavaScript中的window对象,每个框只有一个"},{"type":"text","marks":[{"type":"strong"}],"text":"DOMWindow"},{"type":"text","text":"。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一个"},{"type":"text","marks":[{"type":"strong"}],"text":"Document"},{"type":"text","text":"代表JavaScript中的window.document对象,每个框只有一个"},{"type":"text","marks":[{"type":"strong"}],"text":"Document。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一个"},{"type":"text","marks":[{"type":"strong"}],"text":"ExecutionContext"},{"type":"text","text":"在主线程中抽象一个Document,在worker线程中抽象WorkerGlobalScope。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"渲染进程 :页面 = 1 :N"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"页面 :框 = 1 :M"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Frame : DOMWindow : Document (或ExecutionContext) 在任何情况下都是 1 : 1 : 1 ,但有时引用关系会变化。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"跨进程iframes(OOPIF)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"虽然站点隔离机制让页面变的更安全,但却增加了复杂度。站点隔离致力于为每一个站点创建一个渲染进程,例"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"如,"},{"type":"link","attrs":{"href":"https://mail.example.com","title":null},"content":[{"type":"text","marks":[{"type":"underline"}],"text":"https://mail.example.com"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":" 和 "},{"type":"link","attrs":{"href":"https://chat.example.com","title":null},"content":[{"type":"text","marks":[{"type":"underline"}],"text":"https://chat.example.com"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":" 属于同一个站点,而 "},{"type":"link","attrs":{"href":"https://noodles.com","title":null},"content":[{"type":"text","marks":[{"type":"underline"}],"text":"https://noodles.com"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":" 和 "},{"type":"link","attrs":{"href":"https://pumpkins.com","title":null},"content":[{"type":"text","text":"https://pumpkins.com"}],"marks":[{"type":"underline"}]},{"type":"text","text":"则不属于同一个站点。如果一个页面中存在跨站点的iframe则可能被多个渲染进程承载。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"从主框的角度看,主框是"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"LocalFrame,iframe则是RemoteFrame。从iframe的角度看,主框则是RemoteFrame,而iframe则是LocalFrame。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"LocalFrame和RemoteFrame间的通信被浏览器进程管理。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Web IDL绑定"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Web IDL (Web Interface definition language)是用于描述Web平台中定义的标准如何被浏览器实现的接口定义语言,通过浏览器对这些标准中定义的接口的实现,Web开发者可以使用JavaScript对象来调用这些标准功能。Blink在实现这些标准的同时,还需要为V8中的JavaScript提供调用Blink的途径,这就是Web IDL Bindings。通过对Web IDL的实现和Bindings的存在,就实现了类似在JavaScript中访问某个节点的第一个子节点"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"的功能(node."},{"type":"text","text":"firstChild)"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"。在实现了通用标准的同时,浏览器还实现了自己特有的功能定义,通用的标准被定义在"},{"type":"link","attrs":{"href":"https://heycam.github.io/webidl/","title":null},"content":[{"type":"text","text":"the Web IDL spec"}],"marks":[{"type":"underline"}]},{"type":"text","text":",而Blink自己的定义则被定义在"},{"type":"link","attrs":{"href":"https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/bindings/IDLExtendedAttributes.md","title":null},"content":[{"type":"text","text":"Blink-specific IDL extended attributes"}],"marks":[{"type":"underline"}]},{"type":"text","text":"中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通常在idl文件被构建时,"},{"type":"link","attrs":{"href":"https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/bindings/IDLCompiler.md","title":null},"content":[{"type":"text","marks":[{"type":"underline"}],"text":"the IDL compiler"}]},{"type":"text","text":" 会自动为具体的实现类生成Blink-V8的绑定。当在JavaScript中调用node.firstChild时,V8会调用V8Node::firstChildAttributeGetterCallback() ,然后进一步调用Node::firstChild() 。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"渲染流水线"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Rendering pipeline定义了从HTML字符到在屏幕上显示像素的过程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/59/59f93c4bc67cf902dcaad2524f5d709f.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"关于这部分的内容可以阅读前一篇文章。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"V8"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"V8是Google打造的开源的,高性能的JavaScript和WebAssembly引擎,使用C++语言实现。V8引擎被应用在Chrome、Nodejs和其他应用中。V8引擎可以独立运行,也可以运行在任何的C++程序中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一个V8的实例被称作Isolate,每一个isolate都有独立GC的堆栈空间。这就意味着一个Isolate中的JavaScript对象不能直接访问另一个Isolate中的对象。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在Chrome中,每个渲染进程都有一个V8 Isolate,所有被同一个渲染进程处理的站点的JavaScript代码在同一个Isolate中运行。但对于Web worker,每一个worker则拥有自己的Isolate。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在Isolate中,存在一个或多个JavaScript上下文环境(JavaScript content)。Chrome为每个iframe创建一个JavaScript环境。此外,每个Chrome extension对于一个iframe都有自己的JavaScript环境。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Blink通常使用ScriptState对象作为JavaScript环境的引用,blink::ScriptState与v8::Context有着1 : 1的关系。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"执行流水线"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JavaScript脚本的运行需要经历一系列的过程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a5/a5595e9e68069bbe5d22c63c9ab8b324.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要运行的JavaScript脚本会从网络或缓存中被"},{"type":"text","marks":[{"type":"strong"}],"text":"加载。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通过对JavaScript脚本文本的分析可以生成用于描述源代码结构化的数据,"},{"type":"text","marks":[{"type":"strong"}],"text":"抽象语法树(AST)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下来"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}},{"type":"strong"}],"text":"Ignition解释器"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"会将AST转化成生成体积更小的"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}},{"type":"strong"}],"text":"字节码"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":",字节码中的每行指令代表着对寄存器的操作,当字节码生后以后AST将会被废弃以节省空间,后续的执行和优化都基于字节码。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"在解释器执行字节码时,"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}},{"type":"strong"}],"text":"Object Shapes"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"会试图将代码中对象的类型缓存下来生成"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}},{"type":"strong"}],"text":"Type Feedback"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":",当访问这些对象时会尝试从缓存中获取,如果找不到再动态查找并更新缓存。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}},{"type":"strong"}],"text":"TurboFan"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"是V8中的代码优化编译器,它会评估函数是否需要被进一步优化成机器码以提高性能,需要被优化的函数被编译成"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}},{"type":"strong"}],"text":"Optimized Code"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"但当编译后的函数被发现函数中变量的数据类型与之前缓存的类型不同时,则需要放弃优化的代码回到字节码重新解释执行。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下来让我们逐一了解每个过程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"加载(Loading)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"加载是V8获取JavaScript脚本文本的过程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/33/33980d9f58ed08dcbca52989379c4b24.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"V8并不负责资源的下载,所以这些资源可能来自网络、缓存,也可能来自Service worker。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"V8可以在文件下载的同时进行接下来的分析工作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"V8拥有脚本热加载的能力:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Warm Load"},{"type":"text","text":":当V8再次运行同样的脚本时,会将脚本编译后的结果缓存在硬盘中。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Hot Load:"},{"type":"text","text":"当第三次访问时,V8可以跳过分析和编译过程直接从硬盘中读取之前被编译的结果。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"分析(Parsing)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/4f/4f9f1c010a79037cd568d8145b75bfd9.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"分析是将JavaScript脚本文本转化成"},{"type":"text","marks":[{"type":"strong"}],"text":"抽象语法树(Abstract Syntax Tree)的过程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"词法分析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"词法分析(lexical analysis)"},{"type":"text","text":"是将一系列字符转换成"},{"type":"text","marks":[{"type":"strong"}],"text":"标记(token)"},{"type":"text","text":"的过程。这里的"},{"type":"text","marks":[{"type":"strong"}],"text":"标记"},{"type":"text","text":"是表示源代码的最小单位,将输入的字符流转换成标记的过程被称为"},{"type":"text","marks":[{"type":"strong"}],"text":"标记化(tokenization)"},{"type":"text","text":",在这个过程中,词法分析器还会对这些标记进行分类。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"常见的标记分类有:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"标识符(identifier):x,color,UP"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"关键字(keyword):if,var,return"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"分隔符(separator):(,}, ;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"操作符(operator):*,=,>"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"字面量(literal):\"Hello world\", true, 666"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"注释(comment):// 单行, /* 多行 */"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"Scanner"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"扫描器处在词法分析的第一个阶段,通常基于状态机实现,可以在能识别的标记间不断切换。每种标记可以代表一个字符或由多个字符组成的序列。很多时候,根据第一个非空字符就可以推断出标记的类型,然后逐个处理后面的字符,直到出现不属于该类型标记字符集中的字符时结束(最长一致性原则)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"扫描器只处理utf-16的字符集,所在在扫描器拿到字符之前会有字符集转换的过程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"Evaluator"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"评估器处在词法分析的第二个阶段,用于将某些带有语义的词定义成"},{"type":"text","marks":[{"type":"strong"}],"text":"值(value)"},{"type":"text","text":",一个"},{"type":"text","marks":[{"type":"strong"}],"text":"语义("},{"type":"text","marks":[{"type":"color","attrs":{"color":"#202122","name":"user"}},{"type":"strong"}],"text":"lexeme"},{"type":"text","marks":[{"type":"strong"}],"text":")"},{"type":"text","text":"是标记类型和值的组合。有的标记有值,比如标识符类型的标记,而有的标记没有值,比如分隔符类型的标记。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"例如,在源代码中是这样的:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"net_worth_future = (assets – liabilities);"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"生成的语义标记可能是这样的:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"IDENTIFIER net_worth_future\nEQUALS\nOPEN_PARENTHESIS\nIDENTIFIER assets\nMINUS\nIDENTIFIER liabilities\nCLOSE_PARENTHESIS\nSEMICOLON"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"语法分析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"语法分析("},{"type":"text","marks":[{"type":"color","attrs":{"color":"#202122","name":"user"}},{"type":"strong"}],"text":"syntactic analysis,也叫 parsing"},{"type":"text","marks":[{"type":"strong"}],"text":")"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#202122","name":"user"}}],"text":"是根据某种给定的"},{"type":"text","text":"形式文法"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#202122","name":"user"}}],"text":"对由单词序列(如英语单词序列)构成的输入文本进行分析并确定其语法结构的一种过程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"语法分析器"},{"type":"text","text":"(parser)的作用是进行语法检查、并根据输入的单词序列生成带有层次的数据结构(通常是语法分析树、抽象语法树等)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"抽象语法树与作用域"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"抽象语法树(Abstract Syntax Tree)"},{"type":"text","text":"是源代码结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这里我们以一段代码作为例子"}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"function sayHi () {\n var str = \"hello world\";\n return str;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"转化成AST后的结构是这样的"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/7e/7e62d86a0d4c4277bc91ad76a679f88d.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}},{"type":"strong"}],"text":"FunctionLiteral"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"代表sayHi函数,Block代表函数体。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"左边的"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}},{"type":"strong"}],"text":"VariableDeclaration"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"代表变量str的声明,"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#323232","name":"user"}},{"type":"strong"}],"text":"Assignment"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#323232","name":"user"}}],"text":"代表赋值,"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#323232","name":"user"}},{"type":"strong"}],"text":"ReturnStatement"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#323232","name":"user"}}],"text":"代表return语句。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这里的"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#323232","name":"user"}},{"type":"strong"}],"text":"VariableProxy"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#323232","name":"user"}}],"text":"代表对变量的引用,在目前这个阶段还不知道是引用哪个变量。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在分析这个阶段除了要生成AST,还要分析作用域。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/1a/1ac03cdfbd40f995afbd50da9ee3b5a8.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通过分析,全局作用域中包含函数sayHi的声明,而sayHi的函数作用域中包含变量str的声明。而两个"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#323232","name":"user"}},{"type":"strong"}],"text":"VariableProxy"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#323232","name":"user"}}],"text":"所引用的变量也可以确定下来。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#202122","name":"user"}}],"text":"V8有两种分析器:Preparser和Full parser。Preparser分析器可以推迟那些不是立即需要分析的函数以减少代码启动需要的时间。Preparser只会处理语法分析和一些错误的检查而不会生成抽象语法树。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"V8在这个阶段对各种类型的标记的扫描有着各种的优化手段,感兴趣的同学可以继续阅读"},{"type":"link","attrs":{"href":"https://v8.dev/blog/scanner","title":null},"content":[{"type":"text","text":"Blazingly fast parsing, part 1: optimizing the scanner"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在这里推荐一个用于分析抽象语法树的网站:"},{"type":"link","attrs":{"href":"https://astexplorer.net/","title":null},"content":[{"type":"text","text":"AST Explorer"}]},{"type":"text","text":"。在各种lint自定义规则,babel、webpack插件等代码分析、生成的场景里都有帮助。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"解释(I"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"nterpreting"},{"type":"text","text":")"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"解释阶段会将AST转换成字节码(bytecode)。得益于"},{"type":"link","attrs":{"href":"https://en.wikipedia.org/wiki/Just-in-time_compilation","title":null},"content":[{"type":"text","text":"即时编译(just-in-time (JIT) compilation)"}]},{"type":"text","text":"技术,包括V8在内的现代浏览器JavaScript引擎结合了"},{"type":"text","marks":[{"type":"strong"}],"text":"提前编译(AOT)"},{"type":"text","text":"的高性能和解释的灵活性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"起初,代码首先被编译器快速的编译成没有被优化过的机器码,在运行的同时再有选择的将需要优化的代码通过更高级的编译器进行优化再编译。这样做虽然提高了运行速度,但也浪费了资源。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中比较显著的问题是被编译过的机器码会占用大量的内存,即使有的代码可能只会被执行一次。为了解决这些问题,V8团队提出了新的JavaScript解释器,"},{"type":"link","attrs":{"href":"https://v8.dev/docs/ignition","title":null},"content":[{"type":"text","text":"Ignition"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"借助"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}},{"type":"strong"}],"text":"Ignition"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":",V8可以将AST先转化成更简洁的字节码,其大小与以往的机器码相比缩小到50%至25%的空间。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/35/355cb34cfd38811ba0c6e19ab7e7025c.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}},{"type":"strong"}],"text":"Ignition"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"生成的"},{"type":"text","text":"字节码会被用于优化的编译器和调试工具当作数据源,当字节码生成以后,抽象语法树就可以被废弃掉以节省内存。在生成字节码的同时,还会在字节码上增加一些元数据,比如源代码的位置和用于执行字节码的处理函数。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下来我们利用d8近距离观察一下bytecode,这里我们将下面的JavaScript放在名为bytecode.js的文件中,并运行d8的调试工具。"}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"function sayHi () {\n var str = 'hello world'\n return str\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以得到下面的结果"}]},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"# d8 --print-bytecode bytecode.js\n[generated bytecode for function: (0x300d082d25e5 )]\nParameter count 1\nRegister count 2\nFrame size 16\n 0x300d082d26ae @ 0 : 12 00 LdaConstant [0]\n 0x300d082d26b0 @ 2 : 26 fa Star r0\n 0x300d082d26b2 @ 4 : 27 fe f9 Mov , r1\n 0x300d082d26b5 @ 7 : 62 3e 01 fa 02 CallRuntime [DeclareGlobals], r0-r1\n 0x300d082d26ba @ 12 : 0d LdaUndefined \n 0x300d082d26bb @ 13 : ab Return \nConstant pool (size = 1)\n0x300d082d2681: [FixedArray] in OldSpace\n - map: 0x300d08042201
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.