[JS][easyui]jQuery EasyUI Datagrid VirtualScrollView視圖簡單分析

來源:互聯網
上載者:User

[JS][easyui]jQuery EasyUI Datagrid VirtualScrollView視圖簡單分析


大家都知道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 var classValue = ''; var styleValue = '';
  13. table.push(this.renderRow.call(this, target, fields, frozen, index, rows[i])); table.push('
  14. index++; }
  15. '];
  16. if (typeof css == 'string'){ styleValue = css;
  17. } else if (css){ classValue = css['class'] || '';
  18. styleValue = css['style'] || ''; }
  19. var cls = 'class="datagrid-row ' + (index % 2 && opts.striped ? 'datagrid-row-alt ' : ' ') + classValue + '"'; var style = styleValue ? 'style="' + styleValue + '"' : '';
  20. // get the class and style attributes for this row // var cls = (index % 2 && opts.striped) ? 'class="datagrid-row datagrid-row-alt"' : 'class="datagrid-row"';
  21. // var styleValue = opts.rowStyler ? opts.rowStyler.call(target, index, rows[i]) : ''; // var style = styleValue ? 'style="' + styleValue + '"' : '';
  22. var rowId = state.rowIdPrefix + '-' + (frozen?1:2) + '-' + index; table.push('
  23. '); ');
  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我並有去嘗試,估計是不支援的,有興趣的同學可以去研究研究。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.