橫向對比Element-tree、ztree、ivew-tree性能對比分析與源碼修改自定義組件

1、前言

公司的樹業務複雜的一批,並且數據量十分大,動不動測試就說最少測上w條數據,我頂個狒狒。php

公司的中後臺系統用的是Element-UI框架,天然樹組件也就是用的el-tree。css

說實話,el-tree已經很不錯了,提供豐富的回調事件,屬性,方法,在輕量數據渲染上能夠說是不二之選。html

可是奈何半年多的項目實踐,愈加以爲el-tree卡頓了起來,數據愈來愈多,功能愈來愈複雜。vue

測試提這方面的BUG已經到了測必提的境界,優化樹組件性能勢在必行!!!node

尋找一條通往光明的道路。jquery

2、能夠勝任上W條數據渲染的Tree

2.1 vue-giant-tree

因而擅長找資源的我,找啊找,咦,找到一個很霸氣的傢伙:git

你看它牛的,輕鬆實現海量數據的高性能渲染。github

關鍵詞:輕鬆、海量、高性能面試

還解釋了爲何要用這玩意:json

來來來,有不服氣嗎? ===》 big-tree.json

彭大師表示不服,安排!

首先把這個號稱big-tree的json下載下來看看是真是假!!

大小

內容

一頓git clone操做,先是yarn,而後serve跑起來,F12按一按。

肉眼看:加載的很是快!眨眼之間!

看下dom加載:

???

確實牛逼啊!!!

可是這個vue-giant-tree就像它本身說的那樣,僅僅是套了一層ztree的殼,修改下皮膚,全部的功能都是ztree的,因此我去ztree官網仔細看了一下文檔。

仔細研究後,發現ztree確實牛逼!!能夠在IE6運行且性能十分棒,可是它的icon圖標十分的簡陋,並且採用是的雪碧圖方式,用絕對定位去獲取每一個圖標,這點不符合咱們公司的業務需求,我們要的是好看的,漂亮的,可隨時更換的圖標。

公司的UI小姐姐,說提供一份位置同樣大小同樣的雪碧圖有難度,不必定能搞出來。

因而我打消了這個念頭,決定修改ztree源碼採用iconfont形式去得到靈活且豐富的icon配置。

2.2 來來來,站在巨人的肩頭上

牛頓曾言:我之因此這麼成功,是由於我站在巨人的肩膀上!

我彭大師現在腳踩vue-giant-tree和ztree,產出了能夠勝任業務複雜又要海量數據渲染的vue-magic-tree!

噹噹噹!!!

亮眼表現:

  • 1.賣相十佳,採用vue-giant-tree的漂亮皮膚。
  • 2.提供iconfont圖標庫,任意渲染任意節點類型的圖標。

隨機渲染多種icon圖表

這樣,這顆參天大樹就知足了業務複雜程度爲O(n * n)的業務了。

介紹如下業務:

  • 1.默認展開某個分支的某個節點下的全部節點。(一句話說,我想要這個展開,那個不展開,這個展開到多少層,那個又多少層,項目經理吃屎)
  • 2.每種節點類型都要有相應的icon圖標,有的節點不須要。
  • 3.渲染上w條數據不能有「正在刷新的狀態」(項目經理吃屎)
  • 4.還有一系列要求,我就不展現了。

光上上面3點,el-tree退出遊戲,i-tree退出遊戲。

有的童鞋可能會說,el-tree和i-tree都有提供自定義渲染功能,也能實現icon圖標自定義啊。這樣說的同窗你能夠試試,數據量多的時候,有多卡。並且通篇文章只比較初始化加載時間,根本沒有比較勾選事件。咱們公司的el-tree在3000條數據而後點擊全選(點擊根節點的checkbox)就要卡2-3秒,每次取消、勾選、取消、勾選,卡的一匹。

不相信的話,我只能說你本身體會。

接下來看性能對比測試,不想看的同窗能夠點擊直達源碼修改章節

3、擺個擂臺挑戰下(初始化渲染樹)

規則說明:

