Динамическое создание большой HTML-таблицы в производительности JavaScript - PullRequest
20 голосов
/ 01 февраля 2011

У меня есть приложение, которое используется для анализа данных, и у меня возникли некоторые проблемы с производительностью при создании таблицы. Данные извлекаются из документов, и важно, чтобы все данные были представлены на одной странице (разбиение на страницы, к сожалению, не вариант).

Используя jQuery, я делаю ajax-запрос к серверу для получения данных. По завершении запроса я передаю данные в функцию вывода. Функция вывода проходит по массиву данных, используя цикл for, и объединяет строки в переменную. Когда цикл завершен, переменная, содержащая таблицу, добавляется к существующему элементу div на странице, а затем я продолжаю связывать события с таблицей для работы с данными.

С небольшим набором данных (~ 1000-2000 строк) он работает относительно хорошо, но некоторые из наборов данных содержат более 10000 строк, что приводит к сбою и закрытию Firefox, либо он перестает отвечать.

У меня вопрос: есть ли лучший способ выполнить то, что я делаю?

Вот код:

//This function gets called by the interface with an id to retrieve a document
function loadDocument(id){
    $.ajax({
        method: "get",
        url: "ajax.php",
        data: {action:'loadDocument',id: id},
        dataType: 'json',
        cache: true,
        beforeSend: function(){
            if($("#loading").dialog('isOpen') != true){
                //Display the loading dialog
                $("#loading").dialog({
                    modal: true
                });
            }//end if
        },//end beforesend
        success: function(result){
            if(result.Error == undefined){
                outputDocument(result, id);
            }else{
                <handle error code>
            }//end if
            if($('#loading').dialog('isOpen') == true){
                //Close the loading dialog
                $("#loading").dialog('close');
            }//end if
        }//end success
    });//end ajax
};//end loadDocument();


