大家都知道EasyUI的Datagrid组件在加载大数据量时的优势并不是很明显,相对于其他一些框架,如果数据量达到几千,便会比较慢,特别是在IE下面。针对这种情况,我们首要做的是要相办法优化datagrid组件的各方面性能,不过任何事情都是可以变通解决的,virtualScrollView就是一种不错的解决方案。

virtualScrollView的准则就是尽量少画tr到table里,表格的高度是有限的,而用户的可见区域是很有限的,所以数据量很大的时候,是没有必要将所有数据数据都画到表格中,这样造成庞大的DOM,导致加载速度变慢。

源码分析">源码分析

jQuery EasyUI的datagrid组件官方也扩展了一个virtualScrollView视图,我们来分析一下它的源码:

  1. var scrollview = $.extend({}, $.fn.datagrid.defaults.view, { render: function(target, container, frozen){
  2. var state = $.data(target, 'datagrid'); var opts = state.options;
  3. //这个地方要特别注意,并不是用的state.data.rows数据 //而是用的view.rows,而view.rows在onBeforeRender事件中被设置为undefined了
  4. //onBeforeRender事件在scrollview中,即便是url方式有也只会被触发一次,所以在第一次rend时,是没有数据直接return了。 var rows = this.rows || [];
  5. if (!rows.length) { return;
  6. } var fields = $(target).datagrid('getColumnFields', frozen);
  7. //如果是rend frozen部分,但是有没有行号和frozenColumns的话,那就直接返回
  8. if (frozen){ if (!(opts.rownumbers || (opts.frozenColumns && opts.frozenColumns.length))){
  9. return; }
  10. }
  11. var index = this.index; var table = [''];
  12. for(var i=0; i<rows.length; i++)="" {="" var="" css="opts.rowStyler" ?="" opts.rowstyler.call(target,="" index,="" rows[i])="" :="" '';="" <li="" class="alt"> var classValue = ''; var styleValue = '';
  13. if (typeof css == 'string'){ styleValue = css;
  14. } else if (css){ classValue = css['class'] || '';
  15. styleValue = css['style'] || ''; }
  16. var cls = 'class="datagrid-row ' + (index % 2 && opts.striped ? 'datagrid-row-alt ' : ' ') + classValue + '"'; var style = styleValue ? 'style="' + styleValue + '"' : '';
  17. // get the class and style attributes for this row // var cls = (index % 2 && opts.striped) ? 'class="datagrid-row datagrid-row-alt"' : 'class="datagrid-row"';
  18. // var styleValue = opts.rowStyler ? opts.rowStyler.call(target, index, rows[i]) : ''; // var style = styleValue ? 'style="' + styleValue + '"' : '';
  19. var rowId = state.rowIdPrefix + '-' + (frozen?1:2) + '-' + index; table.push('
  20. ');

  21. table.push(this.renderRow.call(this, target, fields, frozen, index, rows[i])); table.push('
  22. ');

  23. index++; }
  24. table.push('
  25. ');

  26. $(container).html(table.join('')); },
  27. /**
  28. * onBeforeRender事件,首先要明白两点: * 1-调用loadData方法加载数据数据时,loadData内部rend之前会触发这个事件
  29. * 2-url方式时,获取到远程数据之后,也是使用loadData方法加载数据的,所以url方式也会触发onBeforeRender事件 * @param {DOM} target datagrid实例的宿主DOM对象
  30. * @return {[type]} [description] */
  31. onBeforeRender: function(target){ var state = $.data(target, 'datagrid');
  32. var opts = state.options; var dc = state.dc;
  33. var view = this; // 删除onLoadSuccess事件,防止被触发,将备份到state.onLoadSuccess上
  34. state.onLoadSuccess = opts.onLoadSuccess; opts.onLoadSuccess = function(){};
  35. opts.finder.getRow = function(t, p){
  36. var index = (typeof p == 'object') ? p.attr('datagrid-row-index') : p; var row = $.data(t, 'datagrid').data.rows[index];
  37. if (!row){//什么情况会取不到呢? var v = $(t).datagrid('options').view;
  38. row = v.rows[index - v.index]; }
  39. return row; };
  40. dc.body1.add(dc.body2).empty();
  41. this.rows = undefined; // 把需要画的tr绑定到view.rows上了 this.r1 = this.r2 = []; // view.r1和viwe.r2分别存放对第一页tr和最后一页tr的引用
  42. //这里不要想当然,只是绑定了事件,在第一次加载数据时,究竟是什么时候触发这个事件的呢 //这个问题得追溯到loadData方法了,每次loadData之后都会直接使用triggerHandler触发scroll的
  43. dc.body2.unbind('.datagrid').bind('scroll.datagrid', function(e){ if (state.onLoadSuccess){
  44. opts.onLoadSuccess = state.onLoadSuccess; // 恢复onLoadSuccess事件 state.onLoadSuccess = undefined;
  45. } if (view.scrollTimer){// 清除定时器
  46. clearTimeout(view.scrollTimer); }
  47. // 延时五十毫秒执行 view.scrollTimer = setTimeout(function(){
  48. scrolling.call(view); }, 50);
  49. });
  50. function scrolling(){ if (dc.body2.is(':empty')){//dc.body2对应普通列数据,如果为空的话,说明没有数据。
  51. //没有数据就尝试加载数据 reload.call(this);
  52. } else { var firstTr = opts.finder.getTr(target, this.index, 'body', 2);
  53. var lastTr = opts.finder.getTr(target, 0, 'last', 2); var headerHeight = dc.view2.children('div.datagrid-header').outerHeight();
  54. var top = firstTr.position().top - headerHeight; var bottom = lastTr.position().top + lastTr.outerHeight() - headerHeight;
  55. if (top > dc.body2.height() || bottom < 0){
  56. reload.call(this); } else if (top > 0){
  57. var page = Math.floor(this.index/opts.pageSize); this.getRows.call(this, target, page, function(rows){
  58. this.r2 = this.r1; this.r1 = rows;
  59. this.index = (page-1)*opts.pageSize; this.rows = this.r1.concat(this.r2);
  60. this.populate.call(this, target); });
  61. } else if (bottom < dc.body2.height()){// 需要加载下一页的情况 var page = Math.floor(this.index/opts.pageSize)+2;
  62. if (this.r2.length){ page++;
  63. } this.getRows.call(this, target, page, function(rows){
  64. if (!this.r2.length){ this.r2 = rows;
  65. } else { this.r1 = this.r2;
  66. this.r2 = rows; this.index += opts.pageSize;
  67. } this.rows = this.r1.concat(this.r2);
  68. this.populate.call(this, target); });
  69. } }
  70. function reload(){
  71. var top = $(dc.body2).scrollTop();//被卷起的高度 var index = Math.floor(top/25);//获取被卷起的行索引,如:卷起一行半37.5,index为1
  72. var page = Math.floor(index/opts.pageSize) + 1;//获取页数,如果每页10条,卷起262.5,page为2
  73. this.getRows.call(this, target, page, function(rows){ this.index = (page-1)*opts.pageSize;//view.index存放的是page页第一行的索引
  74. this.rows = rows;//view.rows存放需要画的tr this.r1 = rows;
  75. this.r2 = []; this.populate.call(this, target);
  76. dc.body2.triggerHandler('scroll.datagrid'); });
  77. } }
  78. },
  79. getRows: function(target, page, callback){ var state = $.data(target, 'datagrid');
  80. var opts = state.options; var index = (page-1)*opts.pageSize;
  81. var rows = state.data.rows.slice(index, index+opts.pageSize); if (rows.length){//这是一次性加载完所有数据的方式,可以直接从本地javascript数组中取出数据
  82. callback.call(this, rows);
  83. } else {//懒加载方式 var param = $.extend({}, opts.queryParams, {
  84. page: page, rows: opts.pageSize
  85. }); if (opts.sortName){
  86. $.extend(param, { sort: opts.sortName,
  87. order: opts.sortOrder });
  88. } if (opts.onBeforeLoad.call(target, param) == false) return;
  89. $(target).datagrid('loading');
  90. var result = opts.loader.call(target, param, function(data){ $(target).datagrid('loaded');
  91. var data = opts.loadFilter.call(target, data); callback.call(opts.view, data.rows);
  92. // opts.onLoadSuccess.call(target, data); }, function(){
  93. $(target).datagrid('loaded'); opts.onLoadError.apply(target, arguments);
  94. }); if (result == false){
  95. $(target).datagrid('loaded'); }
  96. } },
  97. populate: function(target){
  98. var state = $.data(target, 'datagrid'); var opts = state.options;
  99. var dc = state.dc; var rowHeight = 25;
  100. if (this.rows.length){
  101. opts.view.render.call(opts.view, target, dc.body2, false); opts.view.render.call(opts.view, target, dc.body1, true);
  102. // 看到了么,滚动条有那么大空间是怎么实现的了么?用的padding! dc.body1.add(dc.body2).children('table.datagrid-btable').css({
  103. paddingTop: this.index*rowHeight, paddingBottom: state.data.total*rowHeight - this.rows.length*rowHeight - this.index*rowHeight
  104. }); opts.onLoadSuccess.call(target, {
  105. total: state.data.total, rows: this.rows
  106. }); }
  107. } });

    分析结论

    virtualScrollView原理是通过设置div的上下padding来达到模拟极大数据量的效果的,我们只画比可视部分多一点的trEasyUI的virtualScrollView支持两种方式:一是一次性请求完所有数据;二是每次都是ajax到pageSize条数据EasyUI的virtualScrollView画的tr数量是2*pageSize(初次加载例外,这时候只画1*pageSize的tr)EasyUI的virtualScrollView视图把行高强制视为25px的,如果你设置非25px的行高,这个视图就不能正常工作因为只画2*pageSize个tr,所以我们dategrid的高度不能设置得超过2*25*pageSize个像素,超过的话就会造成可视区有留白使用loadData方法加载数据的话loadData入参不需要total属性,只要是rows数组就可以了,total在loadData内部会自动计算

    对于前面几点,大家自己看看源码里我写的注释,基础差的,看个似懂非懂就行了,基础好的,最好就彻底研究下。

    存在的Bug

    请求后台死循环

    如果是url方式,第一次加载不到数据,就会不断地请求后台。看到146行了么,如果回调函数没有接受到rows,是不应该触发scorll事件的,因为scroll事件会请求后台数据,我已我们只要加上条件就行了:

    1. if(rows && rows.length > 0){ dc.body2.triggerHandler('scroll.datagrid');
    2. }
      二次请求后台

      url方式下,如果后台返回数据不足以填充表格高度的时候,会重复请求后台(注意这地方只重复请求一次,跟第一个bug不同)。这个问题的原因也很简单,其实这种情况,datagrid高度有点大,但是后台又只有很少几条数据造成的,表现在只有一批数据,而这批数据又不足以填满这个表格可视区高度。我们把122行对getRows方法的调用加个条件就可以了:

      1. if (this.rows.length == opts.pageSize) { this.getRows.call(this, target, page, function(rows) {
      2. if (!this.r2.length) { this.r2 = rows;
      3. } else { this.r1 = this.r2;
      4. this.r2 = rows; this.index += opts.pageSize;
      5. } this.rows = this.r1.concat(this.r2);
      6. this.populate.call(this, target); });
      7. }

        this.rows是当前已经画的一批rows,如果rows的条数没有pageSize大,那就说明不需要再请求数据了。

        virtualScrollView是一种很好的优化手段,以后会被应用的越来越广的,EasyUI的VirtualScrollView视图是否支持editor我并有去尝试,估计是不支持的,有兴趣的同学可以去研究研究。

