大体结构
从输入url到页面加载发生了什么
简略:DNS域名解析将域名转换成IP地址=> 建立tcp连接 => 发送http请求 => 服务器接收请求并返回相应的资源 => 浏览器拿到资源开始渲染页面

渲染策略
各个环节都有可进行的优化:
DNS解析:浏览器dns缓存,dns prefetch(预解析)
tcp三次握手:长连接,预连接(不用每次都要进行tcp连接)
再往后前端能做的事情就多了,主要能从以下几个方面优化:
- 网络方面:减少http请求次数,减小请求体积,我们能做什么
- 渲染方面:拿到资源后,怎么能更快地把页面呈现出来;怎样提升用户在交互过程中的流畅度
- 性能分析:有哪些性能指标,怎么用工具分析前端性能
网络层面
目标:减少http请求次数与体积
策略1 资源的压缩与合并:使用构建工具并优化
webpack的瓶颈:打包过程耗费时间;打包结果体积太大
对webpack的优化:
- 在loaders中使用用 include 或 exclude 来避免不必要的转译:如庞大的node-moudules文件
- 第三方库用更高效的打包插件打包:dllplugin
- 对构建结果的体积进一步压缩:
- 删除冗余代码:1. 模块级的冗余:基于 import/export 语法,Tree-Shaking在打包过程中 可以删除定义了但是没有没用到的那些模块。 2. 更细粒度的冗余:UglifyJsPlugin对console语句/注释等进行删除
- 按需加载:页面在用到的时候才会加载,核心:require.ensure()进行异步加载
- gzip压缩:在一个文本文件中找出一些重复出现的字符串、临时替换它们,从而使整个文件变小。但由于压缩和解压都需要时间,所以建议不要在迷你项目中使用。在request header中使用:accept-encoding:gzip
策略2 图片优化
http资源中,图片往往是体积最大的资源,对于电商网站尤其如此
不同业务场景下图片应如何选型:
- JPEG/jpg:有损压缩、体积小、加载快、不支持透明
- 使用场景:适用于呈现色彩丰富的图片,JPG 图片经常作为大的背景图、轮播图或 Banner 图出现。
- png-8/24:无损压缩、质量高、体积大、支持透明,24代表2的24方种颜色
- 使用场景:呈现小的 Logo、颜色简单且对比强烈的(透明)图片或背景等。
- SVG(可缩放矢量图形):文本文件、体积小、不失真、兼容性好,渲染成本较高
- 应用场景:在html文件中使用
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200"> <circle cx="50" cy="50" r="50" /> </svg>或<img src="文件名.svg" alt=""> - iconfont小图标就都是svg格式
- 应用场景:在html文件中使用
- 雪碧图:将小图标和背景图像合并到一张图片上,然后利用 CSS 的背景定位来显示其中的每一部分的技术。定位/替换图片时麻烦(可维护性差)。
- base64:文本文件、依赖编码、小图标解决方案,雪碧图的补充方案
- 应用:将图片用base64编码,直接写入html文件中,无需http请求。编码完的体积会膨胀。当图片非常小且将来不会被替换时可以采用这种方案。
- webp:全能,但兼容性差
策略3 存储篇
利用好浏览器缓存与本地存储也可以有效减少http请求
浏览器缓存机制
- Memory Cache
- 内存中的缓存,当标签页(渲染进程)被关闭,缓存便失效。
- 主要为base64格式的图片
- Service Worker Cache
- 独立于主线程之外的 Javascript 线程。它脱离于浏览器窗体,因此无法直接访问 DOM。
- Window.navigator.serviceWorker.register
- HTTP Cache
- 强缓存:利用 Expires(时间戳、绝对时间) 和 Cache-Control (时间长度、相对时间)两个首部字段来控制。若命中则无需与服务器通信,客服端直接从缓存中获取资源(from disk cache)
- 协商缓存:Last-Modified(服)-If-Modified-Since (客)通过时间对比来确定资源有无发生改变;etag(服)-if-None-Match(客)通过唯一标示确定。若资源未被修改,则服务器返回304;若被修改,则服务器返回完整资源并更新相应字段
- 流程:当我们的资源内容不可复用时,直接为 Cache-Control 设置 no-store,拒绝一切形式的缓存;否则考虑是否每次都需要向服务器进行缓存有效确认,如果需要,那么设 Cache-Control 的值为 no-cache;否则考虑该资源是否可以被代理服务器缓存,根据其结果决定是设置为 private 还是 public;然后考虑该资源的过期时间,设置对应的 max-age 和 s-maxage 值;最后,配置协商缓存需要用到的 Etag、Last-Modified 等参数。
- Push Cache:http2提供的缓存
本地存储
- cookie:用于状态管理,让服务器知道客户端是谁。存储在浏览器的键值对文本,附着于http请求中
- 4KB的大小,同一域名下的所有http请求都会携带cookie,有时造成巨大的性能浪费
- web storage
- local storage:生命周期:永久;作用域:同源的所有网页;
- session storage:生命周期:会话级别;作用域:同源同浏览器窗口
- 与cookie的区别:大小可达5-10m,只存储在浏览器端。
- IndexedDB :一个运行在浏览器上的非关系型数据库。
- 理论上来说,IndexedDB 是没有存储上限的(一般来说不会小于 250M)。它不仅可以存储字符串,还可以存储二进制数据。
CDN
Content Delivery Network,即内容分发网络。一组分布在各个地区的服务器。这些服务器存储着数据的副本,因此服务器可以根据哪些服务器与用户距离最近,来满足数据的请求。缓存+回源
- cdn:用来存放静态资源,像仓库:做储存与转发的工作
- 根服务器:生成动态页面或返回非纯静态页面。像工厂,需要实时动态的生成资源
渲染层面
客户端渲染:首屏慢,SEO差(页面没有实际的内容,搜索引擎抓取不到)
服务端渲染:rendertostring()+模板嵌套
浏览器运行机制
浏览器内核=渲染引擎(html解释器,css解释器,图层布局计算,视图绘制,网络。。)+js引擎(很独立)
渲染过程:

