页面性能优化
一、页面加载过程
加载
- DNS解析:域名 -> IP地址;
- 取得目的主机 MAC 地址;
- 浏览器与目的IP地址建立TCP连接,发起Http请求;
- 服务器处理请求,返回资源。
渲染
- 解析 HTML 代码构建 DOM 树,解析 CSS 代码构建 CSSOM 规则树;
- 根据 DOM 和 CSSOM 构建 Render Tree ;
- 利用 Render Tree 对页面进行布局和绘制;
- 在解析 HTML 代码时,遇到<script> 浏览器会先下载和构建CSSOM,然后执行脚本,最后才是继续构建DOM。
二、优化方向与方法
1、总体方向:
加快加载速度:
压缩代码,减少体积;
减少请求数;
使用 CDN 加速;
使用 SSR (服务器端渲染)。
加快渲染速度:
CSS放在 head 标签内,JS放在 body 最后;*
尽早开始执行JS,用 DOMContentLoaded 触发;**
懒加载;
缓存 DOM 查询操作,将对 DOM 的操作合并,最后再操作改变 DOM;
节流(throttle)、防抖(debounce)。
* 前者是为了防止渲染过程中Render Tree发生改变重新渲染;后者是为了让页面尽快呈现出来,减少用户等待时间;
** window.onload:等页面全部资源加载完才会执行,包括视频、图片;
DOMContentLoaded: DOM渲染完成后就能执行,此时图片、视频等可能为加载完成4。
2、实现方法:
SSR : 可以使用 Nuxt.js 做 SSR
懒加载:
方法一:
<img id="img" src="preview.png" data-realsrc="abc.png"> <script> var num = document.getElementsByTagName('img').length; var img = document.getElementsByTagName("img"); var n = 0; //存储图片加载到的位置,避免每次都从第一张图片开始遍历 lazyload(); //页面载入完毕加载可是区域内的图片 window.onscroll = lazyload; function lazyload() { //监听页面滚动事件 var seeHeight = document.documentElement.clientHeight; //可见区域高度 var scrollTop = document.documentElement.scrollTop || document.body.scrollTop; //滚动条距离顶部高度 for (var i = n; i < num; i++) { if (img[i].offsetTop < seeHeight + scrollTop) { if (img[i].getAttribute("src") == "default.jpg") { img[i].src = img[i].getAttribute("data-src"); } n = i + 1; } } } </script>
节流(throttle):规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。
// 函数节流的实现; function throttle(fn, delay) { var preTime = Date.now(); return function () { var context = this, args = arguments, nowTime = Date.now(); // 如果两次时间间隔超过了指定时间,则执行函数。 if (nowTime - preTime >= delay) { preTime = Date.now(); return fn.apply(context, args); } }; } // 业务实例:拖拽一个元素,要随时拿到该元素被拖拽的位置。 // 直接用drag事件,则会频发的触发,很容易导致卡顿。 // 而节流则是无论比拖拽多快,都会每隔一个固定事件触发一次。 const div1 = document.getElementById('div1') div1.addEventListener('drag', throttle( function (e) { console.log(e.offsetX, e.offsetY) }, 200))
防抖(debounce):在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。
// 函数防抖的实现 function debounce(fn, wait) { var timer = null; return function () { var context = this, args = arguments; // 如果此时存在定时器的话,则取消之前的定时器重新记时 if (timer) { clearTimeout(timer); timer = null; } // 设置定时器,使事件间隔指定事件后执行 timer = setTimeout(() => { fn.apply(context, args); }, wait); }; } // 业务实例:监听一个输入框的文字变化后,触发change事件。 // 直接用keyup事件,会频繁的触发change事件。 // 而防抖则是用户输入结束或者暂停时,才会触发change事件。 const input1 = document.getElementById('input1') input1.addEventListener('keyup', debounce(() => { console.log(input1.value) }))
三、补充
DNS 预解析:使浏览器主动去执行域名解析。
<link rel="dns-prefetch" href="//example.com">
HTTP预连接:
解释href的属性值,如果是合法的URL,然后继续判断URL的协议是否是http或者https否则就结束处理。
如果当前页面host不同于href属性中的host,crossorigin其实被设置为anonymous(就是不带cookie了),如果希望带上cookie等信息可以加上crossorign属性,corssorign就等同于设置为use-credentials
<link rel="preconnect" href="//example.com"> <link rel="preconnect" href="//cdn.example.com" crossorigin>
预加载资源:让浏览器预加载一个资源(HTML,JS,CSS或者图片等),但页面是不会解析或者JS是不会直接执行的。
<link rel="prefetch" href="//example.com/next-page.html" as="html" crossorigin="use-credentials"> <link rel="prefetch" href="/library.js" as="script">
预渲染:不仅会加载资源,还会解执行页面,进行预渲染,但是这都是根据浏览器自身进行判断。
- 分配少量资源对页面进行预渲染。
- 挂起部分请求直至页面可见时。
- 可能会放弃预渲染,如果消耗资源过多。
<link rel="prerender" href="//example.com/next-page.html">
pr属性:
dns-prefetch,preconnect,prefetch和prerender都支持一个pr属性(0.0到1.0范围的值),让浏览器能够判断优先加载哪些资源。浏览器内部是有可用的连接池的,资源紧张的情况下只能加载优先级更高的资源。
浏览器缓存:链接
参考:
https://developer.mozilla.org/zh-CN/docs/Web/HTML/CORS_settings_attributes
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!