前端点滴(前端优化)---倾尽所有
前端优化方案
降低请求量:合并压缩资源,减少HTTP 请求数,minify / gzip 压缩,webP,lazyLoad,预加载,防抖节流。
加快请求速度:预解析DNS,减少域名数,并行加载,CDN 分发。
缓存:HTTP 协议缓存请求,离线缓存 manifest,离线数据缓存localStorage。
渲染:JS/CSS优化,css预处理,加载顺序,服务端渲染,pipeline。
降低请求量
资源合并与压缩
资源压缩
1.html压缩
html压缩就是压缩这些在文本文件中有意义,但是html中不现实的字符,包括(空格,制表符,换行符等),还有一些其他意义的字符,如html注释也可以被压缩
如何进行html压缩
- 使用在线网站进行压缩
- nodejs 提供了html-minifier工具
https://www.npmjs.com/package/html-minifier - 后端模板引擎渲染压缩
但在真实的开发中一般是通过构建工具进行压缩的,比如 webpack。
2.css压缩
简单来说就是无效代码删除,css语义合并
如何进行css压缩
- 使用在线网站进行压缩
- 使用 html-minifier 对 html 中的 css 进行压缩
https://www.npmjs.com/package/html-minifier - 使用 clean-css 对 css 进行压缩
https://www.npmjs.com/package/clean-css
3.js压缩
将无效的字符进行删除,删除注释,代码语义的减缩以及优化,代码的保护
如何进行js压缩
- 使用在线网站进行压缩
- 使用 html-minifier 对 html 中的 css 进行压缩
https://www.npmjs.com/package/html-minifier - 使用 uglifyjs2 对 js 进行压缩
https://www.w3cschool.cn/fundebug_js/fundebug_js-hvum3071.html
资源合并
文件合并存在的问题
首屏渲染问题(文件合并成一个文件,首次加载时间会比较长)
缓存失效问题(修改其中一处内容,整个文件变动,在浏览器的缓存失效,又要重新加载整个文件)
文件合并建议
- 公共库合并
- 不同页面的合并(vue的异步加载组件)
- 见机行事,随机应变
资源合并的方法
- 使用在线网站进行合并
- 使用nodejs或webpack等框架实现文件合并
图片相关优化
了解png8/png24/png32之间的区别
png8 – 256色(2^8) 支持透明–适用于颜色种类较少的png图片
png24 – (2^24) 不支持透明
png32 – (2^24) 支持透明–适用于颜色种类丰富的png图片
不同图片常用的业务场景
jpg有损压缩,压缩率高,不支行透明
png支持透明,浏览器兼容好
webp压缩程序更好,在ios webviewe有兼容性问题
svg矢量图,代码内嵌,相对较小,图片样式相对简单的场景
图片压缩
针对真实图片情况,舍弃一些相对无关的色彩信息
图片压缩的方法
- css雪碧图
优点:减少Http请求
缺点:当雪碧图过大时,加载过慢,在雪碧图上的所有图片就会都没有加载出来
like:
- image inline把图片转成base64格式,然后内嵌在html中
优点:减少http请求
缺点:代码体量过大
- 使用矢量图使用svg进行矢量图绘制
优点:使用iconfont解决icon问题,与jpg相比,更小,加载更快
缺点:颜色比较单调,复杂的图片不适合使用矢量图
- 在安卓下使用webp
优点:webp压缩率更高,大小更小
缺点:并不所有浏览器兼容
图片压缩网址图片压缩
lazyLoad && preLoad
lazyLoad
lazyLoad原理
页面较长,屏幕的可视区域有限。
不设置页面中img标签的src属性值或者将其指向同一个占位图。
图片的实际地址存在img标签自定义的一个属性中,如:“data-url”。
监听scroll,滚动到某个位置时,动态的将url替换成实际的“data-url”。
从而让页面加载速度快到飞起、减轻服务器压力、节约流量、提升用户体验。
html:
<head>
<meta charset="UTF-8">
<title>Lazyload</title>
<style type="text/css">
.mob-wrap li {
list-style: none;
width: 100%;
height: 600px;
}
</style>
</head>
<body>
<ul class="mob-wrap">
<li>
<img class="tamp-img" alt="loading" data-src="banner1.gif">
</li>
<li>
<img class="tamp-img" alt="loading" data-src="banner1.jpg">
</li>
<li>
<img class="tamp-img" alt="loading" data-src="banner2.gif">
</li>
<li>
<img class="tamp-img" alt="loading" data-src="banner2.gif">
</li>
<li>
<img class="tamp-img" alt="loading" data-src="banner2.gif">
</li>
<li>
<img class="tamp-img" alt="loading" data-src="banner2.gif">
</li>
<li>
<img class="tamp-img" alt="loading" data-src="banner2.gif">
</li>
</ul>
</body>
js
//节流函数
_throttle = (fn, delay, least) => {
var timeout = null,
startTime = new Date();
fn();
return function () {
var curTime = new Date();
clearTimeout(timeout);
if (curTime - startTime >= least) {
fn();
startTime = curTime;
} else {
timeout = setTimeout(fn, delay);
}
}
}
function compare() {
var bodyScrollHeight = document.documentElement.scrollTop;// body滚动高度
console.log(bodyScrollHeight + "替换src方法")
var windowHeight = window.innerHeight;// 视窗高度
var imgs = document.getElementsByClassName('tamp-img');
for (var i = 0; i < imgs.length; i++) {
var imgHeight = imgs[i].offsetTop;// 图片距离顶部高度
if (imgHeight < windowHeight + bodyScrollHeight - 340) {
imgs[i].src = imgs[i].getAttribute('data-src');
imgs[i].className = imgs[i].className.replace('tamp-img', '');
}
}
}
window.onscroll = _throttle(compare, 350, 600);
preLoad
什么是预加载
资源预加载是另一个性能优化技术,我们可以使用该技术来预先告知浏览器某些资源可能在将来会被使用到。预加载简单来说就是将所有所需的资源提前请求加载到本地,这样后面在需要用到时就直接从缓存取资源。
为什么要用预加载
在网页全部加载之前,对一些主要内容进行加载,以提供给用户更好的体验,减少等待的时间。否则,如果一个页面的内容过于庞大,没有使用预加载技术的页面就会长时间的展现为一片空白,直到所有内容加载完毕。
预加载的方法
方法一:使用 jquery 实现预加载
简单实现基本原理
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Page Title</title>
<script src="../jquery.1.10.js"></script>
</head>
<style>
.loading{
position: fixed;top: 0;
left:0;
width: 100%;
height: 100%;
background-color: #eee;
text-align: center;
font-size: 30px;
}
.progress{
margin-top: 300px;
}
.btn.hover{
background-color: #eee;
}
</style>
<body>
<div class="box">
<img src="http://preview.quanjing.com/top034/top-924268.jpg" alt="pic" id="img" height="500">
<p>
<a href="#" class="btn" data-control="prev">上一页</a>
<a href="#" class="btn" data-control="next">下一页</a>
</p>
</div>
<div class="loading">
<p class="progress">0%</p>
</div>
</body>
<script>
var imgs = [
"http://preview.quanjing.com/top034/top-924268.jpg",
"http://preview.quanjing.com/CPS1/152-0111.jpg",
"http://preview.quanjing.com/bld011/bld125797.jpg",
"http://preview.quanjing.com/east025/east-ep-a41-8167880.jpg"
]
var index = 0,
len = imgs.length,
count = 0,
$progress = $('.progress');
$('.btn').on('click',function (params) {
if('prev' === $(this).data('control')){
index = Math.max(0,--index);
}else{
index = Math.min(len -1, ++index)
}
document.title = (index + 1) + '/' +len;
$('#img').attr('src',imgs[index]);
})
$.each(imgs, function (i, src) {
var imgObj = new Image();
$(imgObj).on('load error', function () {
$progress.html(Math.round((count + 1) / len * 100) + "%");
console.log("$progress ", (count + 1) / len * 100);
if (count >= len - 1) {
$('.loading').hide();
document.title = '1/' + len;
}
count++
});
imgObj.src = src;
})
</script>
</html>
方法二:使用封装的 jquery,preLoad.js
//---------preLoad.js
(function($){
function preload(imgs, options) {
this.imgs = (typeof imgs === 'string') ? [imgs] : imgs;
this.opts = $.extend({}, preload.defaults, options);
this._unoredered() //加下划线说明方法是私有
}
preload.defaults = {
each: null,//所有图片加载完成后执行
all: null//所有图片加载完毕后执行
};
preload.prototype._unoredered = function(){ //无序加载
var img = this.imgs,
opts = this.opts,
count = 0,
len = imgs.length;
$.each(imgs, function (i, src) {
debugger;
if(typeof src != 'string') return;
var imgObj = new Image();
$(imgObj).on('load error', function () {
opts.each && opts.each(count);//判断 each方法是否存在
if (count >= len - 1) {
opts.all && opts.all();
}
count++
});
imgObj.src = src;
})
}
$.extend({
preload:function(imgs,opts){
new preload(imgs,opts)
}
})
})(jQuery)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Page Title</title>
<script src="../jquery.1.10.js"></script>
</head>
<style>
.loading{
position: fixed;top: 0;
left:0;
width: 100%;
height: 100%;
background-color: #eee;
text-align: center;
font-size: 30px;
}
.progress{
margin-top: 300px;
}
.btn.hover{
background-color: #eee;
}
</style>
<body>
<div class="box">
<img src="http://preview.quanjing.com/top034/top-924268.jpg" alt="pic" id="img" height="500">
<p>
<a href="#" class="btn" data-control="prev">上一页</a>
<a href="#" class="btn" data-control="next">下一页</a>
</p>
</div>
<div class="loading">
<p class="progress">0%</p>
</div>
</body>
<script src="./preLoad.js"></script>
<script>
var imgs = [
"http://preview.quanjing.com/top034/top-924268.jpg",
"http://preview.quanjing.com/CPS1/152-0111.jpg",
"http://preview.quanjing.com/bld011/bld125797.jpg",
"http://preview.quanjing.com/east025/east-ep-a41-8167880.jpg"
],
$progress = $('.progress'),
len = imgs.length;
$.preload(imgs,{
each:function(count){
console.log(count);
$progress.html(Math.round((count + 1) / len * 100) + "%");
},
all:function(){
$('.loading').hide();
document.title = '1/' + len;
}
})
var index = 0;
$('.btn').on('click',function (params) {
if('prev' === $(this).data('control')){
index = Math.max(0,--index);
}else{
index = Math.min(len -1, ++index)
}
document.title = (index + 1) + '/' +len;
$('#img').attr('src',imgs[index]);
})
</script>
</html>
方法三:使用PreloadJS库来管理和协调相关资源加载的类库,它可以方便的帮助你预先加载相关资源
对象:
- 图片
- css/js文件
- 音频/视频
- 数据
- 其他
PreloadJS提供了一种预加载内容的一致方式,以便在HTML应用程序中使用。预加载可以使用HTML标签以及XHR来完成。默认情况下,PreloadJS会尝试使用XHR加载内容,因为它提供了对进度和完成事件的更好支持,但是由于跨域问题,使用基于标记的加载可能更好。
它使用了XHR2来提供实时的加载进度信息,如果不支持则使用标签式的简化进度来实现进度展示,支持支持多队列,多连接,暂停队列
在PreloadJS中,LoadQueue是主要用来预加载内容的API,LoadQueue是一个加载管理器即:可以预先加载一个文件或者一个文件队列
初始化加载管理器:(创建队列)
var preload = new createjs.LoadQueue(true);
传递的参数是boolean类型,false则强制使用标签式的加载,LoadQueue支持的相关的文件:
- BINARY: XHR调用的二进制文件
- TEXT: 文本文件 - 仅支持XHR
- CSS: CSS文件 IMAGE: 一般图片文件格式
- JAVASCRIPT: JavaScript文件
- JSON: JSON数据
- XML: XML数据
- JSONP: 跨域JSON文件
- SOUND: 音频文件
- VIDEO: 视频文件
- SVG: SVG文件
- MANIFEST: JSON格式的文件列表
LoadQueue包含了以下几个常用的监听事件:
- complete: 当队列完成加载所有文件时。
- error: 当队列与任何文件遇到错误时。
- progress: 对于整个队列进展已经改变。
- fileload: 单个文件已完成加载。
- fileprogress: 单个文件进度变化。注意,只有文件装载XHR(或可能通过插件)将 file 事件进展除了0或100%。
举个例子:
html
<body>
<div class="loading">
<div class="progress"></div>
</div>
<img src="images/ts.jpg" style="display: none"/>
<img src="images/ts.jpg" style="display: none"/>
<img src="images/ts.jpg" style="display: none"/>
<img src="images/ts.jpg" style="display: none"/>
<img src="images/ts.jpg" style="display: none"/>
<img src="images/ts.jpg" style="display: none"/>
<img src="images/ts.jpg" style="display: none"/>
<script src="js/jquery-3.3.1.js"></script>
<script src="js/preloadjs.js"></script>
</body>
css
<style>
* {
margin: 0;
padding: 0;
}
.loading {
display: table;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #00ffb1;
}
.loading > .progress {
display: table-cell;
vertical-align: middle;
text-align: center;
}
</style>
js
<script>
var preload;
var mainfest;
// 初始化预加载manifest清单
function setupManifest(){
mainfest = [
{ src:"images/ts.jpg"} , { src:"images/ts.jpg"},
{ src:"images/ts.jpg"} , { src:"images/ts.jpg"},
{ src:"images/ts.jpg"} , { src:"images/ts.jpg"},
{ src:"images/ts.jpg"} , { src:"images/ts.jpg"},
{ src:"images/ts.jpg"} , { src:"images/ts.jpg"},
{ src:"images/ts.jpg"} , { src:"images/ts.jpg"},
{ src:"images/ts.jpg"} , { src:"images/ts.jpg"}
];
}
// 预加载函数
function startPreload() {
preload = new createjs.LoadQueue(true);
//注意加载音频文件需要调用如下代码行
preload.installPlugin(createjs.SOUND);
//为preloaded添加整个队列变化时展示的进度事件
preload.addEventListener("progress",handleFileProgress);
//为preloaded添加当队列完成全部加载后触发事件
preload.addEventListener("complete",loadComplete);
//设置最大并发连接数 最大值为10
preload.setMaxConnections(1);
preload.loadManifest(mainfest);
}
// 当整个队列变化时展示的进度事件的处理函数
function handleFileProgress(event) {
$(".progress").text("loading..."+Math.ceil(event.loaded * 100)+ "%");
console.log(event.loaded);
}
// 处理preload添加当队列完成全部加载后触发事件
function loadComplete() {
// 设置定时器,当全部加载完毕后让100%停留0.4秒,提高用户体验,不至于让用户感觉不到
window.setTimeout(()=>{
$(".loading").remove();
},400);
}
setupManifest();
startPreload();
</script>
防抖与节流
话不多说直接上代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>document</title>
</head>
<body>
没有做防抖与节流的input:<input type="text" id="unDebounce">
使用防抖后的input:<input type="text" id="useDebounce">
使用节流后的input:<input type="text" id="throttle">
</body>
<script type="text/javascript">
function ajax(content) {
console.log('ajax request ' + content);
};
// 没有使用防抖
let input = document.getElementById('unDebounce');
input.addEventListener('keyup', function(e) {
ajax(e.target.value)
});
// 使用了防抖函数
function debounce(ajax, delay) {
var timer = null;
return function() {
var _this = this,
_arg = arguments; //谁调用此方法,this就指向谁
clearTimeout(timer);
timer = setTimeout(function() {
ajax.apply(_this, _arg);
}, delay);
}
}
var useDebounce = document.getElementById('useDebounce');
useDebounce.onkeyup = debounce(ajax, 500);
// 使用节流函数
function throttle(handle, wait) {
var lastTime = 0;
return function () {
//new Date().getTime()从1970到当前所过去的时间(毫秒)
var nowTime = new Date().getTime();
//时间差大于等于等待时间,就去执行函数
if (nowTime - lastTime >= wait) {
handle.apply(this, arguments);
lastTime = nowTime;
}
}
}
// function throttle(fun, delay) {
// let last, deferTimer
// return function(args) {
// let that = this
// let _args = arguments
// let now = +new Date()
// if (last && now < last + delay) {
// clearTimeout(deferTimer)
// deferTimer = setTimeout(function() {
// last = now
// fun.apply(that, _args)
// }, delay)
// } else {
// last = now
// fun.apply(that, _args)
// }
// }
// }
let throttleAjax = throttle(ajax, 1000)
let inputc = document.getElementById('throttle')
inputc.addEventListener(
'keyup',
function(e) {
throttleAjax(e.target.value)
})
</script>
</html>
来看看结果:
一、防抖
函数防抖就是函数在频繁需要触发情况时,只有等足够空闲的时间才去执行一次。好像公交车司机等人都上车以后才出站一样。
比如我们在做搜索框的时候,要根据搜索的内容进行请求查找出相关的内容,此时我们在给输入框绑定oninput事件而不做防抖处理的话,每输入一个字就会进行一次处理。比如我想搜索abc,那就会输入a的时候触发请求数据一次,ab的时候触发请求数据一次,然后才是abc,但往往前两次是不需要的,而且频繁的进行请求也会影响性能,此时我们就需要进行防抖处理。
二、节流
函数节流就是预定一个函数只有在大于等于执行周期的时候才去执行,周期内调用不执行。就好像水滴攒到一定程度才会落下。主要应用于:抢购疯狂点击、页面滚动等。
区别:
好文:
JavaScript专题之跟着 underscore 学防抖
JavaScript专题之跟着 underscore 学节流
加快请求速度
DNS Prefetching(DNS与解析)
预解析的实现:
- 用meta信息来告知浏览器, 当前页面要做DNS预解析:
<meta http-equiv="x-dns-prefetch-control" content="on" />
- 在页面header中使用link标签来强制对DNS预解析:
<link rel="dns-prefetch" href="http://bdimg.share.baidu.com" />
<link rel="dns-prefetch" href="http://nsclick.baidu.com" />
<link rel="dns-prefetch" href="http://hm.baidu.com" />
<link rel="dns-prefetch" href="http://eiv.baidu.com" />
详细可以查看:https://www.cnblogs.com/rongfengliang/p/5601770.html
并行加载与顺序执行
先来做个小小的实验,用来检测加载顺序
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>并行加载测试</title>
</head>
<body>
<h1>并行加载测试</h1>
<img src="./static/bg.7e6b5849.png"/>
<img src="./static/pic04.ceecbd75.png"/>
<img src="./static/pic05.1350807e.png"/>
<img src="./static/pic06.5b13bcd5.png"/>
<script src="./index.js?v=5bb0ca3"></script>
<script src="./test.js?v=6025288"></script>
<script src="./test2.js?v=04a2079"></script>
<script src="./test3.js?v=85ca761"></script>
</body>
</html>
加载顺序
结论
- 图片、js、css都属于静态资源,可以并行下载。
- 浏览器并不是严格地按照顺序下载静态资源,它会根据优先级来安排下载顺序。这个测试案例里,pic05.1350807e.png、pic06.5b13bcd5.png并没有先于js下载,chrome认为js的优先级高于图片(js的阻塞特性)。
- http 1.1协议下,chrome的并行下载数为6。
为了改善上面的阻塞情况,应尽可能的减少页面中script标签的出现次数,这同时也是考虑到HTTP 请求会带来额外的性能开销,也就是说应减少页面中外链脚本的数量。
你可以手动合并你的多个JS 文件,也可采用类似Yahoo! combo handler 这样的实时在线服务来实现,例如下面的这个script标签实际上便载入了3个JS 文件:
还可以参考:https://blog.csdn.net/weixin_34406086/article/details/93198592
<html>
<head>
<title>无标题文档</title>
<link rel="stylesheet" type="text/css" href="styles.css" />
</head>
<body>
<p>页面的内容。。。</p>
<!-- 推荐的位置,页面底部: -->
<script type="text/javascript" src="http://yui.yahooapis.com/combo?file1.js&file2.js&file3.js"></script>
</body>
</html>
无阻塞的脚本:
为了阻塞状况,这里提供了几个实现并行下载JS 脚本的方案。
- 延迟的脚本
HTML4 为script标签定义了一个defer 属性,它能使这段代码延迟执行,然而该属性只有IE4+ 和Firefox 3.5+ 支持。声明了defer 属性的script会在DOM加载完成,window.onload 事件触发前被解析执行:
<script type="text/javascript" src="file1.js" defer></script>
- 动态脚本元素
这是最通用的解决方案,通过DOM 动态地创建script元素并插入到文档中,文件在该元素被添加到页面时开始下载,这样 无论在何时启动下载,文件的下载和执行过程不会阻塞页面其他进程。
不过要注意使用这种方式加载的代码会立刻执行,这样需清楚的了解各文件的作用以及合理的执行顺序,此时跟踪并确保脚本下载完成并准备就绪是很有必要的,非IE浏览器会在script元素接收完成时触发一个load 事件,而IE 下则会触发一个readystatechange 事件并通过readyState 属性加以判断便可。以下是兼容地动态加载一个JS 脚本的函数:
function load_script(url, callback) {
var script = document.createElement('script');
script.type = 'text/javascript';
if (script.readyState) { //IE
script.onreadystatechange = function() {
if (script.readyState == 'loaded' || script.readyState == 'complete') {
script.onreadystatechange = null;
callback();
}
}
} else { //others
script.onload = function() {
callback();
}
}
script.src = url;
document.getElementsByTagName('head')[0].appendChild(script);
}
你可以将这个函数保存至一个load_script.js 文件,然后用该函数来加载其他的脚本,当要加载多个脚本时,为了确保正确的加载顺序,可以将load_script() 的执行串联起来,最后如前面说到的放至页面的底部,这便是比较完美的解决方案了:
<script type="text/javascript" src="load_script.js"></script>
<script type="text/javascript">
load_script('file1.js', function() {
load_script('file2.js', function() {
load_script('file3.js', function() {
//全部载入后的操作...
} );
} );
} );
</script>
- XMLHttpRequest 脚本注入
即通过AJAX 方式加载,不过这种方式无法实现跨域加载,不适用于大型网站。
CDN内容分发
https://www.cnblogs.com/ming-blogs/p/10799877.html
缓存
好文:https://www.cnblogs.com/laixiangran/p/8971119.html