深入理解http请求的过程是前端性能优化的核心
从请求过程中可以发现一些优化点
- dns是否可以通过缓存减少dns查询时间?
dns缓存可以减少dns解析时间
- 网络请求的过程走最近的网络环境?
cdn以空间换时间,减少请求时间
- 相同的静态资源是否可以缓存?
下次不再请求,减少请求时间,减轻服务端压力
能否减少请求http请求大小?静态文件压缩, gzip
- 减少http请求
css\js\img合并
- 服务端渲染
加快首屏渲染时间
资源的合并与压缩
关键点:减少http请求数量
减少请求资源大小
- html压缩
原理:去掉空格,制表符,换行符,HTML注释等。
方法:
使用在线压缩网站进行压缩
利用nodejs提供的html-minifier
后端模板引擎渲染压缩 - css压缩
原理: 无效代码删除,css语义合并
方法:
使用在线压缩网站进行压缩
使用html-minifier对html中的css进行压缩
使用clean-css
对css进行压缩 - js的压缩与混乱
原理:无效字符的删除,剔除注释,代码语义的缩减和优化,代码保护
方法:
使用在线压缩网站进行压缩
使用html-minifier对html中的js进行压缩
使用uglifyjs2
对js进行压缩 - 文件合并
原理:例如将a.js b.js c.js合并成a-b-c.js
优点:
减少了n-1个网络请求, 丢包问题影响变小
缺点:
单文件变大导致首屏渲染变慢,修改局部导致整体缓存失效
解决:
公共库合并,不同页面的合并,随机应变 - 开启gzip
增加请求头Accept-Encoding:gzip,deflate
- 使用fis进行资源的压缩与合并
- 文件目录
- 安装依赖
cnpm i --save-dev fis3 fis-optimizer-html-minifier fis-parser-babel-5.x fis-parser-node-sass fis3-hook-relative html-minifier
- 配置fis-conf.js
//让所有文件,都使用相对路径。
fis.hook('relative');
fis.match('**', {
relative: true
});
//不压缩layout目录下的文件
fis.match('layout/**', {
release: false
});
//发布时,忽略项目中的这些文件
fis.set('project.ignore', ['.git/**', 'package.json','node_modules/**','fis-conf.js', '*.bat']);
//合并所有css文件
fis.match('css/**.scss', {
rExt: '.css',
parser: fis.plugin('node-sass'),
packTo: 'css/app.min.css'
});
//合并所有js文件
fis.match('js/**.js', {
// fis-optimizer-uglify-js 插件进行压缩,已内置
optimizer: fis.plugin('uglify-js', {
mangle: {},
compress: {
drop_console: true
}
}),
packTo: 'js/app.min.js'
});
//压缩html文件
fis.match('*.html',{
optimizer:fis.plugin('html-minifier')
});
//压缩合并css文件
fis.match('*.css', {
// fis-optimizer-clean-css 插件进行压缩,已内置
optimizer: fis.plugin('clean-css')
});
//压缩整合图片
fis.match('*.{png,jpg}', {
// fis-optimizer-png-compressor 插件进行压缩,已内置
optimizer: fis.plugin('png-compressor')
});
//将es6转es5
fis.match('*.js',{
rExt:'.js',
parser:fis.plugin('babel-5.x',{
blacklist:['regenerator'],
stage:3
}),
isMod:true,
useHash:true,
isJsLike:true
});
//压缩js文件
fis.match('*.js', {
// fis-optimizer-uglify-js 插件进行压缩,已内置
optimizer: fis.plugin('uglify-js')
});
fis.media('debug').match('*.{js,css,png}', {
useHash: false,
useSprite: false,
optimizer: null
});
- 命令行运行
fis3 release -d output
图片资源的优化
- 不同格式图片应用场景
- jpg有损压缩,压缩率高,不支持透明
不透明用这个
- png支持透明,浏览器兼容好
透明用这个
png8 —— 256色 + 支持透明
png24 —— 2^24色 + 不支持透明
png32 —— 2^24色 + 支持透明 - webp压缩程度更好,在ios webview有兼容性问题
安卓全用这个
WebP 的优势体现在它具有更优的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量;同时具备了无损和有损的压缩模式、Alpha 透明以及动画的特性,在 JPEG 和 PNG 上的转化效果都非常优秀、稳定和统一。 - svg矢量图,代码内嵌,相对较小,图片样式相对简单的场景,
小图标,字体图标用这个
- 图片压缩
- 在线图片压缩网站
- 在列表页使用缩略图,详情页使用中型图,放大才使用原图。
通过服务端解析文件名,网站访问不同的文件后缀实现, 如:
原图 aaa.jpg
中型图 aaa.jpg_500x500Q50S50.jpg
缩略图 aaa.jpg_100x100Q50S50.jpg
- image inline
小于10KB的图片转成base64直接内嵌到html中,减少请求资源。
进一步优化:将base64图片数据放到localstorage中,从缓存读取,减少html文件大小 - css sprite
使用ps将小图标放到一个png文件中合成雪碧图
通过在线获取雪碧图css代码,直接获取某个图标的css样式代码 - 字体图标
比如我们在www.iconfont.cn上收藏一些图标=》添加项目=》下载字体文件=》导出=》导入项目并使用
使用方法在demo_index.html中
css、js的加载与执行
- 加载规则
- 浏览器采用自上而下的方式解析,在遇到link,style,script时都会阻塞浏览器的解析,直到外部资源加载并解析或执行完毕后
才会继续向下解析html
- html预加载器会扫描代码,并行加载所有外部资源,
但受浏览器同一域名下最大并行数量限制,优化:向不同的cdn服务器请求
- 外部样式不会阻塞后续外部脚本的加载,
但外部样式会阻塞后续脚本执行,直到外部样式加载并解析完毕
。(因为script脚本执行过程中可能会修改html界面;DOM节点的CSS样式会影响js的执行结果) - 所有js按引入顺序执行,而不是加载完成的顺序;执行某个js脚本时,会阻塞html解析和后续js执行
- js引入方式 script defer async
实例:
//domhandle.js
document.getElementById('app').innerHTML="修改成功";
<!--index.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--普通方式,一定修改不成功-->
<script src="domhandle.js"></script>
<!--defer, 一定修改成功-->
<script src="domhandle.js" defer></script>
<!--async, 有可能修改成功-->
<script src="domhandle.js" async></script>
<div id="app">test</div>
</body>
</html>
懒加载和预加载
- 懒加载
并发加载的资源过多,会阻塞js的加载,影响网站的正常使用
页面过长但用户不一定会看下面的资源,减少无效资源的加载
图片进入可视区域之后才请求图片资源 - 预加载
图片等静态资源在使用之前提前请求,使用时从缓存中加载速度更快,提升用户体验 - 实现
- body中直接引入(预加载)
<body>
<img src="xxx" style="display:none">
</body>
<!--index.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.imgs .img-item{
display:block;
width:auto;
height:700px;
}
</style>
</head>
<body>
<div class="imgs">
<img src="" class="img-item" lazyload="true" data-original="https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2862526269,2797435169&fm=26&gp=0.jpg">
<img src="" class="img-item" lazyload="true" data-original="https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3771202784,101134037&fm=26&gp=0.jpg">
<img src="" class="img-item" lazyload="true" data-original="https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3498323135,1472840100&fm=26&gp=0.jpg">
<img src="" class="img-item" lazyload="true" data-original="https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=1082729974,3416555379&fm=26&gp=0.jpg">
<img src="" class="img-item" lazyload="true" data-original="https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=242247130,457677083&fm=26&gp=0.jpg">
<img src="" class="img-item" lazyload="true" data-original="https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2386982545,3518140799&fm=26&gp=0.jpg">
<img src="" class="img-item" lazyload="true" data-original="https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2953216290,3722654732&fm=26&gp=0.jpg">
<img src="" class="img-item" lazyload="true" data-original="https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=295297435,3968482153&fm=26&gp=0.jpg">
<img src="" class="img-item" lazyload="true" data-original="https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=113998444,2470350779&fm=26&gp=0.jpg">
<img src="" class="img-item" lazyload="true" data-original="https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1530410351,3540869741&fm=26&gp=0.jpg">
<img src="" class="img-item" lazyload="true" data-original="https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=1998755803,3998950952&fm=26&gp=0.jpg">
</div>
<script src="lazyload.js"></script>
</body>
</html>
//lazyload.js
let viewHeight=document.documentElement.clientHeight;//可视区域高度
function lazyload(){
let eles=document.querySelectorAll('img[data-original][lazyload]');
Array.prototype.forEach.call(eles,(item,index)=>{
if(item.dataset.original===""){
return ;
}
let rect=item.getBoundingClientRect();
if(rect.bottom>=0 && rect.top < viewHeight){
let img=new Image();
img.src=item.dataset.original;
img.onload=()=>item.src=img.src;
item.removeAttribute('data-original');
item.removeAttribute('lazyload');
}
})
}
lazyload();
document.addEventListener('scroll',lazyload);
重绘和回流
- 回流 reflow
当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow)
盒子模型相关属性
会触发重布局定位属性及浮动
也会触发重布局- 改变节点内部
文字结构
也会触发重布局 - 添加或删除DOM节点
- 浏览器窗口尺寸改变
width | height | padding | margin | display | border-width |
---|---|---|---|---|---|
border | min-height | top | bottom | left | right |
position | float | clear | text-align | overflow-y | font-weight |
overflow | font-family | line-height | vertical-align | white-space | font-size |
- 重绘 repaint
当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘。
color | border-style | border-radius | visibility | text-decoration | background |
---|---|---|---|---|---|
background-image | background-position | background-repeat | background-size | outline-color | outline |
outline-style | outline-width | box-shadow |
-
回流必将引起重绘
,而重绘不一定会引起回流 -
新建DOM的过程
- 获取DOM后分割为多个图层
- 对每个图层的节点计算样式结果(Recalculate style–样式重计算)
- 为每个节点生成图形和位置(Layout–回流和重布局)
- 将每个节点绘制填充到图层位图中(Paint Setup和Paint–重绘)
- 图层作为纹理上传至GPU
- 符合多个图层到页面上生成最终屏幕图像(Composite Layers–图层重组)
- Chrome创建图层的条件
- 3D或透视变换(perspective transform)CSS属性
- 使用加速视频解码的节点
- 拥有3D(WebGL)上下文或加速的2D上下文的节点
- 混合插件(如Flash)
- 对自己的opacity做CSS动画或使用一个动画webkit变换的元素
- 拥有加速CSS过滤器的元素
- 元素有一个包含复合层的后代节点(一个元素拥有一个子元素,该子元素在自己的层里)
- 元素有一个z-index较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)
- 实战优化点
- 用translate替代top改变
top会触发reflow但translate不会
- 用opacity替代visibility
visibility会repaint, 但opacity不会, 因为opacity是Composite Layers阶段变化
- 不要一条一条地修改 DOM 的样式
(虽然浏览器有缓冲机制)
var ele = document.getElementById('myDiv');
ele.style.borderLeft = '1px';
ele.style.borderRight = '2px';
ele.style.padding = '5px';
看起来元素的样式改变了三次,也就是三次重排和重绘,但是大多数浏览器会通过队列化修改并批量执行来优化重排过程,一次完成!但某些操作(获取布局信息的操作
)会强制刷新队列并要求计划任务立即执行。例如:
offsetTop, offsetLeft, offsetWidth, offsetHeight
scrollTop, scrollLeft, scrollWidth, scrollHeight
clientTop, clientLeft, clientWidth, clientHeight
getComputedStyle() (currentStyle in IE)
- 预先定义好 class,然后修改 DOM 的 className
- 使用cssText
合起来写只有一次reflow: div.style.cssText ="width:100px;height:100px;"
- 把 DOM 离线后修改,比如:先把 DOM 给 display:none (有一次 Reflow),然后你修改100次,然后再把它显示出来
- 使用document.createDocumentFragment创建文档片段修改,再添加到DOM树
- 不要把 DOM 结点的属性值放在一个循环里当成循环里的变量,
会回流强制执行缓冲区域以获取最新值
- 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局
- 动画实现的速度的选择 ,使用absolute或fixed定位页面上的动画元素,将其脱离文档流
- 避免不必要的复杂的 CSS 选择器,尤其是后代选择器(descendant selectors),因为为了匹配选择器将耗费更多的 CPU。
- 对于动画新建图层
will-change:transform
- 启用 GPU 硬件加速(3D)
transform:translateZ(0)
transform:translate3d(0,0,0);