1. 如何减少首屏加载时间
1.1 减小入口文件体积
常用的手段是路由懒加载,把不同路由对应的组件分割成不同的代码块,待路由被请求的时候会单独打包路由,使得入口文件变小,加载速度大大增加。
在vue-router
配置路由的时候,采用动态加载路由的形式
1 2 3 4 5 6 7 8
| routes:[ { path: 'Blogs', name: 'ShowBlogs', component: () => import('./components/ShowBlogs.vue') }, {...} ]
|
以函数的形式加载路由,这样就可以把各自的路由文件分别打包,只有在解析给定的路由时,才会加载路由组件。
1.2 UI框架按需加载
在日常使用UI
框架,例如element-UI
、或者antd
,我们经常性直接引用整个UI
库。
1 2
| import ElementUI from 'element-ui' Vue.use(ElementUI)
|
但实际上我用到的组件只有按钮,分页,表格,输入与警告 所以我们要按需引用。
1 2 3 4
| import { Button, Input, Pagination, Table, TableColumn, MessageBox } from 'element-ui'; Vue.use(Button) Vue.use(Input) Vue.use(Pagination)
|
1.3 图片资源压缩
图片资源虽然不在编码过程中,但它却是对页面性能影响最大的因素。
对于所有的图片资源,我们可以进行适当的压缩。
对页面上使用到的icon
,可以使用在线字体图标,或者雪碧图,将众多小图标合并到同一张图上,用以减轻http
请求压力。
1.4 DNS解析优化
如果一个页面中有很多外部资源,例如JavaScript、CSS、或者图片等资源,每次解析页面的时候都需要将这些资源的域名解析成IP地址。
同步解析:

异步解析:

我们可以使用link元素,在页面前面对资源的域名进行异步解析:
1
| <link rel="dns-prefetch" href="https://www.coffee.com"/>
|
在实际项目中可以这么做:
- 编写一个自动生成link标签的dns-prefetch.js文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| const fs = require('fs'); const path = require('patch'); const { parse } = require('node-html-parser'); const { glob } = require('glob'); const urlRegex = require('url-regex');
const urlPattern = /(https?:\/\/[^/]*)/i; const urls = new Set();
async function searchDomain() { const files = await glob('dist/**/*.{html,css,js}'); for (const file of files) { const source = fs.readFileSync(file, 'utf-8'); const matches = source.match(urlRegex({strict: true})); if (matches) { matches.forEach((url) => { const match = url.match(urlPattern); if (match && match[1]) { urls.add(match[1]); } }); } } }
async function insertLinks() { const files = await glob('dist/**/*.html'); const links = [...urls] .map((url) => `<link rel="dns-prefetch" href="${url}"/>`) .join('\n');
for (const file of files) { const html = fs.readFileSync(file, 'utf-8'); const root = parse(html); const head = root.querySelector('head'); head.insertAdjacentHTML('afterbegin', links); fs.writeFileSync(file, root.toString()); } }
async function main() { await searchDomain(); await insertLinks(); }
main();
|
- 在package.json打包文件中进行配置:

这样在npm run build构建项目的时候就会自动运行写好的函数,提取出外部链接构建link标签,进行域名的异步解析。
2. 重排与重绘
2.1 页面生成的过程
- HTML 被 HTML 解析器解析成 DOM 树
- CSS 被 CSS 解析器解析成 CSSOM 树
- 结合 DOM 树和 CSSOM 树,生成一棵渲染树(Render Tree),这一过程称为 Attachment
- 生成布局(flow),浏览器在屏幕上“画”出渲染树中的所有节点
- 将布局绘制(paint)在屏幕上,显示出整个页面
第四步和第五步是最耗时的部分,这两步合起来,就是我们通常所说的渲染
2.2 渲染
在页面的生命周期中,**网页生成的时候,至少会渲染一次。在用户访问的过程中,还会不断触发重排(reflow)和重绘(repaint)**,不管页面发生了重绘还是重排,都会影响性能,最可怕的是重排,会使我们付出高额的性能代价,所以我们应尽量避免。
2.3 重排(回流)> 重绘
单单改变元素的外观,肯定不会引起网页重新生成布局,但当浏览器完成重排之后,将会重新绘制受到此次重排影响的部分。比如改变元素高度,这个元素乃至周边dom都需要重新绘制。
也就是说:重绘不一定导致重排,但重排一定会导致重绘。
2.4 重排 (reflow)
概念
当DOM的变化影响了元素的几何信息(元素的的位置和尺寸大小),浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。
重排也叫回流,简单的说就是重新生成布局,重新排列元素。