1.一樣一份文件,本地加載(ivew的節點名稱必須爲title, 因此iview那份數據中的name都改爲了title, 看我github倉庫代碼能夠看到iview加載的不是全局這份數據,可是是同樣的。)

const bigData = require("@/mock/big-tree.json");
複製代碼

2.一樣適用清空緩存從新刷新。

3.gif一樣的位置,大小,比率。

保證了一致性,能夠明顯看出,iview和element加載明顯有卡頓,而ztree幾乎沒有延遲。

線上測試地址:測試 (海外服務器加載可能比較慢)

3.1 我尊貴Vip iview表示不服!

3.2 我平頭哥element表示不服!

3.3 Ztree 牛逼

3.4 結果分析

牛批!!

我要說下這份數據是一個一維的數組,ztree根據pid自動拼接成一顆樹形結構的數據而後渲染,還這麼出色!!!

並且ztree的功能異常豐富,能夠知足你一切須要,你要本身看文檔,結合你的業務去開發。

我在這裏只是想說ztree牛逼!!!

4、對ZTree動手動腳

嘿嘿,油頭蓬面大叔來了

十八禁哈

既然知道這顆樹如此優秀核心仍是在於ztree,那麼我們就把它的開源代碼下載下來,搞起來!!!

這就是ztree的源碼(JQuery zTree v3.5.41)

能夠看到目錄結構很簡單,實現ztree的都在這個js文件夾中。(因此只聚焦關注這個文件夾)

4.1 從命名分析

一共十一個文件,(按順序)第一個是jquery的min版(無論不看)。而後粗略看了眼,帶min的文件都給它忽略掉!

這樣就剩下5個文件:

jquery.ztree.all.js
jquery.ztree.core.js
jquery.ztree.excheck.js
jquery.ztree.exedit.js
jquery.ztree.exhide.js
複製代碼

從命名上看,很清晰了其實:

  • all應該是全部功能的集合。
  • core就是核心功能。
  • ex開頭的都是擴展功能。

由於引入項目的是all文件,其實只看all就好了哈。(all包含了core和ex的全部)

動手!!!

由於我們須要改變它陳舊古老的icon節點,因此從文檔入手

咱們能夠找到控制節點圖標的功能設置是經過一個叫作setting.view.showIcon變量控制的

setting.view.showIcon: 控制節點圖標顯示與否。

那麼我們就看看這玩意在源碼中是怎麼處理邏輯的,從而找到突破口。

4.2 從全局看

打開all文件:其實裏面備註什麼都很詳細和完善。

定義了:

  • _consts常量
  • _setting默認配置對象
  • 一些初始化方法_init*,
  • 事件綁定和解綁(_bingEvent、_unbindEvent),事件代理(_eventProxy)。

繼續往下看:

定義了:

  • 操做數據的方法集合(data)
  • 事件代理的方法集合(event)
  • 事件處理的方法集合(handler)
  • 爲ztree提供公共方法的結合(tools)
  • 渲染DOM的方法集合(view)
  • ztree的定義

其實很明瞭,咱們想要修改icon圖標所在的那個dom,因此就看view底下的方法便可;亦可用搜索showIcon關鍵字來找到對應方法的位置。不過先以大局觀看整個項目比較好,這樣腦子裏有一個程序設計的完整的流程和生命週期這樣的概念。

4.3 查看view底下的方法:

共計30-40個方法(粗略數了一下)

首先不要慌,先看函數名,相信本身可以看懂。

圈起來這個makeDOMNodeIcon,就是渲染icon和節點名稱的那個方法。

4.4 makeDOMNodeIcon 渲染出節點icon和名稱

makeDOMNodeIcon: function (html, setting, node) {
    var nameStr = data.nodeName(setting, node),
      name = setting.view.nameIsHTML ? nameStr : nameStr.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
    html.push("<span id='", node.tId, consts.id.ICON,
      "' title='' treeNode", consts.id.ICON, " class='", view.makeNodeIcoClass(setting, node),
      "' style='", view.makeNodeIcoStyle(setting, node), "'></span><span id='", node.tId, consts.id.SPAN,
      "' class='", consts.className.NAME,
      "'>", name, "</span>");
  },
