浏览器工作原理:从输入URL到页面加载完成

@amandakelake 2018-09-13 02:23:54发表于 amandakelake/blog Web

有一道经典的题目叫做从输入URL到页面加载完成的过程中发生了什么
除去一些硬件、后端,我顺着整个学习流程,查了一些资料,参考了许多前辈们的博客(再次谢过开源分享的前辈们),在前端方向总结出来的一些知识点,几乎囊括了整个前端体系,可以看到JS在整个流程里面也只是一个很小一块知识点而已(当然,一切的基础围绕JS展开)
c7fa8db7-8c9d-40c1-9636-cfca6dee370b

如果再加上性能(可以从本文延伸出来)、框架原理、工程化、安全、移动端的一些东西,几乎就是目前一个大前端的知识体系了,也是比较庞大了,不再是以前大家眼中简简单单HTML+CSS+JS走天下的时代了,过去了……路漫漫其修远兮,吾将上下而求索!

前面先捋一捋一些比较核心的基础概念,后面再细细展开,如果觉得后面逻辑有点乱了,可以回头再看看这个图,沿着脉络去看会事半功倍

一、进程和线程

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

查看电脑中的进程,mac可以打开活动监视器
a396974e-cfe4-467c-ac63-104ecfe3bc94

单线程与多线程,都是指在一个进程内的单和多

浏览器是多进程的
理论上:在浏览器中打开一个网页相当于新起了一个独立的浏览器进程(进程内有自己的多线程)

打开chrome的任务管理器,可以发现,有些tab的进程会被合并,这应该是浏览器的优化机制
33b6d36c-28a2-4ea7-89d8-63c17f6959fd

浏览器的进程

浏览器的主要进程有如下几个
866a7e55-2cec-4a51-b763-7043c9112499

  • Browser进程:浏览器的主进程(负责协调、主控),只有一个
负责浏览器界面显示,与用户交互。如前进,后退等
负责各个页面的管理,创建和销毁其他进程
将Renderer进程得到的内存中的Bitmap,绘制到用户界面上
网络资源的管理,下载等
  • 第三方插件进程
每种类型的插件对应一个进程,仅当使用该插件时才创建看上面的图片中的“扩展程序”就是了
  • GPU进程:最多一个,用于3D绘制等
  • 浏览器渲染进程(浏览器内核),这是我们重点关注的进程
Renderer进程,内部是多线程的
页面渲染,脚本执行,事件处理等

浏览器多进程的优势

  • 单个tab奔溃不会影响整个浏览器
  • 避免第三方插件影响浏览器
  • 多进程充分利用多核优势

当然,这样内存也会增加,空间换时间吧

二、Browser进程与浏览器内核通信

先看个简单的宏观模型

  • Browser进程收到用户请求
  • 网络请求获取页面内容(资源下载会阻塞)
  • 渲染进程(Render进程)解析
  • 渲染线程(看清楚,是线程,不是进程)渲染网页(需要Browser进程分配资源和GPU进程的帮助)

3d8a3213-8e01-4655-838e-1a61d6336e09

网上盗图一张
7307a22d-870c-4461-9906-14bfb12f771d

三、浏览器渲染(render)进程(浏览器内核)

重点关注渲染进程,以下动作都是这个进程执行的

  • 页面的渲染
  • JS的执行
  • 事件的循环

浏览器的渲染进程是多线程的,它包含了如下线程(得区分清楚进程和线程的概念)
69a510eb-a8b5-4b1d-9e4f-49c1aa522fc7

1、GUI渲染线程

负责渲染浏览器界面,解析HTML、CSS
当界面需要重绘(Repaint)或由于某种操作引发回流(Reflow)时,该线程就会执行
GUI渲染线程与JS引擎线程是互斥的,因为JS可以操作DOM元素, 从而影响到GUI的渲染结果,当JS引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行

2、JS引擎线程

JS内核(例如V8引擎),负责处理Javascript脚本程序
JS引擎一直等待着任务队列中任务的到来,然后加以处理
GUI渲染线程与JS引擎线程是互斥的,所以如果JS执行时间过长,页面渲染就不连贯,造成页面渲染加载阻塞

3、事件触发线程

由于JS引擎这个单线程的家伙自己都忙不过来,所以需要浏览器另开一个线程协助它
待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)

4、 定时触发器线程

setInterval与setTimeout所在线程
JS引擎阻塞状态下计时不准确,所以由浏览器另开线程单独计时
计时完毕后,添加到事件队列中,等待JS引擎空闲后执行
W3C规定,setTimeout中低于4ms的时间间隔算为4ms

5、异步HTTP请求线程

如果请求有回调事件,异步线程就产生状态变更事件,将这个回调再放入事件队列中,等JS引擎空闲后执行

好了,回顾一下,浏览器是多进程的,JS是单线程的
10680d05-8f93-45d1-ac5b-ecd290d60324

四、关于浏览器渲染进程的一些概念

1、GUI渲染线程与JS线程互斥

