一些概念
关于进程(Process)和线程(Thread)
举个例子:
- 比如电脑上可以打开很多软件,网页同时开启每一个程序,每一个程序开启就是开启一个进程
- 每个程序里开启多个任务,每一个任务就是线程
关于单核CPU和多核CPU
- 单核CPU:同时执行一个任务,多个任务交替完成
- 多核CPU:并行执行多个任务
- 浏览器内核:Webkit IE...
浏览器输入URL之后发生的事情
- A. HTTP请求阶段
- B. HTTP响应阶段
- C. 浏览器渲染阶段
A. HTTP请求阶段
第一步 URL解析
http://www.baidu.com:8080/index.html?abc=123&cde=456#heading1
- 协议: 用于传输客户端和服务端通信的信息
- http: 超文本传输协议(除文本外的富媒体资源:视频、图片等)
- HTTPS: HTTP+SSL(加密传输)
- FTP:文件传输协议(一般用于文件的上传下载,不属于浏览器)
- 域名:服务器地址
- 顶级域名:baidu.com
- 一级域名:www.baidu.com
- 二级域名:h5.baidu.com
- 端口号:就是区别同一台服务器上的不同项目(这是nginx配置的)
- 端口号在0~65535之间
- HTTP协议默认端口号是80(自己写地址的时候,不加端口号,浏览器会默认加上)
- HTTPS协议端口号是443
- FTP协议端口号是21
-
请求路径名称: index.html
-
查询字符串(问号参数):abc=123&cde=456
-
片段标识符(HASH值):#heading1
第二步 缓存检查
客户端拿到服务端的数据后,会把数据缓存一份,缓存的位置在内存缓存
或者硬盘缓存
浏览器重启打开一个页面,会查找缓存,先到硬盘缓存查找,没有则关闭网络请求
强缓存与协商缓存 【资源文件的检查】
关于强缓存
- 强缓存是服务器设置的,基于响应头的信息返回给客户端(nginx设置)
- 客户端浏览器接收到响应后,会自己建立缓存机制
第一次请求时,没有缓存,直接从服务器上获取【建立缓存标识】,客户端拿到内容后,缓存到本地
第二次请求,本地有缓存,会检查缓存有没有过期
- 没过期 -> 基于缓存信息渲染页面
- 过期了 -> 重复上一步
但是这样有一个问题是:如果度无端更新的资源文件(项目重新部署版本),会导致用户无法获取到服务器的最新信息
针对这样的情况,前端可以在webpack的output出口文件设置hash值,每次生成的hash值都是不一样的,会保证每次获取的都是最新的缓存 -> index.45121562.js
如果是强缓存,浏览器的network的响应头部分可以看到的响应头信息:
http1.1 => cache-control: max-age 时间
http1.0 expires 时间
关于协商缓存
- 在强缓存失效的情况下,协商缓存机制触发
- 客户端与服务端协商
第一次请求,没有任何缓存,直接从服务器获取资源和标识,缓存到客户端本地(成功返回->状态码200)
第二次请求,首先看本地是否有标识
- 没有 -> 重复上一步骤
- 有 -> 把标识传递给服务器(这是浏览器操作的),服务器收到结果之后,会做匹配(将部署的标识和客户端传的标识进行比对)
- 匹配一致 -> 说明没更新,返回状态码304
- 匹配不一致 -> 说明有更新,把文件信息和标识信息返回给客户端,返回状态码200
客户端判断状态码:
- 304 -> 把之前缓存的文件渲染
- 200 -> 按照新的文件信息渲染,并缓存到本地
如果是协商缓存,浏览器的network的响应头部分可以看到的响应头信息:
http1.1 => ETag/If-None-Match 标识
http1.0 => Last-Modified/If-Modified-Since 时间
哪些资源文件不能强缓存?
html文件不能强缓存 -> 用协商缓存,每次都要从服务器获取
css/js/png等文件可以强缓存,也可以协商缓存
所有资源文件的缓存都是nginx配置的
数据缓存
把从服务器获取到的数据进行缓存
通过Ajax/Axios/JQ的Ajax/Fetch 或者跨域方案,从服务器拿到数据
- 本地存储:cookie/localStorage/sessionStorage
- 本地数据库存储: indexDB
本地存储和本地数据库存储都是存储到物理磁盘,页面关闭重新打开也存在(除了sessionStorage)
- vuex/redux:临时存储->页面存储或刷新,缓存信息会消失
实现步骤:
第一次发送数据请求,看本地是否有缓存(或者缓存有没有过期)
本地如果没有缓存;通过AJAX获取数据,再进行数据绑定时,把获取到的值存储到本地(可以手动设置过期时间)
再次发送请求(重新预览页面或者刷新页面):查找本地缓存及是否过期
缓存生效 -> 不再获取缓存数据;反之,重复上一步
方案选择:
- 如果存储数据量特别大,用indexDB
- LocalStorage 一个源下能存储5MB 永久存储
- sessionStorage 是会话存储,页面关闭则存储的信息消失
- LocalStorage和sessionStorage不兼容IE8
- 同源下,LocalStorage能存储5MB,cookie能存储4kb
- cookie有过期时间,清除浏览器垃圾会把cookie清掉(无痕浏览不记录cookie)
- LocalStorage为永久存储,如果不手动清除会一直存在(和服务器无关)
- 只要本地有cookie,浏览器都会把这些信息发送给服务器(cookie可以兼容IE5)
第三步 DNS解析:把域名解析成IP地址
- 每次DNS解析过程在20~120ms
第一次请求baidu.com,从没解析过,需要20~120ms从头解析一遍
第一次请求完毕,当前的DNS解析记录会缓存下来(这是浏览器默认的)
第二次请求:会查询缓存信息
递归缓存
:会在一下地方查找解析记录:客户端 -> 浏览器缓存 -> 本地host文件 -> 本地DNS解析器缓存 -> 本地DNS服务器缓存 一层一层查找,如果有缓存速度会很快迭代缓存
:如果没有缓存会启动迭代缓存查找: 去DNS服务器查找 -> 去根域名服务器查找 -> 去顶级服务器查找 -> 去权威服务器查找 -> 拿到主机IP
DNS解析优化:
一个产品尽可能少去请求不同的域名(减少DNS的解析次数)
方案一
但是目前开发大部分都是多服务器部署,这样虽然会增加DNS解析次数,但资源分配更合理,提高并发(每台服务器的并发有上限);每个源下同时允许的http并发数6-7个,有助于页面渲染速度
- web资源服务器 -> nginx/apache [html/css/js]
- 图片资源服务器 -> 图片消息大,比较消耗性能,包括音视频
- 数据(接口)服务器:后台程序、数据库
- 第三方服务器
方案二
- DNS预解析:dns-prefetch 边渲染边解析
- GUI渲染线程 -> 自上而下解析代码
- 利用link的异步加载,在GUI线程渲染页面同时,去实现DNS解析
第四步 三次握手
B. HTTP响应阶段
待更新
- HTTP状态码
- 304缓存
- HTTP报文
第五步 四次挥手
C. 浏览器渲染阶段
第六步 页面渲染
浏览器渲染页面的机制和原理
现代操作系统比如Mac OSX,UNIX,Linux,Windows等,都是支持“多任务”的操作系统
单核CPU执行多任务:操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3……由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样
多核CPU执行多任务:真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行
有些进程还不止同时干一件事,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程
多个线程可以同时执行,多线程的执行方式和多进程是一样的,也是由操作系统在多个线程之间快速切换,让每个线程都短暂地交替运行,看起来就像同时执行一样
- 解析HTML,生成DOM树,解析CSS,生成CSSOM树
- 将DOM树和CSSOM树结合,生成渲染树(Render Tree)
- Layout(回流): 根据生成的渲染树,计算它们在设备视口(viewport)内的确切位置和大小,这个计算的阶段就是回流
- Painting(重绘): 根据渲染树以及回流得到的几何信息,得到节点的绝对像素
- Display:将像素发送给GPU,展示在页面上
DOM的重绘和回流 Repaint & Reflow
-
重绘:元素样式的改变(但宽高、大小、位置等不变)
- 如 outline, visibility, color、background-color等
-
回流:元素的大小或者位置发生了变化(当页面布局和几何信息发生变化的时候),触发了重新布局,导致渲染树重新计算布局和渲染
- 如添加或删除可见的DOM元素 ; 元素的位置发生变化; 元素的尺寸发生变化; 内容发生变化(比如文本变化或图片被另一个不同尺寸的图片所替代);
- 页面一开始渲染的时候(这个无法避免);
- 因为回流是根据视口的大小来计算元素的位置和大小的,所以浏览器的窗口尺寸变化也会引发回流....
注意:回流一定会触发重绘,而重绘不一定会回流
前端性能优化之:避免DOM的回流
- 放弃传统操作dom的时代,基于vue/react开始数据影响视图模式
- mvvm / mvc / virtual dom / dom diff ......
- 分离读写操作 (现代的浏览器都有渲染队列的机制)
- offsetTop、offsetLeft、offsetWidth、offsetHeight、clientTop、clientLeft、clientWidth、clientHeight
- scrollTop、scrollLeft、scrollWidth、scrollHeight、getComputedStyle、currentStyle....会刷新渲染队列
<style>
#box {
width: 100px;
height: 100px;
background: red;
border: 10px solid green;
}
.aa {
width: 200px;
}
</style>
<div id="box"></div>
let box = document.getElementById('box');
// 分离读写
// box.style.width = '200px';
// box.style.height = '200px';
// box.style.margin = '10px';
// console.log(box.clientWidth);
// 批量处理
// box.style.cssText='width:200px;height:200px;';
// box.className='aa';
// 缓存处理
// let a=box.clientWidth;
// let b=box.clientHeight;
// box.style.width = a + 10 + 'px';
// box.style.height = b + 10 + 'px';
- 样式集中改变
- div.style.cssText = 'width:20px;height:20px;'
- div.className = 'box';
- 缓存布局信息
- div.style.left = div.offsetLeft + 1 + 'px';
- div.style.top = div.offsetTop + 1 + 'px';
- =>改为
- var curLeft = div.offsetLeft; var curTop = div.offsetTop;
- div.style.left = curLeft + 1 + 'px';
- div.style.top = curTop + 1 + 'px';
- 元素批量修改
- 文档碎片:createDocumentFragment
<style> #box { position: absolute; width: 100px; height: 100px; background: red; border: 10px solid green; } </style> <ul id="box"> </ul>
// 1.文档碎片 // let frg = document.createDocumentFragment(); // for (let i = 0; i < 5; i++) { // let newLi = document.createElement('li'); // newLi.innerHTML = i; // //创建的li放到文档碎片中 // frg.appendChild(newLi); // } // // 一次性把内容放到容器中:引发一次回流 // box.appendChild(frg); // frg = null; // 字符串拼接 let str = ``; for (let i = 0; i < 5; i++) { str += `<li>${i}</li>`; } box.innerHTML = str; // box.style.left='100px'; box.style.transform='translateX(200px)';
- 模板字符串拼接
- 动画效果应用到position属性为absolute或fixed的元素上(脱离文档流)
- CSS3硬件加速(GPU加速)
- 比起考虑如何减少回流重绘,我们更期望的是,根本不要回流重绘;transform \ opacity \ filters ... 这些属性会触发硬件加速,不会引发回流和重绘......
- 可能会引发的坑:过多使用会占用大量内存,性能消耗严重、有时候会导致字体模糊等
- 牺牲平滑度换取速度
- 每次1像素移动一个动画,但是如果此动画使用了100%的CPU,动画就会看上去是跳动的,因为浏览器正在与更新回流做斗争。
- 每次移动3像素可能看起来平滑度低了,但它不会导致CPU在较慢的机器中抖动
- 避免table布局和使用css的javascript表达式