複製代碼

很簡單,這個方法就作了一件事,往html裏面push了兩個span標籤,一個是用於icon圖標的,一個用於顯示節點名稱的。

咱們須要的修改icon圖標這個dom,仔細查看一下,icon這個dom的class和style分別經過makeNodeIcoClassmakeNodeIcoStyle去動態的獲取。

這裏我要介紹下,iconfont圖標渲染形式——font-class引用:

<i class="iconfont icon-xxx"></i>
複製代碼

因此咱們只須要給這個節點的icon圖標dom加上這兩個類便可渲染出iconfont形式的圖標。

往下翻看:

4.5 makeNodeIcoClass 給節點icon寫上類名

先看makeNodeIcoClass

makeNodeIcoClass: function (setting, node) {
    var icoCss = ["ico"];
    if (!node.isAjaxing) {
      var isParent = data.nodeIsParent(setting, node);
      icoCss[0] = (node.iconSkin ? node.iconSkin + "_" : "") + icoCss[0];
      if (isParent) {
        icoCss.push(node.open ? consts.folder.OPEN : consts.folder.CLOSE);
      } else {
        icoCss.push(consts.folder.DOCU);
      }
    }
    return consts.className.BUTTON + " " + icoCss.join('_');
  },
複製代碼

這個方法就像名字那樣,很直白,就是return一個字符串,即一些類名。

consts.className.BUTTON這個是常量,能夠在_consts常量列表中找到值就是:

而後icoCss就是固定一個ico + open close docu 三選一拼接而成,若是設置了iconSkin屬性那麼就拼上這個。 也就是獲得了兩個類名吧:button ico_open這樣形式的。

其實這些不重要,由於咱們將徹底完全的改寫這個方法。

改寫以下:

makeNodeIcoClass: function (setting, node) {
    const iconName = data.nodeIcon(setting, node);
    return `${consts.className.BUTTON} iconfont ${iconName}`; 
 }
複製代碼

我們經過data集合中的一個nodeIcon方法獲得iconName,而後拼接成button iconfont icon-xxx形式。

保留consts.className.BUTTON是由於有樣式,我懶得改。這樣就能夠渲染出iconfont的圖標了。

看下data.nodeIcon(此方法放入data集合中):

nodeIcon: function (setting, node, newName) {
    const key = setting.data.key.nodeType;
    if (typeof newName !== 'undefined') {
      node[key] = newName;
    }
    return setting.data.iconMap[node[key]] || "";
  },
複製代碼

首先得到全局設置對象中的setting.data.key.nodeType這個屬性,而後獲得node[key]=node.nodeType=0,而後經過key:value形式返回iconMap的對應的那個值,最終獲得此節點的icon圖標類名。

節點例如:

{ id: 1, pid: 0, name: "隨意勾選 1", open: true, nodeType: 0, chkDisabled: true },
複製代碼

setting對象中的屬性:

在這個例子中,得到的值就是setting.data.iconMap[0] = 'iconjianyuede'

最後還作了一個判斷,若是·node[key]iconMap中沒有對應的key,也就是setting.data.iconMap[node[key]] = undefined,也有相應的返回值空串,這樣作的好處是,有些節點不須要圖標,那麼在iconMap中就不要有相應的key,ztree就不會渲染圖標。

4.6 makeNodeIcoStyle 給節點icon寫上樣式

控制圖標顯示與否showIcon相關的方法makeNodeIcoStyle

makeNodeIcoStyle: function (setting, node) {
    var icoStyle = [];
    if (!node.isAjaxing) {
      var isParent = data.nodeIsParent(setting, node);
      var icon = (isParent && node.iconOpen && node.iconClose) ? (node.open ? node.iconOpen : node.iconClose) : node[setting.data.key.icon];
      if (icon) icoStyle.push("background:url(", icon, ") 0 0 no-repeat;");
      if (setting.view.showIcon == false || !tools.apply(setting.view.showIcon, [setting.treeId, node], true)) {
        icoStyle.push("width:0px;height:0px;");
      }
    }
    return icoStyle.join('');
  },
