2011-02-01 7 views
19

Mam aplikację, która służy do analizy danych i mam kilka problemów z wydajnością podczas tworzenia tabeli. Dane są pobierane z dokumentów i ważne jest, aby wszystkie dane były prezentowane na jednej stronie (paginacja niestety nie jest opcją).Dynamiczne tworzenie dużych tabel html w wydajności javascript

Korzystając z jQuery, wysyłam żądanie ajax do serwera w celu pobrania danych. Po zakończeniu żądania przekazuję dane do funkcji wyjściowej. Funkcja wyjściowa przechodzi przez macierz danych za pomocą pętli for i konkatenacji wierszy do zmiennej. Po zakończeniu pętli zmienna zawierająca tabelę jest następnie dołączana do istniejącego elementu div na stronie, a następnie włączam zdarzenia do tabeli w celu pracy z danymi.

Z niewielkim zestawem danych (~ 1000-2000 wierszy) działa stosunkowo dobrze, ale niektóre zestawy danych zawierają ponad 10.000 wierszy, co powoduje awarię przeglądarki Firefox i jej zamknięcie lub przestaje odpowiadać.

Moje pytanie brzmi: czy istnieje lepszy sposób na osiągnięcie tego, co robię?

Oto niektóre kodu:

//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); 

Początkowo byłem przy użyciu jQuery każdej pętli, ale przełącza się do pętli który ogolił kilka ms.

Pomyślałem o użyciu czegoś w rodzaju narzędzi Google, aby spróbować odciążyć część przetwarzania (jeśli jest to możliwe w tym scenariuszu).

Jakieś myśli?

+0

Pokaż pierwszą setkę, a następnie użyj zegarów lub robotników do zbudowania innych. Pamiętaj, że przeglądarka jest jednowątkowa. Podczas wykonywania js przeglądarka nie odpowiada. Użyj delegacji wydarzeń. – jasssonpet

+0

Użyj wirtualnego przewijania http://nexts.github.io/Clusterize.js/ – Denis

Odpowiedz

18

joinHi,

Renderowanie jest problemem, ale istnieje również problem z łączeniem tylu ciągów w pętli, szczególnie gdy łańcuch staje się bardzo duży. Najlepiej byłoby umieścić struny w poszczególnych elementach tablicy, a następnie użyć "join", by stworzyć ogromny ciąg za jednym zamachem. na przykład

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('')); 

Również chciałbym użyć metody indeksowania tablicy pokazany tutaj, zamiast „push”, ponieważ dla wszystkich przeglądarkach z wyjątkiem Google Chrome jest szybszy, według this article.

+1

Możesz wycisnąć nieco większą wydajność, określając długość tablicy podczas jej konstruowania, np. var r = new Array (data.length * 19 + 2) – Neil

+0

Uwzględniasz również szerokość jako procent w tagach "" w tabeli. To dużo powielonych specyfikacji. Dlaczego nie umieszczać wartości szerokości tylko na elementach "" u góry. podobne do specyfikacji "align =" center ". W ten sposób zaoszczędzisz silnik, sprawdzając każdą komórkę danych pod kątem jej szerokości i zmniejszając rozmiar łańcucha. – Neil

+0

Przyjemne użycie ++ j, zaleciłbym poprawienie definicji pętli dla dodatkowej wydajności. – digiguru

4

Wyświetlenie wielu wierszy powoduje spowolnienie pracy silnika renderującego przeglądarki , a nie silnika JavaScript. Niestety nie można wiele z tym zrobić.

Najlepszym rozwiązaniem jest po prostu nie wyświetlać tylu wierszy jednocześnie, poprzez stronicowanie lub przewijanie wirtualne.

+0

Dzięki chłopaki, nie jestem pewien, czy wirtualne przewijanie będzie opcja, ale. Myślę, że spróbuję wczytać x rekordów naraz zgodnie z sugestią @jasssonpet. Wiem, że dodawanie rekordów do istniejącego stołu jest kosztowne, zastanawiam się, czy zamiast tego spowoduje to tylko wąskie gardło? – fuel37

0

Odpowiadając aby formatowanie

Co się stanie, jeśli nie

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 
+0

Wypróbowałem to wcześniej, a wydajność była praktycznie identyczna. – fuel37

+0

następnie zgadzam się na sugestię paginacji Box9 – mplungjan

+1

Przy takiej ilości danych sugeruję użycie bufora ciągów.W przeciwnym razie program będzie niepotrzebnie wolno - zobacz http://www.softwaresecretweapons.com/jspwiki/javascriptstringconcatenation w celu uzyskania szczegółowych informacji. – kikito

