曾經在前端之巔看過這么一篇文章,標題是《網站性能優化實戰——從12.67s到1.06s的故事》
,當時我就納悶了,哪來的網站首屏需要加載12秒,而且怎么做到這么極致的優化的?
果然過了幾天,接手了一份祖傳代碼
,運行后發現項目奇卡,渲染也不是一般的慢,除了谷歌和火狐,其他瀏覽器一律崩潰。
亞馬遜的工程師形容說他們的祖傳代碼:“一座很大的屎山,你見過的最大的山,每次你想修正一個bug,你的工作就是爬到屎山的正中心去
原項目是混合在php項目中的,我的任務是抽離并優化
它,瞬間覺得很有挑戰了。
略過具體的抽離過程,直接其中一個優化關鍵點,就是有多個頁面都使用element-ui的el-table
組件進行數據渲染,發現當需要渲染超過1K+
的數據的時候頁面渲染需要很久,查看控制臺發現渲染時間占用10S
之久。
查看el-table
源碼發現內部使用的div來實現table布局,導致產生了大量的dom節點,又由于業務需要無法采用分頁,所以當務之急是開發一個無限滾動、滾動加載的組件,而該組件的核心的無限滾動實現邏輯,而且在下拉框中也需要滾動加載,所以干脆抽離出一個無限滾動指令
。
主要思路是創建三個指令
無限滾動指令 | 參數 | 描述 |
---|---|---|
v-infinite-scroll | callback:function | 滾動到指定底部后觸發的事件 |
v-infinite-scroll-distance | px:number | 距離底部多遠觸發事件 |
v-infinite-scroll-disabled | busy:bool | 當前是否為忙碌狀態 |
<div v-infinite-scroll="scrollLoad" v-infinite-scroll-disabled="busy" v-infinite-scroll-distance="250">
...
</div>
infinite-scroll-distance
和infinite-scroll-disabled
指令分別設置目標標簽的data-infiniteScrollDistance
和data-infiniteScrollDisabled
屬性。
infinite-scroll
指令在目標標簽上注冊滾動事件,但達到data-infiniteScrollDistance
高度和data-infiniteScrollDisabled
為true時,觸發回調。(回調中應該填充目標標簽內容,使得高度變化,觸發下一輪滾動事件)
下面是具體實現
InfiniteScrollDisabled.js
// directives/InfiniteScrollDisabled.js
export default {
bind(el, binding) {
el.setAttribute('data-infiniteScrollDisabled', binding.value);
},
update(el, binding) {
el.setAttribute('data-infiniteScrollDisabled', binding.value);
},
};
InfiniteScrollDistance.js
// directives/InfiniteScrollDistance.js
export default {
bind(el, binding) {
el.setAttribute('data-infiniteScrollDistance', binding.value);
},
update(el, binding) {
el.setAttribute('data-infiniteScrollDistance', binding.value);
},
};
InfiniteScroll.js
// directives/InfiniteScroll.js
export default {
bind(el, binding) {
let target = el;
if (el.getAttribute('data-scrollTarget')) {
target = el.querySelector(el.getAttribute('data-scrollTarget'));
}
target.addEventListener('scroll', e => {
const busy = el.getAttribute('data-infiniteScrollDisabled') ? false : true;
const distance = el.getAttribute('data-infiniteScrollDistance')
? Number.parseInt(el.getAttribute('data-infiniteScrollDistance'), 10)
: 100;
if (!busy && e.target.scrollHeight - (e.target.scrollTop + e.target.clientHeight) < distance) {
el.setAttribute('data-infiniteScrollDisabled', true);
binding.value(e);
}
});
},
};
這里有一個點需要注意,這里接受一個data-scrollTarget
屬性,代表綁定滾動事件的元素,默認是當前元素,如果需要指定為內部的某個元素可以使用該屬性。
到這里無限滾動指令邏輯已經結束了,我們再創建一個文件,用于統一導出指令,并讓vue項目通過vue.use(...)
的形式使用。
// index.js
import InfiniteScroll from './directives/InfiniteScroll';
import InfiniteScrollDisabled from './directives/InfiniteScrollDisabled';
import InfiniteScrollDistance from './directives/InfiniteScrollDistance';
export default {
install(Vue, options) {
const directives = {
InfiniteScroll,
InfiniteScrollDisabled,
InfiniteScrollDistance
};
Object.keys(directives).forEach(key => {
directives[key].$options = options;
Vue.directive(key, directives[key]);
});
},
};
導致index.js后,直接用vue.use(...)
,將會觸發導出的install方法,并傳入vue實例和相關參數。
當然也可以發布到npm上,具體發布流程這里暫不介紹。
細心的朋友可能已經發現了,這里寫的是es6代碼,在不兼容的瀏覽器上將無法使用,我們可以全部改回es5,但是,我建議使用
babel
進行轉譯
。
首先我們先安裝babel-cli
和babel-preset-es2015
$ npm install babel-cli babel-preset-es2015 --save-dev
然后添加一個轉譯命令
// package.json
"scripts": {
"build": "babel ./src --presets babel-preset-es2015 --out-dir ./lib"
},
把./src下的全部文件轉譯成es5到./lib文件夾下。
執行打包轉譯
$ npm run build
后面的任務就是通過該無限滾動指令指令封裝無限滾動組件,請看后續文章。