发生重排的情况
- 页面初始渲染,这是开销最大的一次重排
- 添加/删除可见的DOM元素
- 改变元素位置
- 改变元素尺寸,比如边距、填充、边框、宽度和高度等
- 改变元素内容,比如文字数量,图片大小等
- 改变元素字体大小
- 改变浏览器窗口尺寸,比如resize事件发生时
- 激活CSS伪类(例如::hover)
- 设置 style 属性的值,因为通过设置style属性改变结点样式的话,每一次设置都会触发一次reflow
- 查询某些属性或调用某些计算方法:offsetWidth、offsetHeight等,除此之外,当我们调用 getComputedStyle方法,或者IE里的 currentStyle 时,也会触发重排,原理是一样的,都为求一个“即时性”和“准确性”。
重排影响的范围
由于浏览器渲染界面是基于流式布局模型的,所以触发重排时会对周围DOM重新排列,影响的范围有两种:
- 全局范围:从根节点html开始对整个渲染树进行重新布局。
- 局部范围:对渲染树的某部分或某一个渲染对象进行重新布局
2.5 重绘 (repaints)
概念
当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘。

2.6 如何减少重排和重绘
- 避免设置多层内联样式
- 使用absolute或者fixed,使元素脱离文档流,这样他们发生变化就不会影响其他元素
- 避免使用table布局,一个小的改动可能会使整个table进行重新布局
- 将DOM的多个读操作(或者写操作)放在一起,而不是读写操作穿插着写
浏览器会将所有的回流、重绘的操作放在一个队列中,当队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会对队列进行批处理。这样就会让多次回流、重绘变成一次回流重绘。
3. 防抖函数
防抖是指在事件被触发n秒后再执行,如果在这n秒内事件又被触发,则重新计时
3.1 非立即执行版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <div> <button onclick="btnClick()">按钮</button> </div>
<script> function debounce(fn, wait) { let timer = null;
return function() { const context = this; const args = arguments;
if(timer) { clearTimeout(timer); timer = null; }
timer = setTimeout(() => { fn.apply(context, args); }, wait); } }
function handler() { console.log('点击按钮'); } const btnClick = debounce(handler, 1000); </script>
|
3.2 立即执行版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <div> <button onclick="btnClick()">按钮</button> </div>
<script> function debounce(fn, wait) { let timer = null;
return function() { const context = this; const args = arguments; const callNow = !timer;
if(timer) { clearTimeout(timer); }
timer = setTimeout(() => { timer = null; }, wait); if (callNow) fn.apply(context, args); } }
function handler() { console.log('点击按钮'); } const btnClick = debounce(handler, 1000); </script>
|
4. 节流函数
节流是指规定的时间内,只能触发一次这个事件,如果在同一个规定时间内,事件被触发多次,只有一次能生效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <div> <button onclick="btnClick()">按钮</button> </div>
<script> function throttle(fn, delay) { let curTime = Date.now();
return function() { const context = this; const args = arguments; const nowTime = Date.now();
if (nowTime - curTime >= delay) { curTime = Date.now(); fn.apply(context, args); } } }
function handler() { console.log('点击按钮'); } const btnClick = throttle(handler, 1000); </script>
|