一. 浏览器内核
最开始想先说说浏览器内核,起初,浏览器内核指的是js引擎和渲染引擎,后来,js引擎越来越独立,浏览器内核逐渐就单指渲染引擎(也称排版引擎)。
浏览器都由哪些部分组成
1.用户界面
2.浏览器引擎(the browser engine)— 用来查询及操作渲染引擎的接口;
3.渲染引擎(the rendering engine)— 显示请求的内容;
4.网络 — 用来完成网络调用,例如 HTTP 请求;
5.UI 后端 — 用来回执类似组合框以及对话框等基本组件;
6.JS 解释器 — 解释执行JS 代码;
7.数据存储 — 属于持久层 ,浏览器或许会在本地保存各种数据,比如cookie
什么是渲染引擎(浏览器内核)
渲染引擎负责解析HTML文档和渲染页面,主流的渲染引擎有:IE的Trident,火狐的Gecko,Safari和Chrome的WebKit等。渲染引擎首先通过网络获得请求文档的内容,之后进行文档内容的解析和页面的渲染,一般流程如下:
解析html构建DOM tree—->结合样式规则构建render tree—>布局render tree—>绘制render tree
什么是js引擎
JavaScript引擎是一个专门处理JavaScript脚本的虚拟机,一般会附带在网页浏览器之中,主要负责解析和执行js代码。
二.js单线程
我们常说js运行在浏览器中是单线程的,也就是说,在某个特定的时刻只有特定的代码能够被js引擎执行,并且会阻塞其它的代码。
进程和线程都是操作系统的概念。进程是应用程序的执行实例,每一个进程都是由私有的虚拟地址空间、代码、数据和其它系统资源所组成;进程在运行过程中能够申请创建和使用系统资源(如独立的内存区域等),这些资源也会随着进程的终止而被销毁。而线程则是进程内的一个独立执行单元,在不同的线程之间是可以共享进程资源的,所以在多线程的情况下,需要特别注意对临界资源的访问控制。在系统创建进程之后就开始启动执行进程的主线程,而进程的生命周期和这个主线程的生命周期一致,主线程的退出也就意味着进程的终止和销毁。主线程是由系统进程所创建的,同时用户也可以自主创建其它线程,这一系列的线程都会并发地运行于同一个进程中。
为什么js要是单线程的
作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。若以多线程的方式操作这些DOM,则可能出现操作的冲突。假设有两个线程同时操作一个DOM元素,线程1要求浏览器删除DOM,而线程2却要求修改DOM样式,这时浏览器就无法决定采用哪个线程的操作。当然,我们可以为浏览器引入“锁”的机制来解决这些冲突,但这会大大提高复杂性,所以 JavaScript 从诞生开始就选择了单线程执行。那么js是如何解决例如I/0操作之类这样耗时的操作造成的阻塞问题呢?
JavaScript与生俱来的特性:异步与回调
js作为一门单线程的脚本语言还能够如此受欢迎当然离不开他与生俱来的特性:异步和回调。
所谓异步指的是函数的调用并不直接返回执行的结果,而往往是在特定的时刻,通过回调函数异步的执行。回调函数就是等到那个特定的时间执行的函数。
js中有很多异步的场景,比如:setTimeout和setInterval,AJAX异步加载,用户事件。那么问题就来了,既然浏览器是单线程的,那是谁在特定的事件将回调函数拿来让js线程执行,他是怎么做到异步的呢?
这里我们就要分清楚一个很重要的概念,js引擎至始至终都是单线程的,但浏览器是多线程的呀!浏览器的线程一般有:
- javascript引擎线程
- 界面渲染线程
- 浏览器事件触发线程
- Http请求线程
浏览器的内核是多线程的,它们在内核制控下相互配合以保持同步,一个浏览器至少实现三个常驻线程:javascript引擎线程,GUI渲染线程,浏览器事件触发线程。异步机制通常都是由浏览器的两个或两个以上线程完成的。那么异步机制具体的实现过程到底是什么样的呢?
这里,我们需要先引入几个下文要用到的概念:
执行栈:我们将js线程称为主线程,所有在主线程上排队执行的任务就形成了一个执行栈。
任务队列:主线程之外,浏览器中还存在一个”任务队列”。只要异步任务有了运行结果,就在”任务队列”之中放置一个任务。一旦执行栈中的任务执行完,系统就会它通过某种机制按顺序读取任务队列中的任务,拿到执行栈执行。
事件循环:事件循环是浏览器的一种运行机制,当主线程的任务执行完了以后,会将任务队列中的任务按照队列先进先出的顺序拿到主线程执行。取一个任务并执行的过程叫做一次循环。
接下来我们逐一说一下我们上文提到的几个异步的场景。
异步ajax:当主线程发起异步ajax请求时,浏览器就会新开一个ajax请求线程执行ajax请求,主线程就会继续往下执行,当ajax线程拿到响应以后,就会将注册异步任务时添加的回调函数放入任务队列尾部,主线程在执行完执行栈中的所有任务后,就会到任务队列中取出这个回调函数并执行。
setTimeout和setInterval:当主线程执行到setTimeout和setInterval时,浏览器会新开一个定时触发线程来处理计时器timer,根据设置的时间将回调函数放入任务队列。
用户事件:当主线程执行到一个事件监听器时,就相当于发起了一个异步过程,浏览器的事件触发线程就会去执行这个异步任务,当事件触发时,就意味着这个异步任务完成,事件触发线程就会将事件监听函数放入任务队列,等待主线程执行。
注意点:GUI渲染线程与JS引擎是互斥的,当JS引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。