複製代碼

裏面就是經過isParentshowIcon一塊兒控制icon圖標dom的樣式,若是顯示就是給background設置路徑,不顯示就讓它寬高爲0,最終返回一個包含樣式的字符串。

咱們的業務不須要判斷父節點圖標顯示與否,因此不用管isParent屬性,只要配合showIcon便可,改寫以下:

makeNodeIcoStyle: function (setting, node) {
    if(!node.isAjaxing) {
      if (setting.view.showIcon == false
          || !tools.apply(setting.view.showIcon, [setting.treeId, node], true)
          || data.nodeIcon(setting, node) === ''
        ) {
        const iconStyle = "display:none;"
        return iconStyle
      }else {
        return ''
      }
    }
  },
複製代碼

若是顯示返回空串,不顯示返回display:none;便可。

4.7 來看看效果

首先項目中引入iconfont:

// main.js
import './style/iconfont.css'
複製代碼

而後引用我們魔改後的ztree源碼文件all:

將它require進來,別忘了引入jquery!!!

// 引入jquery
import * as $ from "jquery";
if(!window.jQuery){
  window.jQuery = $;
}
// 引入魔改版ztree
require("./lib/jquery.ztree.all");

// 調用ztree官方初始化方法,參數省略
$.fn.zTree.init()
複製代碼

最後效果就是這樣:

效果不錯,可是在你操做的時候,好比摺疊這個節點會出現,此節點已有的圖標會消失的問題。 因此,還須要繼續看下源碼接着修改。

4.8 摺疊事件

在view集合中繼續查看函數名,能夠找到:

這三個方法,前綴都是expandCollapse意思就是展開節點這一類的意思。

展開expandCollapseNode方法,往下翻到1235行:

這裏用一個node.open屬性去判斷調用replaceIcoClass方法,替換了icon圖標dom的class。因此下一步,修改這個方法。

replaceIcoClass:

replaceIcoClass: function (node, obj, newName) {
    if (!obj || node.isAjaxing) return;
    var tmpName = obj.attr("class");
    if (tmpName == undefined) return;
    var tmpList = tmpName.split("_");
    switch (newName) {
      case consts.folder.OPEN:
      case consts.folder.CLOSE:
      case consts.folder.DOCU:
        tmpList[tmpList.length - 1] = newName;
        break;
    }
    obj.attr("class", tmpList.join("_"));
  },
複製代碼

就是作了替換class的操做,不分析了,直接修改:

replaceIcoClass: function (node, obj, newName) {
    // 根據你的狀況,我這邊就所有刪除就能夠了
 },
複製代碼

改完以後,摺疊的時候節點圖標不會消失,正常了。這樣改完全部增刪改功能都不會影響圖標正常的顯示了。

4.9 暫且完成一階段任務

按照上面修改後,正常使用,能夠知足業務的需求並且渲染海量數據上性能也十分出色。不過也只能說暫且完成一個階段而已,畢竟狗需求每天改。。。

5、寫在後面

我我的認爲能看懂一些源碼,且修改一些內容,並不厲害。畢竟會玩手機跟生產手機是一回事嗎?

你們也不要妄自菲薄,其實不少源碼並無你想象的那些難,只是你認爲本身不行,你的潛意識一直告訴本身:你看不懂。因此致使了你們誤覺得這些源碼很難看懂。

其實我想告訴你們的是,多看源碼,好處很是明顯:

  • 1.看看大廠或者大神人家的代碼是如何編寫的,人家的代碼規範是怎麼樣的?
  • 2.能夠學到更多高階函數的用法,你們面試的時候不是常常背一些什麼函數柯里化,閉包,設計模式,compose等等,可是基本在本身的接觸項目中不會用到,而這些好的開源項目中的源碼內你就能夠發現一些影子,你能夠試着理解試着運用起來。
  • 3.能夠了解整個程序設計須要考慮的東西,這些開源項目的完整性和可維護性都很是的強,能夠從中學習,而且提高本身。

GitHub連接:vue-magic-tree 但願能過幫到你們。 線上測試地址:測試

相關文章
相關標籤/搜索