1.浏览器的主要构成部分

  • 1.用户界面
  • 2.浏览器引擎(负责窗口管理、Tab进程管理等)
  • 3.渲染引擎(有叫内核,负责HTML解析、页面渲染)
  • 4.JS引擎(JS解释器,如Chrome和Nodejs采用的V8)

828919d9-c202-4faf-bd3f-5a30ac8e317f

这里面最核心的就是渲染引擎和JS引擎,后面会详细介绍这两个引擎的相关内容。

常见浏览器的渲染引擎和JS引擎如下:

浏览器 渲染引擎 JS引擎
IE Trident Chakra
Edge EdgeHTML Chakra
Firefox Gecko SpiderMonkey
Chrome Webkit -> Blink V8(著名的)
Safri Webkit Javascriptcore
Opera Presto->Blink Carakan

注:新版本的Chrome采用的渲染引擎是Blink,Blink是由谷歌团队从Webkit衍生开发出来的引擎,主要有应用到Chrome和Opera浏览器。

2.从进程和线程的角度来理解浏览器工作

1)进程和线程

  • 进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位)
  • 线程是cpu调度的最小单位(线程是建立在进程的基础上的一个程序运行单位,一个进程中可以有多个线程)

进程可以类比为工厂,线程就是工厂里面的工人,一个工厂可以包含一个或者多个工人,工人之间可以相互协作,并且共享工作空间

2)浏览器的多进程架构

现代的浏览器采用的都是多进程架构,主要包含以下三种进程:

1.Browser进程

浏览器的主线程,主要负责浏览器的页面管理、书签、前进后退、资源下载管理等,整个浏览器应用程序只有一个,对应上述浏览器组成中的浏览器引擎。

2.渲染进程

内核进程、负责页面渲染、JS执行,对应的是上述的渲染引擎和JS引擎,一个浏览器可以包含多个渲染进程,每个Tab窗口页对应一个渲染进程

3.GPU进程

负责GPU渲染,整个浏览器应用程序只有一个

4.插件进程

浏览器安装的插件(扩展程序),每个插件会创建一个进程

bb2acc88-8a5c-46af-b0c9-435249759ad7

当打开上面两个Tab时,Chrome任务管理器截图:主要包括

  • 1个浏览器进程
  • 1个GPU进程
  • 1个网络进程
  • 2个渲染进程(对应一个Tab一个进程)
  • 4个扩展程序进程

9b570a0e-3e1f-4d1d-b7b6-50ef4fa59ccc

MAC的活动监视器中的chorme进程,可以看到所有的渲染、扩展、GPU、网络进程都统一显示为Google Chrome Helper。

b235d30b-15ea-4a2b-a339-8b089e6f20ea

这种多进程浏览器架构主要有如下优势:

  • 1.避免单个页面奔溃影响整个浏览器
  • 2.避免第三方插件奔溃影响整个浏览器
  • 3.充分利用多核优势

3)浏览器的渲染进程

  • 浏览器有多个渲染进程、一个Tab页面一个(相同的Tab页面可能会被合并)
  • 一个渲染进程包含多个线程

一个渲染进程主要包括如下线程:

1.GUI线程(主要负责解析HTML、CSS和渲染页面)

2.JS引擎线程(负责解析和执行JS代码)

3.事件线程(控制事件循环)

4.定时器线程(处理定时器相关逻辑)

5.异步请求线程(发起Ajax时会生成该线程)

线程规则:

1.GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起,页面的更新操作会等到JS引擎空闲时执行,涉及任务和微任务相关知识

2.一个渲染进程同时只有一个JS解析线程在运行

3.JS引擎线程不停的处理事件线程推送到事件队列中的任务

4.定时器和异步请求最终生成的回调事件也有事件线程来控制和管理

了解了浏览器的渲染进程之后我们再来看看JS引擎。

4)从事件循环的角度来理解JS引擎的工作过程

在理解什么是事件循环之前,我们先来了解下同步和异步的概念

1.同步和异步

同步是代码执行后就可以获得想要的结果,异步是指代码执行之后不能立即获得结果,

你打电话问书店老板有没有《Javascript权威指南》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下",然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。
而异步机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来返回结果。

2.同步任务和异步任务

同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而是由事件线程调度,在满足执行条件的时候放到事件队列中,等待主线程(JS线程)的执行。

3.为什么要有事件循环的概念

JS包含有异步操作(如:Ajax、定时器),这些异步操作完成之后需要通知JS引擎来处理异步操作的返回,如Ajax的Callback。这些异步操作什么时候完成是不确定的,所以需要有一个事件队列,事件线程将已经完成的异步操作的回调任务加载到事件队列中,JS引擎在执行完当前的同步任务之后循环从事件队列中取事件执行。

