利用 JavaScript Profiler 分析 Vue 性能問題

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


1. 發現問題

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

當組件的值自增到必定數量以後,組件會開始卡頓,而且頁面上下滾動也會有明顯的延遲。vue

問題體驗java

相關代碼node

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 定時執行組件值變化的函數。git

初步定位問題應該就發生在 timerHandle 以後,當 inputNumberValue 發生變化以後,它會按照必定的規則來改變 inputValue 的值,從而觸發 $emit(input, this.inputValue) 來完成 v-modelgithub

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

接下來要判斷具體是哪一句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);
        });
    }
});
複製代碼

原創聲明: 該文章爲原創文章,轉載請註明出處。

相關文章
相關標籤/搜索