【问题标题】:Javascript Effectively Build Table From JSON and Add It to DOMJavascript 有效地从 JSON 构建表并将其添加到 DOM
【发布时间】:2012-02-02 19:43:36
【问题描述】:

我有一个来自服务器的 JSON 数组,其中包含 200 个对象的数组,每个对象包含另外 10 个我想以表格格式显示的对象。起初,我为每次迭代创建一个<tr>,并使用jQuery 将一个从数组值构建的<td> 附加到<tr>。这在 Chrome 中大约需要 30 秒,在 IE 8 中大约需要 19 秒。这花费的时间太长,所以我尝试切换到 Array.join() 方法,在该方法中,我将构成整个表的每个字符串存储在一个数组中,并在最后做$('#myTable').append(textToAppend)。这实际上比我的第一个版本差了大约 5 秒。

我希望将其缩短到 10 秒左右。我有机会吗?如果没有,我只会一次添加一行,但我不想那样做。

for(allIndex = 0; allIndex < entries.alumnus.length; allIndex++){

  var entry = '<tr id="entry' + allIndex + '" class="entry"></tr>';
  $('#content_table').append(entry);

  $('#entry' + allIndex).append(($.trim(entries.alumnus[allIndex].title) != '' ?
        '<td id="title' + allIndex + '" class="cell"><span class="content">' +
         entries.alumnus[allIndex].title + '</span></td>' : '<td width="5%">' + 
         filler + '</td>'));    
  .
  .
  .
  .//REST OF ELEMENTS
  .
  .
  .
}   

更新:我昨天一定搞砸了,因为我回去尝试从 DOM 中附加元素,然后稍后附加它们,而不使用 jQuery,我已经把时间花在了Chrome 为 85 毫秒,IE7 为 450 毫秒!!!你们真棒!!!我给了 user1 答案,因为那个更全面,并且在 Chrome 中使用片段并没有真正改变我的时间,在 IE7 中增加了大约 20 毫秒。但我仍然很欣赏@Emre Erkan 的回答,并且会更频繁地使用:)

【问题讨论】:

  • 你的意思是你有一个 JSON 数组,里面有 200 个元素,每个元素都是 10 个元素的数组?
  • 是的,我把我的术语弄错了:)
  • 你能贴一些代码吗?你只是在使用字符串,还是在 DOM 之外构建元素,然后将其添加进去?
  • 200x10 表格需要 15 秒才能在 Chrome 中生成?不错..

标签: javascript jquery string performance concatenation


【解决方案1】:

如果您有来自服务器的 JSON 验证字符串,并且结构可靠地是字符串数组的数组,则下面将允许您避免 JSON 解析,而是用一系列常量正则表达式替换 HTML 生成倾向于在使用本机缓冲区的本机代码中实现的操作。这应该完全避免一次解析,并将任何花费 O(n**2) 的缓冲区副本替换为 k,O(n) 缓冲区副本以获得常数 k。

var jsonContent
    = ' [ [ "foo", "bar", "[baz\\"boo\\n]" ], ["1","2" , "3"] ] ';

var repls = {  // Equivalent inside a JSON string.
  ',': "\\u002b",
  '[': "\\u005b",
  ']': "\\u005d"
};
var inStr = false;  // True if the char matched below is in a string.
// Make sure that all '[', ']', and ',' chars in JSON content are
// actual JSON punctuation by re-encoding those that appear in strings.
jsonContent = jsonContent.replace(/[\",\[\]]|\\./g, function (m) {
  if (m.length === 1) {
    if (m === '"') {
      inStr = !inStr;
    } else if (inStr) {
      return repls[m];
    }
  }
  return m;
});

// Prevent XSS.
jsonContent = jsonContent.replace(/&/g, "&amp;")
   .replace(/</g, "&lt;");
// Assumes that the JSON generator does not encode '<' as '\u003c'.

