最近在開發一套元件庫,期間在實現InputNumber 元件時候碰到一個詭異卡頓的現象,用了時間來排除這個問題,涉及到一些問題定位的方法,記錄下來已備後用。
1. 發現問題
在實現 InputNumber 元件的時候,有一個功能是按住 + 或 - 按鈕時,元件的值在不斷的自增或者自減,具體如下圖
當元件的值自增到一定數量之後,元件會開始卡頓,並且頁面上下滾動也會有明顯的延遲。
2. 定位問題
<script>
export default {
name: "input-number",
...
methods: {
handleClick(type) {
const { step } = this;
const period = 10;
const timerHandle = () => {
const { addDisabled, decDisabled } = this;
if (!addDisabled && type === "add") this.inputNumberValue += step;
if (!decDisabled && type === "dec") this.inputNumberValue -= step;
};
const timer = setInterval(timerHandle, period);
const startTime = new Date();
const handler = () => {
const endTime = new Date();
if (endTime - startTime < period) timerHandle();
clearInterval(timer);
document.removeEventListener("mouseup", handler, false);
};
document.addEventListener("mouseup", handler, false);
}
...
};
</script>
複製程式碼
首先定位問題發生的位置,直觀上感受應該是點選之後不無端自增發生的卡頓,對應程式碼中的 handleClick
函式,它將 click
事件分為 mousedown
以及 mouseup
,當觸發 mousedown
事件時候,呼叫一個 setInterval
定時執行元件值變化的函式。
初步定位問題應該就發生在 timerHandle
之後,當 inputNumberValue
發生變化之後,它會按照一定的規則來改變 inputValue
的值,從而觸發 $emit(input, this.inputValue)
來完成 v-model
。
computed: {
inputNumberValue: {
get() {
return this.inputValue;
},
set(value) {
// ...一定規則
this.inputValue = limits.find(limit => limit.need(value)).value;
}
}
},
watch: {
value: {
handler(newVal) {
console.timeEnd()
this.inputNumberValue = newVal;
},
immediate: true
},
inputValue(newVal) {
this.$emit("input", newVal);
}
}
複製程式碼
利用 console.time
以及 console.timeEnd
來排查,那一步發生的卡頓,檢測整個 v-model
變化的流程。
也就是在 timerHandle
以及 watch value handler
內新增 console.time
以及 console.timeEnd
,具體如下
const timerHandle = () => {
const { addDisabled, decDisabled } = this;
if (!addDisabled && type === "add") this.inputNumberValue += step;
if (!decDisabled && type === "dec") this.inputNumberValue -= step;
console.time();
};
watch: {
value: {
handler(newVal) {
console.timeEnd()
this.inputNumberValue = newVal;
},
immediate: true
}
}
複製程式碼
然後執行,發現執行時間是在不斷地增加的,這時候問題的可以歸類為,inputNumber 元件的值在不斷地變動,導致的 update
的時間會不斷地增長。
接下來要判斷具體是哪一句js導致整個頁面的 update
時間不斷地變長,利用 Chrome 的 JavaScript Profiler 來完成該工作。開啟開發者工具
利用這個皮膚你可以追蹤網頁程式的記憶體洩漏問題,進一步提升程式的JavaScript執行效能,點選Start 按鈕,然後去復現剛才的操作,得到結果如下
圖中標識處有三個模式:
- Chart 按時間先後順序顯示的火焰圖;
- Heavy(Bottom Up) 根據對效能的消耗影響列出所有的函式,並可以檢視該函式的呼叫路徑;
- Tree(Top Down) 從呼叫棧的頂端(最初呼叫的位置)開始,顯示呼叫結構的總體的樹狀圖情況。
選擇 Tree(Top Down) 模式,得到結果如下
可以看出 flushCallbacksvue
函式佔用了74.66%的 Total Time,所以需要對它進行分析
在它的呼叫棧中,關鍵的一步是 Vue._update
,它的主要功能是將 Vnode
渲染成真實DOM,所以上述的卡頓問題果然出現在渲染這一步。
繼續分析,發現主要問題在與 updateDirctives
這個函式內,看來問題和指令的更新相關。
最後,發現原來是 highlightBlock
的鍋,因為要完成頁面中程式碼高亮的需求,開發了一個指令
import hljs from 'highlight.js/lib/highlight';
Vue.directive ('highlight', function (el) {
let blocks = el.querySelectorAll ('code');
Array.prototype.forEach.call (blocks, block => {
hljs.highlightBlock (block);
});
});
複製程式碼
當 InputNumber 元件 v-model
所繫結的父元件 data
變動時候,會導致 v-highlight
指令不斷地更新,使得頁面卡頓。
3. 解決問題
只需要將該指令的高亮程式碼的函式寫在 bind
裡面,這樣就只呼叫一次,指令第一次繫結到元素時呼叫。
Vue.directive ('highlight', {
bind (el) {
let blocks = el.querySelectorAll ('code');
Array.prototype.forEach.call (blocks, block => {
hljs.highlightBlock (block);
});
}
});
複製程式碼
原創宣告: 該文章為原創文章,轉載請註明出處。