2

sposób budują swój ciąg spowoduje ogromne ilości zbierania śmieci.

W miarę jak łańcuch staje się coraz dłuższy, silnik javascript musi przydzielać większe bufory i odrzucać stare. W końcu nie będzie w stanie przydzielić wystarczającej ilości pamięci bez recyklingu pozostałości wszystkich starych łańcuchów.

Ten problem jest coraz gorszy, ponieważ sznurek rośnie dłużej.

Zamiast spróbować dodać nowe elementy do DOM jednym naraz używając jQuery manipulation API

również pod uwagę tylko to, co jest widoczne renderingu i zaimplementować własną przewijanie.

+0

Ta pierwsza część twojej odpowiedzi jest tym, czego szukałem. Nie wiem zbyt wiele o wewnętrznych silnikach javascript, ale doszedłem do wniosku, że dołączenie wszystkich danych do zmiennej musi być przyczyną jakiegoś problemu. Czy istnieje lepszy sposób osiągnięcia tego bez dołączania danych do tabeli i resetowania zmiennej. Próbowałem dołączać wiersz do tabeli podczas każdej pętli, ale od 5 000 do 132 000 ms w przypadku dokumentu zawierającego 7200 rekordów. – fuel37

+0

Szczerze mówiąc uważam, że najlepszą odpowiedzią jest po prostu nie wyświetlanie tej ilości danych. Trudno wyobrazić sobie sytuację, w której chciałbym przewinąć 10000 wierszy - a nawet 1000. –

+0

Ma to dla mnie taki sam sens !! Nie chcę powiedzieć, co to jest aplikacja, ale dane muszą być wyświetlane w kolejności i jako całość do wykrywania trendów itp. Próbuję wymyślić jakieś rozwiązanie, aby obejść to, ale jest to bardzo wizualne i trudno to wytłumaczyć, musiałbyś to zobaczyć :). Dziękuję bardzo za twoją odpowiedź, wskazała mi to w nowym kierunku! – fuel37

1

można zrobić kilka rzeczy, aby zwiększyć wydajność:

  1. twoi rzędy zmienna jest coraz większe i większe, tak, nie przechowywać html w jednej zmiennej.rozwiązaniem może być funkcja $.each() i każda funkcja, do której dodajesz element do DOM. Ale to niewielkie dostosowanie.
  2. Generowanie HTML jest dobre, ale można wypróbować tworzenie i dołączanie DOM. Podobnie jak $('<tr></tr>').
  3. I na koniec to rozwiąże twój problem na pewno: użyj wielu wywołań ajaxowych w pierwszym wywołaniu ajax, zebrać ile danych jest dostępnych i pobrać około 1000 lub może być więcej danych. I użyj innych połączeń, aby zebrać pozostałe dane. Jeśli chcesz, możesz korzystać z połączeń synchronicznych lub połączeń asynchronicznych mądrze.

Ale staraj się unikać przechowywania wartości. Twój rozmiar DOM będzie ogromny, ale powinien działać w przeglądarkach moder i zapomnieć o IE6.

@ fuel37: Przykład

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); 
    }); 
} 

w tym procesie trzeba tworzyć & utrzymać id lub zajęcia dla manipulacji. Masz kontrolę, by wiązać zdarzenia i manipulować poszczególnymi elementami.

+0

Czy możesz podać przykład # 2? Nigdy wcześniej nie tworzyłem elementów w ten sposób. – fuel37

0

Co do innych sugestii (nie jestem wystarczająco grzeczny, aby komentować, przepraszam!), Możesz spróbować TableSorter plugin, aby obsłużyć tylko wyświetlanie użytecznej ilości danych na raz.

Nie wiem, jak to kosztuje przy bardzo dużej liczbie wierszy, ale ich przykładowe dane to 1000 wierszy.

To nie pomogłoby w wydajności JS, ale zmniejszyłoby obciążenie z mechanizmu renderującego przeglądarki.

+0

Próbowałem tego w innym przypadku i wydaje się, że wymiotuje również po około 2000 rekordów. – fuel37

+0

Dobrze wiedzieć. Powodzenia! – peteorpeter

0

można spróbować ...

Improve Loops

Improve String Concat

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(''); 

To może zmieścić dodatkowy bit wydajności ...

var lstReset = i * lstReset.length; 
tmpLst[lstReset + 1]='<tr id="'; 
tmpLst[lstReset + 2]=recordId; 
tmpLst[lstReset + 3]='" class="'; 
Powiązane problemy