利用 JavaScript Profiler 分析 Vue 效能問題

FatGe發表於2019-01-04

最近在開發一套元件庫,期間在實現InputNumber 元件時候碰到一個詭異卡頓的現象,用了時間來排除這個問題,涉及到一些問題定位的方法,記錄下來已備後用。


1. 發現問題

在實現 InputNumber 元件的時候,有一個功能是按住 + 或 – 按鈕時,元件的值在不斷的自增或者自減,具體如下圖

利用 JavaScript Profiler 分析 Vue 效能問題

當元件的值自增到一定數量之後,元件會開始卡頓,並且頁面上下滾動也會有明顯的延遲。

問題體驗

相關程式碼

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 的時間會不斷地增長。

利用 JavaScript Profiler 分析 Vue 效能問題

接下來要判斷具體是哪一句js導致整個頁面的 update 時間不斷地變長,利用 Chrome 的 JavaScript Profiler 來完成該工作。開啟開發者工具

利用 JavaScript Profiler 分析 Vue 效能問題

利用這個皮膚你可以追蹤網頁程式的記憶體洩漏問題,進一步提升程式的JavaScript執行效能,點選Start 按鈕,然後去復現剛才的操作,得到結果如下

利用 JavaScript Profiler 分析 Vue 效能問題

圖中標識處有三個模式:

  • Chart 按時間先後順序顯示的火焰圖;
  • Heavy(Bottom Up) 根據對效能的消耗影響列出所有的函式,並可以檢視該函式的呼叫路徑;
  • Tree(Top Down) 從呼叫棧的頂端(最初呼叫的位置)開始,顯示呼叫結構的總體的樹狀圖情況。

選擇 Tree(Top Down) 模式,得到結果如下

利用 JavaScript Profiler 分析 Vue 效能問題

可以看出 flushCallbacksvue 函式佔用了74.66%的 Total Time,所以需要對它進行分析

利用 JavaScript Profiler 分析 Vue 效能問題

在它的呼叫棧中,關鍵的一步是 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);

});

}
});
複製程式碼

原創宣告: 該文章為原創文章,轉載請註明出處。

來源:https://juejin.im/post/5c2ebae16fb9a049be5d9e49

相關文章