jQuery EasyUI Datagrid VirtualScrollView视图简单分析的更多相关文章

  1. 扩展jquery easyui datagrid编辑单元格

    扩展jquery easyui datagrid编辑单元格 1.随便聊聊 这段时间由于工作上的业务需求,对jquery easyui比较感兴趣,根据比较浅薄的js知识,对jquery easyui中的 ...

  2. jQuery EasyUI datagrid列名包含特殊字符会导致表格错位

    首先申明:本文所述的Bug存在于1.3.3以及更高版本中,其它低版本,本人未测试,太老的版本不想去折腾了. 洒家在写前端的SQL执行工具时,表格用了 jQuery EasyUI datagrid,因为 ...

  3. jQuery EasyUI datagrid实现本地分页的方法

    http://www.codeweblog.com/jquery-easyui-datagrid%e5%ae%9e%e7%8e%b0%e6%9c%ac%e5%9c%b0%e5%88%86%e9%a1% ...

  4. jquery easyui datagrid 加每页合计和总合计

    jquery easyui datagrid 加每页合计和总合计 一:效果图 二:代码实现 这个只有从后台来处理 后台根据rows 和page两个参数返回的datatable 命名为dt 然后根据dt ...

  5. 浅谈jQuery easyui datagrid操作单元格样式

    今天项目上遇到问题,就是表格风格统一的问题,由于用了2个不同的框架,所以如果要大修比较麻烦,考虑到修改表格样式工作量会少很多,所以考虑修改jQuery easyui datagrid数据网格的样式. ...

  6. 扩充 jQuery EasyUI Datagrid 数据行鼠标悬停/离开事件(onMouseOver/onMouseOut)

    客户需求: jQuery EasyUI Datagrid 用户列表鼠标悬停/离开数据行时显示人员头像(onMouseOver/onMouseOut) 如图所示,Datagrid 鼠标悬停/离开数据行时 ...

  7. 扩展 jQuery EasyUI Datagrid 数据行鼠标悬停/离开事件(onMouseOver/onMouseOut)

    客户需求: jQuery EasyUI Datagrid 用户列表鼠标悬停/离开数据行时显示人员头像(onMouseOver/onMouseOut) 如图所示,Datagrid 鼠标悬停/离开数据行时 ...

  8. jQuery EasyUI DataGrid Checkbox 数据设定与取值

    纯粹做个记录,以免日后忘记该怎么设定. 这一篇将会说明两种使用 jQuery EasyUI DataGrid 的 Checkbox 设定方式,以及在既有数据下将 checked 为 true 的该笔数 ...

  9. jquery easyui datagrid使用参考

    jquery easyui datagrid使用参考   创建datagrid 在页面上添加一个div或table标签,然后用jquery获取这个标签,并初始化一个datagrid.代码如下: 页面上 ...