//Output document to screen
function outputDocument(data, doc_id){

    //Begin document output
    var rows = '<table>';
    rows += '<thead>';
    rows += '<tr>';
    rows += '<th>ID</th>';
    rows += '<th>Status</th>';
    rows += '<th>Name</th>';
    rows += '<th>Actions</th>';
    rows += '<th>Origin</th>';
    rows += '</tr>';
    rows += '</thead>';
    rows += '<tbody>';

    for(var i in data){
        var recordId = data[i].id;
        rows += '<tr id="' + recordId + '" class="' + data[i].status + '">';
        rows += '<td width="1%" align="center">' + recordId + '</td>';
        rows += '<td width="1%" align="center"><span class="status" rel="' + recordId + '"><strong>' + data[i].status + '</strong></span></td>';
        rows += '<td width="70%"><span class="name">' + data[i].name + '</span></td>';
        rows += '<td width="2%">';
        rows += '<input type="button" class="failOne" rev="' + recordId + '" value="F">';
        rows += '<input type="button" class="promoteOne" rev="' + recordId + '" value="P">';
        rows += '</td>';
        rows += '<td width="1%">' + data[i].origin + '</td>';
        rows += '</tr>';
    }//end for

    rows += '</tbody>';
    rows += '</table>';
    $('#documentRows').html(rows);

Первоначально я использовал jQuery для каждого цикла, но переключился на цикл for, который сбрил несколько мс.

Я думал об использовании чего-то вроде google gears, чтобы попытаться разгрузить часть обработки (если это возможно в этом сценарии).

Есть мысли?

Ответы [ 7 ]

19 голосов
/ 01 февраля 2011

joinHi,

Рендеринг - это проблема, но есть также проблема с конкатенацией такого количества строк внутри цикла, особенно когда строка становится очень большой. Вероятно, было бы лучше поместить строки в отдельные элементы массива, а затем, наконец, использовать «соединение» для создания огромной строки одним махом. например,

var r = new Array();
var j = -1, recordId;
r[++j] =  '<table><thead><tr><th>ID</th><th>Status</th><th>Name</th><th>Actions</th><th>Origin</th></tr></thead><tbody>'; 
for (var i in data){
    var d = data[i];
    recordId = d.id;
    r[++j] = '<tr id="';
    r[++j] = recordId;
    r[++j] = '" class="';
    r[++j] = d.status;
    r[++j] = '"><td width="1%" align="center">';
    r[++j] = recordId;
    r[++j] = '</td><td width="1%" align="center"><span class="status" rel="';
    r[++j] = recordId;
    r[++j] = '"><strong>';
    r[++j] = d.status;
    r[++j] = '</strong></span></td><td width="70%"><span class="name">';
    r[++j] = d.name;
    r[++j] = '</span></td><td width="2%"><input type="button" class="failOne" rev="';
    r[++j] = recordId;
    r[++j] = '" value="F"><input type="button" class="promoteOne" rev="';
    r[++j] = recordId;
    r[++j] = '" value="P"></td><td width="1%">';
    r[++j] = d.origin;
    r[++j] = '</td></tr>';
}
r[++j] = '</tbody></table>';
$('#documentRows').html(r.join(''));

Кроме того, я бы использовал показанный здесь метод индексации массива, а не «push», поскольку для всех браузеров, кроме Google Chrome, он работает быстрее, согласно этой статье .

4 голосов
/ 01 февраля 2011

Отображение такого количества строк приводит к замедлению движка браузера , а не движка JavaScript.К сожалению, с этим ничего не поделаешь.

Лучшее решение - просто не отображать столько строк одновременно, либо через нумерацию страниц, либо через виртуальную прокрутку.

2 голосов
/ 01 февраля 2011

То, как вы строите свою строку, приведет к огромному количеству мусора.

Поскольку строка становится все длиннее и длиннее, движок javascript должен продолжать выделять большие буферы и отбрасывать старые. В конечном итоге он не сможет выделить достаточно памяти, не переработав остатки всех старых строк.

Эта проблема усугубляется, когда длина строки увеличивается.

Вместо этого попробуйте добавлять новые элементы в DOM по одному, используя API манипуляции jQuery

Также рассмотрите только рендеринг того, что видно, и используйте собственную прокрутку.

1 голос
/ 01 февраля 2011

Вы можете сделать несколько вещей, чтобы увеличить производительность:

  1. Ваша строка переменная становится все больше и больше, поэтому не храните HTML в одной переменной. Решением может быть $.each() функция и каждая функция, которую вы добавляете в DOM. Но это незначительная корректировка.
  2. Создание HTML - это хорошо, но вы можете попробовать создать и добавить DOM. Как $('<tr></tr>').
  3. И, наконец, это наверняка решит вашу проблему: используйте множественный вызов ajax в первом вызове ajax, соберите, сколько данных доступно, и извлеките приблизительно 1000 или может быть больше данных. И использовать другие вызовы для сбора оставшихся данных. Если вы хотите, вы можете использовать синхронный вызов или асинхронные вызовы с умом.

Но старайтесь не хранить значение. Ваш размер DOM будет огромен, но он должен работать в современных браузерах и забыть о IE6.

@ fuel37: пример

function outputDocumentNew(data, doc_id) {
    //Variable DOM's
    var rowSample = $('<tr></tr>').addClass('row-class');
    var colSample = $('<td></td>').addClass('col-class');
    var spanSample = $('<span></span>').addClass('span-class');
    var inputButtonSample = $('<input type="button"/>').addClass('input-class');

    //DOM Container 
    var container = $('#documentRows');
    container.empty().append('<table></table>');

    //Static part
    var head = '<thead>\
                <tr>\
                    <th width="1%" align="center">ID</th>\
                    <th width="1%" align="center">Status</th>\
                    <th width="70%">Name</th>\
                    <th width="2%">Actions</th>\
                    <th width="1%">Origin</th>\
                </tr>\
                </thead>';
    container.append(head);

    var body = $('<tbody></tbody>');
    container.append(body);

    //Dynamic part
    $.each(data, function (index, value) {
        var _this = this;

        //DOM Manupulation
        var row = rowSample.clone();

        //Actions
        var inpFailOne = inputButtonSample.clone().val('F').attr('rev', _this.id).addClass('failOne').click(function (e) {
            //do something when click the button.
        });
        var inpPromoteOne = inputButtonSample.clone().val('P').attr('rev', _this.id).addClass('promoteOne').click(function (e) {
            //do something when click the button.
        });

        row
        .append(colSample.clone().append(_this.id))
        .append(colSample.clone().append(spanSample.colne().addClass('status').append(_this.status)))
        .append(colSample.clone().append(spanSample.colne().addClass('name').append(_this.name)))
        .append(colSample.clone().append(inpFailOne).append(inpPromoteOne))
        .append(colSample.clone().append(_this.origin));

        body.append(row);
    });
}

в этом процессе вам нужно создавать и поддерживать идентификаторы или классы для манипуляции. У вас есть контроль, чтобы связать события и управлять каждым элементом там.

0 голосов
/ 01 февраля 2011

Можно попробовать это ...

Улучшение циклов

Улучшение Concat String

var tmpLst = [];

for (var i=0, il=data.length; i<il; i++) {
    var record = data[i];
    var recordId = record.id;

    tmpLst.push('<tr id="');
    tmpLst.push(recordId); 
    tmpLst.push('" class="'); 
    tmpLst.push(record.status);
    tmpLst.push('">');
    tmpLst.push('<td width="1%" align="center">');

...ect...


}
rows += tmpLst.join('');

Это может сжатьдополнительный бит производительности ...

var lstReset = i * lstReset.length;
tmpLst[lstReset + 1]='<tr id="';
tmpLst[lstReset + 2]=recordId; 
tmpLst[lstReset + 3]='" class="'; 
0 голосов
/ 01 февраля 2011

Согласно другим предложениям (я не достаточно авторитетен, чтобы комментировать, извините!), Вы можете попробовать TableSorter плагин , чтобы обрабатывать только отображаемый объем данных за раз.

Я не знаю, как это получается при очень большом количестве строк, но их примерные данные - 1000 строк или около того.

Это не поможет с производительностью JS, но избавит от бремени рендерера браузера.

0 голосов
/ 01 февраля 2011

Отвечая, чтобы получить форматирование

Что произойдет, если вы сделаете

for(var i in data){
    var record = data[i];
    var recordId = record.id;
    rows += '<tr id="' + recordId + '" class="' + record.status + '">';
    rows += '<td width="1%" align="center">' + recordId + '</td>';
    rows += '<td width="1%" align="center"><span class="status" rel="' + recordId + '"><strong>' + data[i].status + '</strong></span></td>';
    rows += '<td width="70%"><span class="name">' + record.name + '</span></td>';
    rows += '<td width="2%">';
    rows += '<input type="button" class="failOne" rev="' + recordId + '" value="F">';
    rows += '<input type="button" class="promoteOne" rev="' + recordId + '" value="P">';
    rows += '</td>';
    rows += '<td width="1%">' + record.origin + '</td>';
    rows += '</tr>';
}//end for
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...