- 回流:对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来。
- 对 DOM 的修改导致了样式的变化、却并未影响其几何属性(比如修改了颜色或背景色)时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式。
event loop:script => 微任务队列 => 一个宏任务
基于渲染流程的优化:
- css样式是从右向左进行匹配,所以:应尽量用id选择器替代后代选择器/避免通配符/利用好可以继承的样式
- 渲染根据render树来,而render树需要html+css树,所以CSS 是阻塞渲染的资源。需要将它尽早、尽快地下载到客户端,以便缩短首次渲染的时间。将css资源放在head里。
- JS 的作用在于修改,js的执行会阻塞浏览器的渲染(js引擎与渲染引擎是互斥的)所以js代码应放在页面底部,或者通过async/defer的方式引入。异步引入允许让浏览器在js下载过程中做其他事情。
- 减少对dom的操作:z操作一次dom都意味着js引擎与渲染引擎的沟通,并且一般伴随着回流与重绘。
- 渲染的时机:浏览器在每次宏任务执行的间隙会执行一次渲染,所以我们会造成渲染的操作应尽量靠近这个时机。
- 异步更新:vue(nextTick)或者react中会将更新推到一个异步队列中,在js层面批量完成,再将结果返回渲染(合并渲染操作)
- 避免回流重绘
- 将频繁变化属性的元素操作缓存起来,只返回最终结果
- 避免逐条改变样式,使用类名去合并样式
- 将 DOM “离线”:先display:none,修改好了再block。折中操作
- flush队列:浏览器会将触发回流重绘的操作缓存起来,等到队满/一定时间间隔/不得已(需立即获取某个属性值)一起执行
交互时优化
懒加载
当图片位于可见区域时才加载出来。
思路:先将图片scr设空,真正的链接放在另一个自定义属性中。在scroll事件中判断图片是否到达可视区域,到达了则将src替换。
节流与防抖
都以闭包的形式存在。
它们通过对事件对应的回调函数进行包裹、以自由变量的形式缓存时间信息,最后用 setTimeout 来控制事件的触发频率。
节流throttle:在某段时间内,不管你触发了多少次回调,我都只认第一次,并在计时结束时给予响应。
防抖debounce:在某段时间内,不管你触发了多少次回调,我都只认最后一次。
代码实现
懒加载结合节流防抖
div>
<div class="img">
<img data-src="./img/头像.jpg" src="" alt="loading">
</div>
...//很多张图片
</div>
<script>
//window.innerHeight为可视高度,
document.documentElement.clientHeight为兼容低版本的可视高度
//node.getBoundingClientRect()返回一组该元素距离视口左上角的上下左右值
//初级:1. scroll事件一直触发
// 2. 只有发生滚动行为时,首屏的图片才能加载
// 中级:1. 首屏图片无需用懒加载 2. 用防抖优化scroll事件
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
const imgs = document.getElementsByTagName("img");
let num = 0;
for (let i = num;i<imgs.length;i++){
//let distance = viewHeight - imgs[i].getBoundingClientRect().top;
if(imgs[i].getBoundingClientRect().top < viewHeight){
imgs[i].src = imgs[i].getAttribute("data-src");
}
}
function lazyload(){
for (let i = num;i<imgs.length;i++){
let distance = viewHeight - imgs[i].getBoundingClientRect().top;
if(distance >= 0){
imgs[i].src = imgs[i].getAttribute("data-src");
}
}
//num = i + 1;
console.log("scroll");
}
//节流:为持续触发事件注册一个时间间隔,当scroll开始时计时开始,时间间隔中的scroll事件都会被过滤掉,
//时间间隔到=>触发scroll事件回调.一段时间后执行第一次触发,过滤其他触发
function betterScroll(fn,interval){
let last = 0;
return function(){
let that = this;
let now = +new Date();
if(now - last >= interval){
fn.apply(that,arguments);
last = now;
}
}
}
//防抖:第一次触发时添加定时任务,在定时任务期间若有新的触发便清空定时器,
//重新计时,直到回调成功执行:只执行最后一次触发
function betterScroll2(fn,interval){
let time = null;
return function(){
if(time) clearTimeout(time);
let that = this;
time = setTimeout(function(){
fn.apply(that,arguments)},interval);
}
}
//window.addEventListener("scroll",betterScroll(lazyload,100),false);
window.addEventListener("scroll",betterScroll2(lazyload,200),false);
</script>
工具篇
- 性能分析:chrome开发工具performance面板
- 性能可视化:lighthouse插件
- 性能api:window.performance,提供了从dns解析到渲染的各个时间节点
常见性能指标
页面的性能指标详解:
白屏时间(first Paint Time)——用户从打开页面开始到页面开始有东西呈现为止
首屏时间——用户浏览器首屏内所有内容都呈现出来所花费的时间
用户可操作时间(dom Interactive)——用户可以进行正常的点击、输入等操作,默认可以统计domready时间,因为通常会在这时候绑定事件操作
总下载时间——页面所有资源都加载完成并呈现出来所花的时间,即页面 onload 的时间