一、浏览器内核类型
内核(渲染引擎) | JS引擎 | |
---|---|---|
Chrome | webkit -> blink | v8 |
Firefox | Gecko | SpiderMonkey(1.0-3.0) / TraceMonkey(3.5-3.6) / JaegerMonkey(4.0-) |
Safari | webkit | Nitro(4-) |
Opera | Presto -> blink | Linear A(4.0-6.1)/ Linear B(7.0-9.2)/ Futhark(9.5-10.2)/ Carakan(10.5-) |
IE/Edge | trident -> EdgeHTML | JScript(IE3.0-IE8.0 / Chakra(IE9+之后) |
渲染引擎:解析HTML/CSS进行页面渲染
JS引擎:用来解释执行js代码,早期内核包含js引擎,目前基本独立
二、浏览器进程
背景:JS之父Brendan Eich在1995年在Netscape公司就职时,为了快速满足浏览器上运行的客户端脚本语言,将Mocha设计为单线程运行脚本,当时为了趁Java语言的热度,将Mocha命名成JavaScript,其中单线程运行机制一直沿用至今。浏览器需要展示大量网页应用,一般用C++编写可以支持多进程。
从图上可以一目了然的看到,浏览会有四大进程,分别是
主进程
/插件进程
/GPU进程
/渲染进程
,其余还有NetworkService、AudioService、StorageService等;
- 主进程
主进程又称为Brower进程,负责浏览器的协调、主控,包活界面显示、用户交互、子进程管理、存储功能,有点像k8s的master节点的作用。 - GPU进程
最初是为了实现3D CSS效果,后续Chrome的UI界面都采用了GPU绘制,Chrome在其多进程架构上也引入了GPU进程。 - 插件进程
负责插件的运行,设计之初为了防止插件崩溃导致浏览器及页面的影响而专门跑了一个单独的插件进程进行隔离。 - 渲染进程
渲染进程算是最核心的进程了,主要是将html、css和js转换为具有用户交互的网页,其中JS引擎都运行在其中,默认情况下,Chrome主进程会为每个Tab标签创建一个渲染进程(当然还有),出于安全考虑,每个进程都运行在沙箱模式下。渲染进程包含诸多运行的线程,这部分下面单独细讲。
三、网页解析流程
一张网页是如何展示到页面上的,其中经过哪些环节,大致环节如图表示:
从图上可以看出,整个流程大致分为
三大块
去执行
- 首先是当用户在地址栏输入连接时,主流程会去解析URL,通过DNS服务器找到域名对应的IP地址,最后找到对应服务器建立传输层进行页面流的响应体,这块主要由浏览器主进程负责处理;
- 其次拿到页面内容以后,这时渲染进程里的GUI线程开始工作了,首先会对HTML内容进行深度优先搜索解析,生成Dom Tree 即Dom的节点树,其中遇到
<link>
标签会通知浏览器网络进程开启单独的下载线程进行异步下载css资源,此时是不会阻塞GUI线程对Dom树地解析,但是对于JS解析不同,GUI线程和JS引擎线程是互斥的,原因在于JS是可以操作Dom节点的,假如两个线程同时运行,很可能造成渲染线程前后获得的元素数据不一致的情况,所以在设计之初就有效的避免了这种情况。当Dom Tree和Cssom Tree形成一个Render Tree,从渲染树知道了每个元素的位置和尺寸,就会回流(Reflow)成Layout Tree,合成器线程合成渲染帧; - 最后就是将合成好的渲染帧交给GPU进程绘制(Paint)ui界面;
至此我们对网页的加载显示有了大致的了解。
四、JS编译到执行
都说JS是解释型动态语言,边解释边执行,不需要类似java一样先通过javac将源程序编译成.class文件,字节码,再将字节码转为机器码。那解析来具体看下整体过程是怎么样的。
首先了解下编译型
& 解释型
语言异同点,两者的设计初衷就是为了将高级语言编译成能被机器理解执行的语言,差异就是前者是运行前执行,后者是运行时执行并且需要在环境中安装解释器才能被解析,这部分就是JS引擎要做的工作。
由图可以看出JS解析过程主要包含两大阶段
语法检查阶段
和运行阶段
- 词法分析
JS代码运行之前,有一个类似类似编译的过程叫词法分析,主要包含三个步骤:分析函数参数
、分析变量声明
、分析函数声明
函数在运行瞬间会生成一个AO对象(Active Object)对每一步进行标记。
就以下面简短的例子举例
// A1 代码片段
var age = 11;
function foo() {
console.log(age);
}
foo();
// A2 代码片段
var age = 11;
function foo() {
console.log(age);
var age = 22;
console.log(age);
}
foo();
这里不得不提编程语言都会用到的两大作用域
词法作用域--类似javascript、java、c/c++、python、c# ...
动态作用域--类似Lisp、Perl
词法作用域(静态作用域),在创建阶段,会确定词法环境,当前词法环境会指向父级词法环境,直至null,是一种链表结构,类似原型链。
基于A1&A2代码片段的图中可以看出,创建阶段主要包含以上的三块内容,参数、变量、函数声明分别在全局和函数内部定义了age、foo同时被赋值成undefined,至此词法分析阶段结束。
- 语法分析
该过程简单来说会构造一棵抽象语法树(AST),如果分析到一个语法错误,就会抛出一个语法错误(SyntaxError),停止js代码执行,如果正确,则进入运行阶段的预解析过程。
- 预解析
在当前作用域下,JS代码执行前完成变量和函数的声明,会对var
和function
做变量提升,但不会做赋值操作。