JS可以操作DOM,如果GUI和JS线程可以用时运行,那么最后的DOM是不可预测的
所以当JS引擎执行时GUI线程会被挂起, GUI更新则会被保存在一个队列中等到JS引擎线程空闲时立即被执行

2、WebWorker 改变了JS单线程的本质吗

Web Worker 允许 JavaScript 脚本创建多个线程,但是子线程完全受主线程控制,且不得操作 DOM 。
所以,这个新标准并没有改变 JavaScript 单线程的本质

WebWorker SharedWorker区别

Web Workers除了WebWorker,还有SharedWorker、Service Workers
具体详看MDN
Web Workers API - Web API 接口 | MDN

大概提一下,WebWorker只是属于该render进程下的一个线程,SharedWorker由独立的进程管理,多个render进程可以共享

3、JS阻塞页面

由于JS线程和GUI线程互斥 ,所以一旦某段JS代码执行时间过长,页面渲染就会渲染不连贯,出现“加载阻塞”页面渲染的现象

从性能方面考虑,以下几个地方需要优化的

  • JS加载逻辑放到页面底部,减少JS加载对GUI渲染工作的影响
  • 避免重排/回流/Reflow(影响布局和大小的CSS样式),减少重绘/Repaint(颜色改变)
  • 避免DOM嵌套层级过深
  • 使用 requestAnimationFrame 来实现动画视觉变化,setTimeoutsetInterval 的回调在帧的某个时间点运行,如果刚好在末尾,可能导致丢帧,引发卡顿

4、CSS加载会否阻塞页面?

  • CSS是由单独的网络请求线程异步下载的
  • CSS下载不会阻塞DOM树解析(DOMContentLoaded),但会阻塞render树渲染(loaded)

因为CSS有可能会影响DOM节点,所以如果不阻塞render树渲染,有可能会造成回流(重绘倒是小事),造成不必要的性能开销

5、普通图层和复合图层

普通文档流都在一个复合图层内,绝对布局absolute/fixed也不例外,依然在这个普通图层中

可以通过硬件加速的方式来声明新的复合图层,新的复合图层不会影响默认图层内的回流重绘

* translate3d、translateZ
* opacity属性/过渡动画(动画执行过程中才会生成复合图层,其他状态依然在默认图层中)
* will-chang属性

GPU会单独分配资源,单独绘制各个复合图层,互不影响

absolute和硬件加速的区别

absolute脱离了普通文档流,但没有脱离默认复合图层
absolute中的信息变化不会影响render树,但依然会影响最终的默认复合图层的绘制,会引起重绘,所以也会消耗资源(对本页面的渲染消耗)

而硬件加速,则只会影响新的复合图层,改动后可以避免整个页面重绘
但也不能无节制的使用复合图层,否则也会引起资源消耗过度(对GPU的资源消耗)

五、从输入url到呈现页面(前端角度)

简单过一下整体流程,每一块都值得细细展开去了解更多
后面重点聊聊浏览器渲染流程

整个过程

  • DNS解析
  • TCP连接+HTTP请求
  • 浏览器解析渲染页面
  • 连接结束

1、DNS解析

首先在本地域名服务器中查询IP地址,如果没有,按照如下顺序
根域名服务器. -> 顶级域名服务器com -> …… -> 本地域名服务器
. -> .com -> google.com. -> www.google.com.

优化:DNS缓存
浏览器缓存 -> 系统缓存 -> 路由器缓存 -> ISP(运营商)DNS缓存 -> 根域名服务器 -> 顶级域名服务器com -> 主域名服务器的顺序

浏览器缓存可通过在浏览器输入chrome://net-internals/#dns查看
系统缓存在/etc/hosts文件中(linux系统)

2、TCP请求(三次握手)+ HTTP请求

客户端:我要请求数据,可以吗
服务器:可以
客户端:好的

客户机与服务器建立连接后就可以通信,浏览器向web服务器发送http请求

3、浏览器解析渲染页面

4fd56ac9-5d48-4d8a-8283-1ea65077eb5b

  1. 处理 HTML 标记并构建 DOM 树
  2. 处理 CSS 标记并构建 CSSOM 树。
  3. 将 DOM 与 CSSOM 合并成一个渲染树。
  4. 根据渲染树来布局,以计算每个节点的几何信息。
  5. 调用 GPU 绘制,合成图层,显示在屏幕上

DOMContentLoaded 事件触发代表初始的 HTML 被完全加载和解析,不需要等待 CSS,JS,图片加载。
Load 事件触发代表页面中的 DOM,CSS,JS,图片已经全部加载完毕。

4、连接结束,四次挥手

客户端:我没要数据要发送了,准备挂了
服务器:收到,但我还有一些数据没发送完,稍等一哈

服务端:好了,发送完了,可以断开连接了
客户端:OK,你断开连接吧(内心独白:我将会在2倍的最大报文段生存时间后关闭连接,如果再收到服务器的消息,那么服务器就是没听到我最后这句话,我就再发送一遍)

六、浏览器解析渲染页面