异步任务:setTImeout、setInterval、Promise、process.nextTick(Node.js)、Ajax

同步任务:除以上异步任务

同步任务和异步任务的执行流程如下:

异步任务统一有事件线程管理,当异步任务完成的时候会被放入到事件队列中,JS在顺序执行完当前的代码之后会从事件队列中读取任务,再重复整个流程,判断该任务是同步还是异步。

c090d99e-5d8b-4328-990b-3071a2be9cdb

4.异步任务的优先级

如果按照上述的简化理解,所有异步任务都按照满足执行条件的顺序放到事件队列中,世界很和平,先来先到,但是在ES6当中,引入了microtask的概念,microtask会在当前的任务执行完成之后立即执行。因为我们将异步任务分为task和microtask,我们又称为宏任务和微任务。

task:setTImeout、setInterval、ajax

microtask:MutationObserve、promise、process.nextTick(Node.js)

这样子加了优先级的话JS的执行又会变得再复杂一点,如下图所示,异步任务执行完成之后会判断他是task还是microtask,再分别加到不同的时间队列中,JS当前任务执行完成之后优先清空当前的microtask队列,而且在每次执行完宏任务的时候都会去清空微任务。

0c051b9f-5c3f-46f3-9565-bd466f74e182

示例:

运行如下示例,就可以验证上述执行流程:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	<div class="outer">
	  	<div class="inner"></div>
	</div>
	<script>
		// Let's get hold of those elements
		var outer = document.querySelector('.outer');
		var inner = document.querySelector('.inner');
		// Let's listen for attribute changes on the
		// outer element
		new MutationObserver(function() {
		  console.log('mutate');
		}).observe(outer, {
		  attributes: true
		});
		// Here's a click listener…
		function onClick() {
		  console.log('click');
		  setTimeout(function() {
		    console.log('timeout');
		  }, 0);
		  Promise.resolve().then(function() {
		    console.log('promise');
		  });
		  outer.setAttribute('data-random', Math.random());
		}
		// …which we'll attach to both elements
		inner.addEventListener('click', onClick);
		outer.addEventListener('click', onClick);
	</script>
</body>
</html>

5.UI渲染线程什么时候工作

UI渲染线程会在当前的Task执行完成之后,下一个Task执行之前执行,微任务会优先于UI渲染线程,这就意味着我们使用微任务更新的DOM能更快的被渲染出来。另外Vue.js最新版本数据变更的时候采用的是promise和MutationObserver创建微任务:https://github.com/vuejs/vue/blob/ba0ebd4771ddb5c56c1261f82c842b57ca7163a6/src/core/util/next-tick.js

Demo:用于理解任务和UI渲染之间的关系

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue nextTick</title>
</head>
<body>
    <!-- jsFiddle例子:http://jsfiddle.net/gkmzns9u/14/ -->
    <div id="task">
        <div id="msg">
            {{msg}}
        </div>
        <div @click="greet">
            click me!
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <script type="text/javascript">
        const vm = new Vue({
            el: '#task',
            data: {
                msg: 'hello'
            },
            methods: {
                greet: () => {
                    // 修改model,触发set方法,调用update方法,添加DOM更新微任务
                      vm.msg  = 'hello world';
                      vm.msg  = 'hello world2';
                      // 查看DOM,由于是异步更新DOM,根据EventLoop原理可知,这里DOM还没有更新,
                      // hello
                    alert(document.getElementById('msg').innerHTML);
                    // nextTick使用promise,是个微任务,在当前greet方法执行完成之后会立即执行
                    vm.$nextTick().then(() => {
                        // 由于DOM更新微任务先被添加,先入先出,这里获取的DOM已经是更新好的
                        // hello world
                        alert(document.getElementById('msg').innerHTML);
                        // 直接修改DOM,同步任务
                        document.getElementById('msg').innerHTML = 'test'
                        // 立即生效
                        // test
                        alert(document.getElementById('msg').innerHTML);
                        /* 根据HTML Standard,一轮事件循环执行结束(包括微任务)之后,下轮事件循环执行之前开始进行UI render。即:macro-task任务执行完毕,接着执行完所有的micro-task任务后,此时本轮循环结束,开始执行UI render。UI render完毕之后接着下一轮循环。 */
                    });
                    // 由于setTimeout为宏任务,虽然延迟时间为0,但还是要晚于nextTick执行
                    // 而且可以明显看到在setTimeout回调执行之前页面上已经渲染上test,说明UI Render已经在setTimeout回调之前执行
                    setTimeout(()=>{
                        alert('setTimeout start')
                        document.getElementById('msg').innerHTML = 'setTimeout'
                    }, 0)
                }
            }
        });
    </script>
</body>
</html>