// Remove all string delimiters and space outside of strings.
var html = jsonContent
    .replace(/\"\s*([,\]])\s*\"?|\s*([\[,])\s*\"/g, "$1$2");
// Introduce the table header and footer.
html = html.replace(/^\s*\[/g, "<table>")
html = html.replace(/]\s*$/g, "</table>")
// Introduce row boundaries.
html = html.replace(/\],?/g, "</tr>")
html = html.replace(/\[/g, "<tr><td>")
// Introduce cell boundaries.
html = html.replace(/,/g, "<td>")

// Decode escape sequences.
var jsEscs = {
  '\\n': '\n',
  '\\f': '\f',
  '\\r': '\r',
  '\\t': '\t',
  '\\v': '\x0c',
  '\\b': '\b'
};
html = html.replace(/\\(?:[^u]|u[0-9A-Fa-f]{4})/g, function (m) {
      if (m.length == 2) {
        // Second branch handles '\\"' -> '"'
        return jsEscs[m] || m.substring(1);
      }
      return String.fromCharCode(parseInt(m.substring(2), 16));
    });

// Copy and paste with the below to see the literal content and the table.
var pre = document.createElement('pre');
pre.appendChild(document.createTextNode(html));
document.body.appendChild(pre);
var div = document.createElement('div');
div.innerHTML = html;
document.body.appendChild(div);

【讨论】:

    【解决方案2】:

    我建议你使用DocumentFragment 并使用原生javascript 来进行这种大规模的DOM 操作。我准备了一个例子,你可以查看here

    var fragment = document.createDocumentFragment(),
        tr, td, i, il, key;
    for(i=0,il=data.length;i<il;i++) {
        tr = document.createElement('tr');
        for(key in data[i]) {
            td = document.createElement('td');
            td.appendChild( document.createTextNode( data[i][key] ) );
            tr.appendChild( td );
        }
        fragment.appendChild( tr );
    }
    $('#mytable tbody').append( fragment );
    

    我认为这是完成这项工作的最快方法。

    【讨论】:

    • 在 jQuery 上直接使用 JavaScript 是否存在任何跨浏览器问题?
    • 我不知道。但是如果你使用得当,原生 JavaScript 会比 jQuery 运行得更快,你不觉得吗?
    • 哦,我并不是在争论 JavaScript 比 jQuery 快。我刚刚在 Chrome、Firefox 和特别是 IE8 之间遇到了几个问题,这些问题在很大程度上让我更喜欢使用 jQuery,因为所有这些都经过测试以消除可能会让人痛苦的细节。但是鉴于这个纯 JavaScript 是多么简单(而不是试图用它做任何可笑的事情),我宁愿整天都这样做。
    【解决方案3】:

    最快的看起来像这样:

    var oldTable = document.getElementById('example'),
        newTable = oldTable.cloneNode(true);
    for(var i = 0; i < json_example.length; i++){
        var tr = document.createElement('tr');
        for(var j = 0; j < json_example[i].length; j++){
            var td = document.createElement('td');
            td.appendChild(document.createTextNode(json_example[i][j]));
            tr.appendChild(td);
        }
        newTable.appendChild(tr);
    }
    
    oldTable.parentNode.replaceChild(newTable, oldTable);
    

    并且应该以毫秒为单位运行。这是一个示例:http://jsfiddle.net/Paulpro/YhQEC/ 它创建了 200 个表行,每个行包含 10 个 td。

    您希望使用元素而不是字符串进行附加,但在创建整个结构之前不希望向 DOM 附加任何内容(以避免循环中的重排)。因此,您可以克隆原始表并附加到克隆,然后在循环完成后插入克隆。

    通过避免使用 jQuery 并直接与 DOM 交互,您还将获得相当多的速度。

    您的代码可能如下所示:

    var oldTable = document.getElementById('content_table'),
        newTable = oldTable.cloneNode(true),
        tr, td;
    for(var i = 0; i < entries.alumnus.length.length; i++){
        tr = document.createElement('tr');
        tr.id = 'entry' + i;
        tr.className = 'entry';
    
        if(entries.alumnus[i].title){
            td = document.createElement('td');
            td.id = 'title' + i;
            td.className = 'cell';
            var span = document.createElement('span');
            span.className = 'content';
            span.appendChild(document.createTextNode(entries.alumnus[i].title);
            td.appendChild(span);
            tr.appendChild(td);
            tr.appendChild(createFiller(filler));
        }
    
        // REST OF ELEMENTS
    
        newTable.appendChild(tr);
    
    }
    
    oldTable.parentNode.replaceChild(newTable, oldTable);
    
    function createFiller(filler){
        var td = document.createElement('td');
        td.style.width = '5%';
        td.appendChild(document.createTextNode(filler);
        return td;
    }
    

    【讨论】:

    • 已编辑以包含计时器:jsfiddle.net/Paulpro/tPrcK 在我的笔记本电脑上的 Chrome 版本 16 中大约需要 15 - 25 毫秒。
    • 感谢您修复这些问题,Emre!
    • 如果你使用DocumentFragment会更好。它变得更快。 DocumentFragments 是轻量级的 document 对象,它没有父对象并且不附加到 DOM。你可以随心所欲地操作它,当你完成它时,你可以将它附加到DOM。这样您就不必克隆 table 元素。
    • @EmreErkan 谢谢!我不知道 DocumentFragment。我每天都学到更多:)。我暂时保留我的答案,希望 OP 能同时看到这些 cmets。 PS。为什么在将片段附加到表时使用fragment.cloneNode(true) 而不是只传入fragment
    • 我更新了您的fiddle,但变化不大。我想这是因为您还使用了未附加的 DOM 元素。
    【解决方案4】:

    最重要的是从 dom 中创建整个表格内容,然后插入到表格中。 chrome 中的这个在大约 3 到 5 毫秒后结束:

    function createTableFromData(data) {
        var tableHtml = '';
        var currentRowHtml;
        for (var i = 0, length = data.length; i < length; i++) {
            currentRowHtml = '<tr><td>' + data[i].join('</td><td>') + '</td></tr>';
            tableHtml += currentRowHtml;        
        }  
        return tableHtml;    
    }
    
    var textToAppend= createTableFromData(yourData);
    $('#myTable').append(textToAppend);
    

    【讨论】:

    • 赞成,因为这是迄今为止表现最好的解决方案。由于 OP 没有对各个行做任何事情(例如添加事件侦听器),innerHTML总是比每个节点的 DOM 更快。尽管您的 currentRowHtml 变量只会稍微减慢速度。
    • 我实际上是在添加监听器。我只是没有使用我在上面的示例中使用的元素。
    • @Eliezer 使用事件委托。
    • 我目前正在使用 jQuery 为我的事件设置处理程序
    • @Eliezer - c69 是对的。如果你有这么大的桌子,从性能的角度来看,使用事件委托会更好,例如通过 jQuery 中的 delegate()。
    【解决方案5】:

    这绝对是可行的。我不认为字符串连接是要走的路。一般来说,当元素没有附加到主 DOM 时,创建元素和操作它们似乎更快;因此,首先创建整个表,然后将其添加。在普通的 javascript 中,我认为下面的代码基本上是你想要实现的..

        //mock up the data (this will come from you AJAX call)..
        var data = [];
        for(var i = 0; i < 200; i++){
            var rowData = [];
            for(var j = 0; j < 10; j++){
                rowData.push("This is a string value");
            }
            data.push(rowData);
        }
    
        //create table..
        var table = document.createElement("table");
        for(var i = 0; i < data.length; i++){
            var rowData = data[i];
            var row = document.createElement("tr");
            for(var j = 0; j < rowData.length; j++){
                var cell = document.createElement("td");
                cell.innerHTML = rowData[j];
                row.appendChild(cell);
            }
            table.appendChild(row);
        }
        //finally append the whole thing..
        document.body.appendChild(table);
    

    当我将它插入 Safari 的控制台时,它会在

    【讨论】:

    • @EmreErkan -- innerHTML nothing 有问题,尽管在此示例中的处理方式存在问题 :)跨度>
    • 作为替代方案 - cell.appendChild(document.createTextNode(rowData[j]))
    • @cwolves 只是想知道-在上面的代码中使用 innerHTML 有什么问题?我写得很快,所以我不介意,但我没有看到问题 - 这是一个新表,所以我不会丢失对旧元素的引用,因为代码将用于显示来自 Ajax 的表数据打电话不应该有安全风险。
    • @EmreErkan -- innerHTML 不是邪恶的,它被滥用了。对于生成 HTML,它不仅完全有效,而且在许多旧浏览器中比 DOM 方法快得多。 jQuery 和许多其他库在内部使用它。
    • @MarkRhodes -- 事实上,您在其他任何地方都使用createElement,在一个地方使用innerHtml 很麻烦,您应该改用createTextNode。它比其他任何东西都更具风格,我倾向于只在使用字符串构建大型 DOM 节点时使用innerHTML
    猜你喜欢
    • 2019-07-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-11
    • 2023-04-01
    • 1970-01-01
    • 2015-04-11
    • 2014-11-10
    相关资源
    最近更新 更多