前端性能优化—网页加载过程、性能优化方法、防抖和节流
- 一、网页加载过程
- 1、加载资源的形式
- 2、加载资源的过程
- 3、渲染页面的过程
- 4、关于window.onload 和 DOMContentLoaded
- 二、性能优化
- 1、性能优化原则
- 2、性能优化的方法
- 3、让加载更快
- 4、让渲染更快
- 三、防抖和节流
- 1、防抖 debounce
- 2、节流 throttle
- 四、写在最后
平常我们在加载网页的时候,首先需要先加载网页代码,之后渲染出页面,在这个期间会执行若干个
JS
。那么,如果想要让网页呈现速度和渲染速度快,我们就得保证我们的代码在浏览器这个运行环境当中稳定且高效。这就谈到一个前端性能优化的问题。
在下面这篇文章,将讲解关于前端性能优化的一些常见问题。
一、网页加载过程
1、加载资源的形式
网页需要加载的资源,一般包括以下内容:
html
代码;- 媒体文件,如图片、视频等;
javascript
、css
。
2、加载资源的过程
加载资源的过程需要经过以下几个步骤:
DNS
解析:域名 ->IP
地址。- 浏览器根据
IP
地址向服务器发起http
请求。 - 服务器处理
http
请求,并返回给浏览器。
3、渲染页面的过程
渲染过程 - 1
- 根据
HTML
代码生成DOM Tree
; - 根据
CSS
代码生成CSSOM
; - 将
DOM Tree
和CSSOM
整合形成Render Tree
。
渲染过程 - 2
- 根据
Render Tree
渲染页面; - 遇到
<script>
则暂停渲染,优先加载并执行JS
代码,完成后再继续执行; - 直至把
Render Tree
渲染完成。
4、关于window.onload 和 DOMContentLoaded
window.addEventListener('load', function(){//页面的全部资源加载完才会执行,包括图片、视频等
});document.addEventListener('DOMContentLoaded', function(){//DOM 渲染完即可执行,此时图片、视频还可能没加载完 -> 尽量选择此方法
});
二、性能优化
性能优化是一个综合性的问题,永远没有标准答案,下面将从几个方面来讲解性能优化的内容。
1、性能优化原则
- 多使用内存、缓存或其他方法。
- 减少
CPU
计算量,减少网络加载耗时。 - 适用于所有编程的优化方法 —— 空间换时间。
2、性能优化的方法
(1) 让加载更快
(2) 让渲染更快
3、让加载更快
(1)减少资源体积
- 压缩代码: 可以通过压缩代码来减少资源体积,包括
js
文件、css
文件和图片都可以进行压缩。同时服务器端 可以通过gzip
算法来对资源进行压缩。
(2)减少访问次数
-
合并代码: 比如说我们写了三四个文件,但通过打包可能就只剩下一个文件,并且文件里面是以一行的形式出现,或者雪碧图也算是其中一种方式。
-
SSR服务器端渲染:
服务端把网页和数据一起加载,一起渲染。
非SSR:先加载网页,再加载数据,再渲染数据,这个过程听起来就优点繁琐。
早期的
JSP
、ASP
、PHP
,现在的vue SSR
、React SSR
。 -
缓存:
举个例子:假设有
10
个资源,如果每次请求都要请求10
次,那这样子是非常耗时的;那如果10
个资源中有6
个命中了缓存,则只需要请求另外4
个。那如何做缓存来达到减少访问次数呢?
前端会在静态资源上加
hash
后缀,根据文件内容计算hash
。当文件内容不变时,则hash
和url
都不变。此时url
和文件不变,则会自动触发http
缓存机制,返回304
。
(3)使用更快的网络
-
通过 CDN 来操作:
CDN
,即内容分发网络(Content Delivery Network,简称CDN
),是建立并覆盖在承载网之上,由分布在不同区域的边缘节点服务器群组成的分布式网络。CDN
就是一个反向代理,根据地域来做网络服务,主要是前端用来做静态资源加载,通常在前端项目部署的时候进行这项操作。我们平常所使用的
bootstrap
、JQuery
一般都采用cdn
的形式。这里我也不是特别了解
cdn
的内容,只能才疏学浅的进行介绍。附上一篇我看完觉得比较好理解的文章,大家可以根据需求进行查看~
4、让渲染更快
(1)html、css、js和图片层面
-
css
放在head
,JS
放在body
最下面; -
尽早开始执行
JS
,用DOMContentLoaded
触发; -
懒加载(图片懒加载,上滑加载更多)。
<img id = "img1" src = "preview.png" data-realsrc = "abe.png"/> <script type = "text/javascript">let img1 = document.getElementById('img1');//把真实的图片data-realsrc赋值给预览的图片srcimg1.src = img1.getAttribute('data-realsrc');//…… 一系列逻辑操作 </script>
(2)从DOM层面
- 对
DOM
查询进行缓存; - 从频繁进行
DOM
操作,变为合并到一起进行DOM
结构插入;
注:关于DOM的性能优化我有在之前的一篇文章中讲过,在主题一的第4小点,这里不再进行细讲,大家可以根据自身需求进行查阅~
(3)防抖 debounce 和 节流 throttle
关于防抖和节流的讲解,详细见下方。
三、防抖和节流
1、防抖 debounce
(1)含义: 从频繁执行触发变为只在最后一次触发。
(2)发生场景: 监听一个输入框,如果直接用 keyup
事件,则每输入一个文字都会触发 onchange
事件,频繁执行操作。
(3)防抖解决: 用户输入结束或暂停时,才会触发 change
事件。
(4)引例阐述
假设我们现在要监听一个输入框的值,此时我们直接用 keyup
事件来实现。实现方式如下:
<!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>Document</title>
</head>
<body><input type="text" id="input1"><script type="text/javascript">const input1 = document.getElementById('input1');input1.addEventListener('keyup', function(){console.log(input1.value);});</script>
</body>
</html>
此时浏览器的显示效果是这样的。
大家可以发现,当我们使用 keyup
事件处理,每当我们输入一个字母时,浏览器就会频繁进行打印,这样看起来也太耗费性能了。
于是就有了防抖的解决方案,防抖通过对频繁执行的操作变为只在最后一次执行。实现方式如下:
<!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>Document</title>
</head>
<body><input type="text" id="input1"><script type="text/javascript">const input1 = document.getElementById('input1');let timer = null;input1.addEventListener('keyup', function(){if(timer){clearTimeout(timer);}timer = setTimeout(() => {//模拟触发 change 事件console.log(input1.value);//清空定时器timer = null;}, 500);// console.log(input1.value);});</script>
</body>
</html>
此时浏览器的显示效果是这样的。
通过上图发现,当我们在输入框输入字母时,只在最后输入结束时控制台才打印结果出来。而不会像上面那样频繁打印,因此通过防抖,实现了把频繁执行变为了只在最后一次执行的操作。
(5)封装防抖函数
通过上面的例子,相信大家对防抖有了一个基础的了解。接下来我们通过封装函数来实现相同效果的防抖功能。具体代码如下:
<!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>Document</title>
</head>
<body><input type="text" id="input1"><script type="text/javascript">const input1 = document.getElementById('input1');// 封装一个防抖函数function debounce(fn, delay = 500){// timer 是在闭包中的let timer = null;return function(){if(timer){clearTimeout(timer);}timer = setTimeout(() => {fn.apply(this, arguments);}, delay);}}//运用防抖函数实现input输入框触发操作input1.addEventListener('keyup', debounce(function () {console.log(input1.value);}, 600));</script>
</body>
</html>
2、节流 throttle
(1)含义: 从频繁执行触发变为每隔一段时间触发一次。
(2)发生场景: 拖拽一个元素时,直接用 drag
事件,则会频繁触发,很容易导致卡顿。
(3)节流解决: 无论拖拽速度多快,都会每隔 100ms
触发一次(这里的 100ms
不是固定值,也可设置成其它的值)。
(4)引例阐述
假设我们现在要拖拽一个元素,并且想要随时拿到该元素被拖拽的值。此时我们直接用 drag
事件来实现。实现方式如下:
<!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>Document</title><style>#div1{border: 1px solid #ccc;width: 200px;height: 100px;}</style>
</head>
<body><div id = "div1" draggable="true">可拖拽</div><script type="text/javascript">const div1 = document.getElementById('div1');//用节流之前div1.addEventListener('drag', function(e) {console.log(e.offsetX, e.offsetY);})</script>
</body>
</html>
此时浏览器的显示效果是这样的。
大家可以发现,当我们使用 drag
事件处理,每当我们拖拽元素时,浏览器就会频繁进行打印,这样似乎有一点点耗费性能。
于是就有了节流的解决方案,节流通过对频繁执行的操作变为只在每隔一段时间操作。实现方式如下:
<!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>Document</title><style>#div1{border: 1px solid #ccc;width: 200px;height: 100px;}</style>
</head>
<body><div id = "div1" draggable="true">可拖拽</div><script type="text/javascript">const div1 = document.getElementById('div1');// 用节流之后let timer = null;div1.addEventListener('drag', function(e){if(timer){return;}timer = setTimeout(() => {console.log(e.offsetX, e.offsetY);timer = null;}, 500);});</script>
</body>
</html>
此时浏览器的显示效果是这样的。
通过上图发现,当我们在拖拽时,不再像刚刚那样频繁打印,而是有规律的每隔 500ms
打印一次,这样子看起来就比刚刚的频繁触发要好很多了。所以,节流是通过将频繁执行改为每隔一段时间执行。
(5)封装节流函数
通过上面的例子,相信大家对节流有了一个基础的了解。接下来我们通过封装函数来实现相同效果的节流功能。具体代码如下:
<!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>Document</title>
</head>
<body><input type="text" id="input1"><script type="text/javascript">const input1 = document.getElementById('input1');// 封装一个节流函数function throttle(fn, delay = 500){let timer = null;return function (){if(timer){return;}timer = setTimeout(() => {fn.apply(this, arguments);timer = null;}, delay);}}//运用节流函数实现拖拽时每隔一段时间触发操作div1.addEventListener( 'drag', throttle(function(e){console.log(e.offsetX, e.offsetY);}, 200));</script>
</body>
</html>
四、写在最后
关于前端性能优化的一些基础内容就讲到这里啦!如有疑问或文章有讲的不好的地方欢迎评论区评论或私信我交流~
关注公众号 星期一研究室 ,不定期分享学习干货
如果这篇文章对你有用,记得点个赞加个关注再走哦~