# 浏览器进程和线程、PWA
# 1、浏览器进程
- GPU进程:浏览器全局只有这么一个进程,主要与图形渲染有关。
- 其他插件的进程,浏览器安装插件,一个插件就是一个进程。
- browser进程:浏览器的主进程(负责协调、主控),只有一个。主要功能如下:
- 负责浏览器界面显示,与用户交互,如前进、后退等。
- 负责各个页面的管理,创建和销毁其他进程
- 浏览器渲染进程,也被称为浏览器内核。负责页面的渲染,脚本执行,时间处理,网络请求等功能。分类有:
- Google Chrome:Chrome 28开发版本中还在使用WebKit,最新的Chrome使用Blink。
- Internet Explorer:Trident内核,ie内核
- Mozilla Firefox:Gecko内核,firefox内核
- safari:Webkit
- Opera:最初是Presto内核,后来是Webkit,现在是Blink内核
# 2、浏览器的渲染进程
在一个进程中,至少有一个线程,线程为CPU任务调度的最小执行单位。浏览器的渲染进程中有如下线程:
- GUI渲染线程:解析html文档,生成DOM树与CSS树(注意CSS树不会阻塞dom树的生成)。当生成DOM树和CSS树之后,就根据这两个树生成一个render树(在生成render树时候,如果有一方没有解析完毕就会等待解析完成,此时双方会相互阻塞),然后将这个render树渲染到界面上。当页面触发回流或者重绘的时候,会再次执行此次操作。
- js线程:执行js代码。与GUI线程互斥,当一个执行时,另一个会被强制挂起。当js执行一个时间负责度高的算法时,导致GUI渲染线程被挂起太久,会导致页面卡顿,解决办法可以通过web worker解决。
- 定时器线程:用来处理定时器线程,当定时器到期的时候,将回调放在任务队列里面,等待js线程的执行。js是单线程的,为了避免代码执行冲突,添加定时器线程,用于处理定时器操作。
- 事件触发线程:用来管理事件的触发,例如:点击事件、鼠标移动事件。当这些事件被触发时,将事件的回调添加至任务队列,等待js的执行。
- 异步HTTP请求线程:在XMLHttpRequest连接后新启动一个线程,线程如果检测到请求的状态变更,如果设置有回调函数,该线程会把回调函数添加至事件队列,等待js执行。
# 1、渲染流程
构建 DOM 树 -> CSS Parser -> 样式计算 -> 布局阶段 -> 分层 ->绘制 -> 分块 -> 光栅化和合成,渲染是在渲染进程执⾏的,渲染进程分为渲染主线程、光栅线程、合成线程等;从分块阶段开始,分块、光栅化、合成这三步是在⾮渲染主线程执⾏。
⼀个完整的渲染流程⼤致可总结为如下:
构建 DOM 树: 渲染进程通过 HTML Parser 将 HTML 字符串转换为浏览器能够读懂的 DOM 树结构,查看:document
构建 Style Rules: 渲染引擎将 CSS 字符串转化为浏览器可以理解的 styleSheets,查看:document.styleSheets
样式计算:根据上⾯两步⽣成的 DOM Tree 和 Style Rules,可以计算出 DOM 节点的样式。
创建布局树:计算出 DOM 节点的样式后,还需要知道 DOM 节点在⻚⾯中显示的位置,这就需要计算元素的布局信息,⽣成⼀颗布局树,其中不包括不显示的 DOM 节点。
分层:对布局树进⾏分层,并⽣成分层树,具体哪些元素需要分层是由 CSS 决定的,⽐如 z-index等,也可以设置 will-change 来通知浏览器此元素需要分层。
绘制:为每个图层⽣成绘制列表,并将其提交到合成线程。
分块、光栅化: 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
合成:把光栅化⽣成的多张位图合成⼀张位图;合成线程发送绘制图块命令 DrawQuad 给浏览器进程。
显示:浏览器进程根据 DrawQuad 消息⽣成⻚⾯,并显示到显示器上。
# 2、相关问题解析
HTML Parser需要等待HTML全部加载完再解析?还是边加载边解析?
一边加载一边解析,转换为:document
JS如何影响HTML Parser?
script脚本放在body元素中页面内容的后面,避免js阻碍html解析,减少白屏时间,
- 内嵌的js脚本会执行阻塞html解析、网络加载的js脚本也会阻塞html解析
- script标签中的defer属性,延迟执行脚本,解析完
</html>
后执行,执行顺序不变,即第一个defer脚本会在第二个defer脚本前执行 - script标签中的async属性,异步执行脚本,文件加载完成后执行,无法保证执行顺序,哪个加载完哪个执行。
css如何影响js执行?
由于JS可以获取和操作样式,因此会等待JS语句之前的CSS Parser完成后才会执⾏此JS⽂件。 结论: CSS Parser -> JS执⾏ -> HTML Parser。
css的渲染?
计算样式的过程不会等待⻚⾯的底部的Style rules⽣成完毕, 会先paint⼀次⻚⾯,等⻚⾯底部的CSS解析完成后,repaint⼀次,会看到闪烁的情况;因此CSS⽂件尽量放在head中,尽快下载CSS⽂件并解析,然后结合dom渲染到⻚⾯中
性能优化方法:下载CSS⽂件并解析、下载JS⽂件并执⾏可能会影响到⻚⾯的⾸屏渲染。因此⼀般可采⽤如下策略:
css文件加载尽量放在head中,减少CSS⽂件体积,对于⼤的CSS⽂件,可以通过媒体查询的⽅式加载不同⽤途的CSS⽂件, 参考
动画采⽤css3动画,或者添加will-change属性
script标签放在⻚⾯内容后,或者不需要在HTML Parser阶段使⽤的,可以加上defer 或者 async
⽹络层⾯:DNS prefetch; 采⽤CDN;开启HTTP2等
# 3、PWA
PWA(Progressive Web App)渐进式web app,使用多种技术来增强web app的功能,可以让网站的体验变得更好,能够模拟一些原生功能,比如通知推送。在移动端利用标准化框架,让网页应用呈现和原生应用相似的体验。
# 1、解决web的三大问题
- 没有一级入口:只能通过浏览器输入url访问
- 不能离线访问:无离线缓冲机制
- 没有消息推送功能
# 2、manifest.json
一级入口问题:通过引入manifest.json解决,主页通过link标签引入即可
// pwa-manifest.html
<link rel="manifest" href="/manifest.json">
//manifest.json
{
//web app名称
"name": "PWA Demo",
"short_name": "PWA",
//显示模式:fullscreen全屏模式、standalone独⽴应⽤模式
//minimal-ui比standalone多出地址栏、browser浏览器模式
"display": "standalone",
//应用描述
"description": "pwa demo description",
//指定了⽤户打开该Web App时加载的URL
"start_url": "/pwa-manifest.html",
//指定桌面图标
"icons": [{
//图标路径,相对于manifest.json
"src": "/icon/logo-144.png",
"sizes": "144x144", //图标大小
"type": "image/png" //图标类型
}],
//开屏界面默认背景
"background_color": "#aaa",
//应用程序默认背景色
"theme_color": "#aaa"
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 3、service worker
解决离线访问问题,本质为浏览器和后端服务的代理中间件,兼容性不佳,百度使用local storage模拟功能
- 特点:
- 不能访问/操作dom
- 会⾃动休眠,不会随浏览器关闭所失效(必须⼿动卸载)
- 离线缓存内容开发者可控
- 必须在https或者localhost下使⽤
- 所有的api都基于promise
- 原理:浏览器和后端服务之间的中间件
- 生命周期
- installing:sw注册之后,表示开始安装,会触发install事件
- installed
- activating:触发activate事件
- activated
- redundant:冗余卸载时触发
- 事件
- install
- installing生命周期函数中触发
- event.waitUntil(promise),等待promise resolved后进入下一个生命周期installed
- self.skipWaiting可跳过installed生命周期,直接进入activating生命周期
- activate
- activating生命周期内被触发
- event.waitUntil(promise),等待promise resolved后进入下一个生命周期activated
- self.clients.claim(),执行该方法表示取得页面控制权,这样打开页面就会使用新版本的缓存
- fetch:监听数据获取fetch方法
- push:监听推送消息
- install
// ***** pwa-demo.html *****
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>pwa manifest</title>
<!-- 引入manifest.json文件 -->
<link rel="manifest" href="/manifest.json">
</head>
<body>
<div>pwa manifest 1.01</div>
<script src="./sw-cache.js"></script>
</body>
</html>
//***** sw-cache.js *****
//serviceworker挂载在navigator下
if('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw.js').then(() => {
console.log('service worker 注册成功')
})
//缓存有更新,重新加载页面
let refreshing = false
navigator.serviceWorker.addEventListener('controllerchange', () => {
if (refreshing) {
return
}
refreshing = true;
window.location.reload();
});
}
//***** sw.js *****
//pwa全局对象专属对象 self、caches
var cacheName = '1.01'; // 缓存的key
var cacheFiles = [
'./pwa-manifest.html',
'./sw-cache.js',
'./manifest.json',
'./icon/logo-144-144.png',
];
// 监听install事件
self.addEventListener('install', function (e) {
console.log('Service Worker 状态: install');
self.skipWaiting()
const cacheOpenPromise = caches.open(cacheName).then(function (cache) {
console.log(cache)
return cache.addAll(cacheFiles);
});
e.waitUntil(cacheOpenPromise); // 等待promise resolved后,会进入下一个生命周期 installed
});
// 监听fetch事件,返回fetch数据缓存
self.addEventListener('fetch', function (e) {
// 如果有cache则直接返回,否则通过fetch请求
console.log(e.request)
e.respondWith(
caches.match(e.request).then(function (cache) {
return cache || fetch(e.request);
}).catch(function (err) {
console.log(err);
return fetch(e.request);
})
);
});
// 监听activate事件,激活后通过cache的key来判断是否更新cache中的静态资源
self.addEventListener('activate', function (e) {
console.log('Service Worker 状态: activate');
const cachePromise = caches.keys().then(function (keys) {
return Promise.all(keys.map(function (key) {
if (key !== cacheName) {
return caches.delete(key);
}
}));
})
e.waitUntil(cachePromise);
return self.clients.claim();// 激活当前client
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# 4、Push
解决通知问题:网站内通知,当前网站收到本网站的消息
浏览器和应用服务器间,添加Push Service中间代理层,用于消息订阅和推送,核⼼实现流程:
Subscribe: 向Push Service发起订阅,获取PushSubscription
Monitor: 实现浏览器和PushService通信 (⽤户离线,PushSevice会维护消息列表)
Distribute Push Resource: 将PushSubscription交给服务器,⽤于通信
Push Message: 服务端将消息推送给 Push Service, Push Service推送给对应的客户端
兼容性: Chrome所依赖的FCM服务在国内是⽆法访问的,⽽Firefox的服务在国内可以正常使⽤。
# 5、Notification
解决消息通知问题:网站外通知,百度收到淘宝发送的通知
# 4、web Worker
Web Worker 是HTML5标准的一部分,这一规范定义了一套 API,它允许一段JavaScript程序运行在主线程之外的另外一个线程中。
参考资料:Web Worker 使用教程(阮一峰) (opens new window)
# 5、web socket
websocked时一种双向通信协议,在建立连接后,websocked服务端和浏览器都能主动向对方发送和接收数据,websocked类似tcp的客户端和服务器通过握手连接,连接成功后才可通信。