前端點滴(前端優化)---傾盡所有
前端優化方案
降低請求量:合併壓縮資源,減少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