# 浏览器进程和线程、PWA

# 1、浏览器进程

  1. GPU进程:浏览器全局只有这么一个进程,主要与图形渲染有关。
  2. 其他插件的进程,浏览器安装插件,一个插件就是一个进程。
  3. browser进程:浏览器的主进程(负责协调、主控),只有一个。主要功能如下:
    • 负责浏览器界面显示,与用户交互,如前进、后退等。
    • 负责各个页面的管理,创建和销毁其他进程
  4. 浏览器渲染进程,也被称为浏览器内核。负责页面的渲染,脚本执行,时间处理,网络请求等功能。分类有:
    • Google Chrome:Chrome 28开发版本中还在使用WebKit,最新的Chrome使用Blink。
    • Internet Explorer:Trident内核,ie内核
    • Mozilla Firefox:Gecko内核,firefox内核
    • safari:Webkit
    • Opera:最初是Presto内核,后来是Webkit,现在是Blink内核

# 2、浏览器的渲染进程

在一个进程中,至少有一个线程,线程为CPU任务调度的最小执行单位。浏览器的渲染进程中有如下线程:

  1. GUI渲染线程:解析html文档,生成DOM树与CSS树(注意CSS树不会阻塞dom树的生成)。当生成DOM树和CSS树之后,就根据这两个树生成一个render树(在生成render树时候,如果有一方没有解析完毕就会等待解析完成,此时双方会相互阻塞),然后将这个render树渲染到界面上。当页面触发回流或者重绘的时候,会再次执行此次操作。
  2. js线程:执行js代码。与GUI线程互斥,当一个执行时,另一个会被强制挂起。当js执行一个时间负责度高的算法时,导致GUI渲染线程被挂起太久,会导致页面卡顿,解决办法可以通过web worker解决。
  3. 定时器线程:用来处理定时器线程,当定时器到期的时候,将回调放在任务队列里面,等待js线程的执行。js是单线程的,为了避免代码执行冲突,添加定时器线程,用于处理定时器操作。
  4. 事件触发线程:用来管理事件的触发,例如:点击事件、鼠标移动事件。当这些事件被触发时,将事件的回调添加至任务队列,等待js的执行。
  5. 异步HTTP请求线程:在XMLHttpRequest连接后新启动一个线程,线程如果检测到请求的状态变更,如果设置有回调函数,该线程会把回调函数添加至事件队列,等待js执行。

# 1、渲染流程

构建 DOM 树 -> CSS Parser -> 样式计算 -> 布局阶段 -> 分层 ->绘制 -> 分块 -> 光栅化和合成,渲染是在渲染进程执⾏的,渲染进程分为渲染主线程、光栅线程、合成线程等;从分块阶段开始,分块、光栅化、合成这三步是在⾮渲染主线程执⾏。

⼀个完整的渲染流程⼤致可总结为如下:

  1. 构建 DOM 树: 渲染进程通过 HTML Parser 将 HTML 字符串转换为浏览器能够读懂的 DOM 树结构,查看:document

  2. 构建 Style Rules: 渲染引擎将 CSS 字符串转化为浏览器可以理解的 styleSheets,查看:document.styleSheets

  3. 样式计算:根据上⾯两步⽣成的 DOM Tree 和 Style Rules,可以计算出 DOM 节点的样式。

  4. 创建布局树:计算出 DOM 节点的样式后,还需要知道 DOM 节点在⻚⾯中显示的位置,这就需要计算元素的布局信息,⽣成⼀颗布局树,其中不包括不显示的 DOM 节点。

  5. 分层:对布局树进⾏分层,并⽣成分层树,具体哪些元素需要分层是由 CSS 决定的,⽐如 z-index等,也可以设置 will-change 来通知浏览器此元素需要分层。

  6. 绘制:为每个图层⽣成绘制列表,并将其提交到合成线程。

  7. 分块、光栅化: 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。

  8. 合成:把光栅化⽣成的多张位图合成⼀张位图;合成线程发送绘制图块命令 DrawQuad 给浏览器进程。

  9. 显示:浏览器进程根据 DrawQuad 消息⽣成⻚⾯,并显示到显示器上。

# 2、相关问题解析

  1. HTML Parser需要等待HTML全部加载完再解析?还是边加载边解析?

    一边加载一边解析,转换为:document

  2. JS如何影响HTML Parser?

    script脚本放在body元素中页面内容的后面,避免js阻碍html解析,减少白屏时间,

    • 内嵌的js脚本会执行阻塞html解析、网络加载的js脚本也会阻塞html解析
    • script标签中的defer属性,延迟执行脚本,解析完</html>后执行,执行顺序不变,即第一个defer脚本会在第二个defer脚本前执行
    • script标签中的async属性,异步执行脚本,文件加载完成后执行,无法保证执行顺序,哪个加载完哪个执行。
  3. css如何影响js执行?

    由于JS可以获取和操作样式,因此会等待JS语句之前的CSS Parser完成后才会执⾏此JS⽂件。 结论: CSS Parser -> JS执⾏ -> HTML Parser。

  4. css的渲染?

    计算样式的过程不会等待⻚⾯的底部的Style rules⽣成完毕, 会先paint⼀次⻚⾯,等⻚⾯底部的CSS解析完成后,repaint⼀次,会看到闪烁的情况;因此CSS⽂件尽量放在head中,尽快下载CSS⽂件并解析,然后结合dom渲染到⻚⾯中

  5. 性能优化方法:下载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"
}  
1
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:监听推送消息
// ***** 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
});
1
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的客户端和服务器通过握手连接,连接成功后才可通信。

Last Updated: 9/2/2021, 7:56:42 PM