横向对比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 但愿能过帮到你们。 线上测试地址:测试

相关文章
相关标签/搜索