利用Chrome優化頁面

背景

咱們公司的內部系統中,有一個配置組件(如圖所示),功能爲實現左邊勾選節點,右邊顯示所選節點的交互列表(這裏和下面都作了字段脫敏處理,因此看起來沒什麼意義,但功能是同樣的)。node

卡頓例子

上線後,一直風平浪靜,可是有一天,測試數據庫被導入了一批1.8萬的數據,而後致使這個組件的所在頁面出現了下面的性能問題。git

  • 進入組件所在頁面,初始化嚴重卡頓;
  • 勾選一個節點要等上十幾二十秒;
  • 全選或取消全選甚至等上一分多鐘;

經過Chrome Performance 定位性能問題

錄製

  1. 打開Chrome控制檯,點擊performance標籤(下圖所示),備用;
  2. 復現會引發頁面性能問題的動做;
  3. 點擊左上角的錄製按鈕,開始錄製;
  4. 錄製時間取決於你的頁面須要監控的時間段
  5. 中止錄製,會出現一個十分複雜的頁面。

定位耗時代碼

這裏錄製了列表點擊全選時的時間段(以下圖所示)。首先這裏作個說明:performance能夠分析內存、CPU、FPS等好多參數,咱們這裏主要是頁面等待過久了,而這個取決於FPS(頁面每秒幀數,fps < 24 會讓用戶感受到卡頓,由於人眼的識別主要是24幀)。github

咱們按着圖上序號順序解釋:chrome

  1. FPS區域出現了紅條,意味着幀數已經降低到影響用戶體驗的程度,chrome已經幫你標註了這塊是有問題;
  2. Main區域展現的是火焰圖,也就是函數調用的堆棧(火焰圖,能夠簡單理解,x軸表示時間,y軸表示調用的函數,函數中還包含依次調用的函數,y軸只佔用x軸的一個時間維度),點擊灰色條會出現圖中3的紅色區域。
  3. Summary區域顯示了Warning,長時間任務耗時了1.6min,其中腳本Scripting佔絕大部分耗時。

這時,咱們已經知道有一個耗時很長的任務了,咱們繼續深究究竟是哪一些代碼形成的:數據庫

  1. 咱們在函數棧從上往下找(以下圖所示),找到該函數佔用的時間(Self Time)很大時,問題就出來這裏了,Chrome還標出是哪一個文件出來。

  1. 點擊耗時文件,Chrome會爲咱們標出耗時長的代碼(下圖所示)

分析與優化

經過Chrome,咱們已經定位出問題代碼了,接下來就是分析問題的緣由了。數組

這裏列出有性能問題的代碼塊:bash

...
computed: {
    treeData () {
        const tree = this.formmatData(this.data);
        return tree;
    },
    ...
},
methods: {
    formmatData (origin, rootId = 0, result = {}) {
        // 1.找出根節點數組
        const rootNode = origin.filter(node => node.fatherModuleId === rootId);
        if (rootNode.length > 0) {
            // 2.對根節點數組進行遍歷賦值,並放到傳入result的children中
            result.children = rootNode.map(node => ({
                title: node.moduleName,
                id: node.moduleId,
                checked: origin.filter(n => n.fatherModuleId === node.moduleId).length < 1 && this.checked.includes(node.moduleId),
                expand: true,
                disableCheckbox: !this.editable || this.disabledList.includes(node.moduleId),
            }));
            //3. 遍歷根節點數組,遞歸調用該方法找下一級的節點
            result.children.forEach((child) => {
                this.formmatData(origin, child.id, child);
            });
        }
        // 4.返回當前的result的孩子
        return result.children;
    },
    ...
}
複製代碼

其中this.data數據格式爲:函數

[
    {
        "moduleId": 1,
        "moduleName": "節點1",
        "fatherModuleId": 0
    },
    {
        "moduleId": 2,
        "moduleName": "節點2",
        "fatherModuleId": 0
    },
    {
        "moduleId": 3,
        "moduleName": "節點3",
        "fatherModuleId": 0
    },
    ...
]
複製代碼

這裏先說明一下,該段代碼作了什麼事情吧:post

  1. 首先,數據data包含三個字段moduleIdfatherModuleIdmoduleName,其中moduleId是模塊ID,fatherModuleId是模塊上一級的ID。
  2. 其次,經過fromData方法計算出treeData,而formmatData作的就是遞歸調用,構造出樹狀數據。

可是,就是由於裏面出現了遞歸,循環的嵌套致使了性能問題,經過結合業務,優化作了如下操做:性能

  • 減小循環嵌套,
  • 增長條件
  • 減小循環次數
  • 利用set來優化查找

優化後的代碼:

computed: {
    checkedSet () {
        return new Set(this.checked);
    },
    ...
}
methods: {
    formmatData (origin, rootId = 0, result = {}) {
        const otherNode = [];
        result.children = [];
        // 1. 整合本來代碼的過濾和遍歷賦值,減小一次循環
        for (const node of origin) {
            if (node.fatherModuleId === rootId) {
                result.children.push({
                    title: node.moduleName,
                    id: node.moduleId,
                    checked: this.checkedSet.has(node.moduleId), // 利用`set`來優化查找
                    expand: true,
                    disableCheckbox: !this.editable || this.disabledList.includes(node.moduleId),
                });
            } else {
                otherNode.push(node);
            }
        }
        // 2.在剩下的節點去查找子節點,而不是重複在所有數據中查找,由於當前的節點數組已經找到
        if (otherNode.length > 0) {
            result.children.forEach((child) => {
                if (child.checked) {
                    // 3.利用find來代替filter
                    child.checked = !otherNode.find(n => n.fatherModuleId === child.id);
                }
                this.formmatData(otherNode, child.id, child);
            });
        }
        return result.children;
    },
    ...
}
複製代碼

效果

通過一番定位耗時問題,優化代碼邏輯後,先後時間對好比下:

總結

  • Chrome Performance是個很好的定位問題利器,方便快速找到問題代碼;
  • 平時寫代碼時,特別循環遞歸,若是考慮日後會有數據大的狀況,就儘可能用少的循環去完成功能;
  • ES6的新數據類型set在查找方面,數據量很大時性能大幅高於數組,推薦使用。

參考

使用Chrome分析頁面性能問題

如何使用 Set 來提升代碼的性能

相關文章
相關標籤/搜索