前端性能优化
之前写过一篇文章前端网络高级篇(六)网站性能优化,里面提到过13个性能优化的点:
- 减少HTTP请求
- 使用CDN
- 利用HTTP缓存
- Gzip压缩
- 将样式表放在顶部
- 将JS脚本放在底部
- 避免CSS表达式
- 使用外部JS和CSS
- 减少DNS查找
- 压缩JavaScript和CSS
- 少用iframe
- JS文件异步/按需加载
- 图片懒加载
在具体编程方面,再补充几个点。
1. DOM编程优化
用JS操作DOM,是比较慢的。为什么呢?首先,补充一下浏览器相关的知识。下图为浏览器结构:
image
我们只要关心渲染引擎(Rendering engine)和JS引擎(JavaScript Interpreter)也可称为JS解析器。如图所示,当用JS引擎和渲染引擎是独立实现的,两者通过桥接接口通信。而DOM由渲染引擎绘制,所以,当JS改变DOM结构时,必须通过Bridge通知给渲染引擎,然后进行重排或者重绘。这个通信是有开销的。
重排:当我们对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来。
重绘:当我们对 DOM 的修改导致了样式的变化、却并未影响其几何属性(比如修改了颜色或背景色)时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式。
重排的开销要远大于重绘
所以,我们的优化点就是:
- 尽可能减少DOM操作
- 尽可能减少重排
看看下面的例子吧。
例子1: 在container
元素里面添加10000个“hello”。
不好的行为(JS多次读取DOM元素):
for(var count=0;count<10000;count++){
document.getElementById('container').innerHTML+='<span>hello</span>';
}
改造(JS只读取一次DOM元素,但是,依旧多次更改DOM元素):
// 只获取一次container
let container = document.getElementById('container');
for(let count=0;count<10000;count++){
container.innerHTML += '<span>hello</span>';
}
再改造(JS只读取一次DOM元素,只操作DOM元素):
let container = document.getElementById('container');
let content = '';
for(let count=0;count<10000;count++){
// 拼接内容
content += '<span>hello</span>';
}
// 最后更改DOM
container.innerHTML = content;
其实,JS里面用+
号拼接String开销也略大,一般会建议创建数组,然后通过array.join('')
将数组转为String,如:
let array = [];
for(let count=0;count<10000;count++){
// 拼接内容
array.push('<span>hello</span>');
}
container.innerHTML = array.join('');
不过,DOM提供了更好的内置容器来帮助做内容拼接 - DOM Fragment。最后,用 DOM Fragment 改写总结版:
let container = document.getElementById('container');
// 创建一个DOM Fragment对象作为容器
let content = document.createDocumentFragment();
for(let count=0;count<10000;count++){
// 通过DOM API创建span
let spanElt = document.createElement("span");
spanElt.innerHTML = 'hello';
// 像操作真实DOM一样操作DOM Fragment对象
content.appendChild(spanElt);
}
// 最后更改DOM
container.appendChild(content)
例子2:更改DOM元素样式
不好的行为(逐条更改样式):
const container = document.getElementById('container')
container.style.width = '100px';
container.style.height = '200px';
container.style.color = 'red';
改造(利用class,只改动一次样式):
//style.css
.basic_style {
width: 100px;
height: 200px;
color: red;
}
//app.js
const container = document.getElementById('container');
container.classList.add('basic_style');
当DOM离线时(display: none),无论怎么操作,浏览器都不会绘制它,也就不会引发重排或者重绘。所以,利用这个特性再改造一版:
let container = document.getElementById('container');
container.style.display = 'none';
container.style.width = '100px';
container.style.height = '200px';
container.style.color = 'red';
container.style.display = 'block'
最后提醒一下,下面的属性慎用。因为这些属性都需要实时计算得到,所以,浏览器为了取得正确的值,会进行重排!
offsetTop、offsetLeft、 offsetWidth、offsetHeight、 scrollTop、scrollLeft、scrollWidth、scrollHeight、 clientTop、clientLeft、clientWidth、clientHeight
3. 事件节流(throttle)和防抖(debounce)
比如窗口的scroll和resize事件,一旦激活,会频繁触发相应的事件函数。频繁触发回掉函数导致的大量计算有可能引发页面抖动甚至卡顿。为了规避这些风险,我们会采用事件节流或者防抖,来降低函数的触发频率。
节流:当事件第一次被触发时,在指定时间内,无论再次触发多少次,都会被忽略。也就是说,以第一次事件为准。 防抖:事件触发后,会延迟执行,在延迟时间内,如果事件再次被触发,上一次的事件被取消,以当次为准,重新延迟执行。也就是说,以最后一次事件为准。
示例代码如下:
// 节流 1
let canRun = true;
$(window).scroll(() => {
if(!canRun){
// 判断是否已空闲,如果在执行中,则直接return
return;
}
canRun = false;
setTimeout(() => {
canRun = true;
}, 300);
});
// 节流 2
let interval = 300;
let last = 0;
$(window).scroll(() => {
let now = +new Date()
if (now - last >= interval) {
// 如果时间间隔大于设定的时间间隔阈值,则执行回调
last = now;
....
}
});
// 防抖
let timer;
$(window).scroll(() => {
if(timer){
clearTimeout(timer)
}
timer = setTimeout(() => {
// 延时 200ms,处理滚动逻辑
}, 200)
})
一般在浏览器scroll和resize事件应用节流,在远程搜索场景下,应用防抖。
4. CSS优化
CSS选择器是从右向左解析的,所以,尽可能直接用class作为选择器,减少查询时间。
// 推荐
.top {...}
// 不推荐
// 浏览器会先查找所有的a标签,然后再找这些a标签中哪些有span父标签...
div span a {...}
- Eclipse JAVA文件注释乱码
- 2018年小程序的红利趋势预测,懂的来……或许你将成为下个富翁
- VUE 入门基础(6)
- 五年换4高管,6000员工裁95%剩300人,王健林为何抛弃万达网科?
- Android Permission中英对照
- 你知道人脸识别技术是如何实现的吗?
- WordPress REST API 定制化输出
- ASP.NET MVC的Action Filter
- Android LayoutInflater详解
- 在Android中实现service动态更新UI界面
- VUE 入门基础(5)
- Android的UI设计与后台线程交互
- 更强悍的Silverlight: WCF RIA Services
- Java究竟该怎么学?文末有彩蛋!
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Windows10中VS2017环境下使用libmodbus库Modbus TCP读取设备的数据
- 记一次生产服务器进程突然消失问题排查!
- 0812-7.1.3-如何使用Ranger给HBase授权
- Redis集群方案对比:Codis、Twemproxy、Redis Cluster
- 这就是你日日夜夜想要的docker!!!---------Docker镜像制作与私有仓库建立
- 排障集锦:九九八十一难之第十八难!-----System has not been booted with systemd as init system (PID 1). Can‘t operat
- 深入了解 Flex 属性
- 如何设计一个安全的短信接口?
- ERROR Shell:396 - Failed to locate the winutils binary in the hadoop binary path java.io.IOE...
- Windows 安装配置 PySpark 开发环境(详细步骤+原理分析)
- 安利三个关于Python字符串格式化进阶知识
- TCP/IP学习笔记1——协议分层
- 用Python爬取淘宝4403条大裤衩数据进行分析,终于找到可以入手的那一条
- Python 微信机器人:属于自己的微信机器人制作,简单易懂。图灵机器人接口api调用。
- 最全总结:把模块当做脚本来执行的 7 种案例及其原理