随机推荐

  1. 第三课作业——set类型、sorted set类型的增删改查,redis的事务

    第三课时作业 静哥 by 2016.2.23~2016.3.6   [作业描述] 1.总结什么是set以及什么是sorted set,并完成对set以及sorted set的增删改查(查需要至少4种方 ...

  2. 接口测试工具 — jmeter(关联)

    1.正则表达式 1)添加正则表达式提取器 2)提取关联词 3)填写正则表达式 4)使用关联,其他请求使用${sign2}代替变量值 2. 1)添加提取器 2)填写变量值 3)使用关联,其他请求使用${ ...

  3. JavaBean 介绍

    // Person.java public class Person{ private String name; private int age; // 无参构造函数 public Person(){ ...

  4. 转!!SQL左右连接中的on and和on where的区别

    原博文地址:http://blog.csdn.net/xingzhemoluo/article/details/39677891 原先一直对SQL左右连接中的on and和on where的区别不是太 ...

  5. pandas(六)读写文本格式的数据

    pandas提供的将表格型数据读取为DataFrame对象的函数. 函数 说明 read_csv 从文件.URL.文件型对象中加载带分隔符的数据.默认分隔符为逗号. read_table 从文件.UR ...

  6. Oracle SQL 外键测试

    测试SQL 创建SQL     t1为主表      t2为子表 create table t1(insert_date number,id int) create table t2(insert_d ...

  7. Mahout学习路线图-张丹老师

    前言 Mahout是Hadoop家族中与众不同的一个成员,是基于一个Hadoop的机器学习和数据挖掘的分布式计算框架.Mahout是一个跨学科产品,同时也是我认为Hadoop家族中,最有竞争力,最难掌 ...

  8. Numpy中的时间类型

    从Numpy1.7开始,已经有了原生的日期-时间支持,基本类型称为datetime64. In [1]: import numpy as np In [2]: nd = np.datetime64(' ...

  9. Github 的其他用法

    一.概述 Github 除了作为代码托管库外,有趣的程序员们还利用它解锁了有趣的新姿势. 二.新姿势 2.1 Github Pages 可以为项目建立静态主页(即gh-pages分支), 也可以建立命 ...

  10. cdoj1341卿学姐与城堡的墙

    地址:http://acm.uestc.edu.cn/#/problem/show/1341 题目: 卿学姐与城堡的墙 Time Limit: 2000/1000MS (Java/Others)    ...