it-swarm.com.de

Knockout.js ist bei halbgroßen Datensätzen unglaublich langsam

Ich bin nur mit Knockout.js (immer wollte es ausprobieren, aber jetzt habe ich endlich eine Ausrede!) Die ersten Schritte - aber ich bin in ein paar wirklich schlechte Performance-Probleme laufen, wenn eine Tabelle auf einen relativ kleinen Satz Bindung von Daten (ungefähr 400 Zeilen oder so).

In meinem Modell habe ich den folgenden Code:

this.projects = ko.observableArray( [] ); //Bind to empty array at startup

this.loadData = function (data) //Called when AJAX method returns
{
   for(var i = 0; i < data.length; i++)
   {
      this.projects.Push(new ResultRow(data[i])); //<-- Bottleneck!
   }
};

Das Problem ist, dass die oben beschriebene for-Schleife mit etwa 400 Zeilen ungefähr 30 Sekunden dauert. Wenn ich jedoch den Code ändere in:

this.loadData = function (data)
{
   var testArray = []; //<-- Plain ol' Javascript array
   for(var i = 0; i < data.length; i++)
   {
      testArray.Push(new ResultRow(data[i]));
   }
};

Dann ist die for-Schleife im Handumdrehen abgeschlossen. Mit anderen Worten, die Methode Push des Knockout-Objekts observableArray ist unglaublich langsam.

Hier ist meine Vorlage:

<tbody data-bind="foreach: projects">
    <tr>
       <td data-bind="text: code"></td>
       <td><a data-bind="projlink: key, text: projname"></td>
       <td data-bind="text: request"></td>
       <td data-bind="text: stage"></td>
       <td data-bind="text: type"></td>
       <td data-bind="text: launch"></td>
       <td><a data-bind="mailto: ownerEmail, text: owner"></a></td>
    </tr>
</tbody>

Meine Fragen:

  1. Ist dies der richtige Weg, um meine Daten (die von einer AJAX -Methode stammen) an eine beobachtbare Sammlung zu binden?
  2. Ich gehe davon aus, dass Push jedes Mal, wenn ich es aufrufe, eine schwere Neuberechnung durchführt, z. B. die Neuerstellung gebundener DOM-Objekte. Gibt es eine Möglichkeit, diese Neuberechnung zu verzögern oder alle meine Artikel auf einmal einzuschieben?

Ich kann bei Bedarf weiteren Code hinzufügen, bin mir aber ziemlich sicher, dass dies relevant ist. Zum größten Teil habe ich nur Knockout-Tutorials von der Website verfolgt.

UPDATE:

Gemäß dem nachstehenden Rat habe ich meinen Code aktualisiert:

this.loadData = function (data)
{
   var mappedData = $.map(data, function (item) { return new ResultRow(item) });
   this.projects(mappedData);
};

Für 400 Zeilen dauert this.projects() jedoch noch etwa 10 Sekunden. Ich gebe zu, ich bin nicht sicher, wie schnell dies sein würde ohne Knockout (nur das Hinzufügen von Zeilen durch das DOM), aber ich habe das Gefühl, es wäre viel schneller als 10 Sekunden.

UPDATE 2:

Per anderen Ratschläge unten, gab ich jQuery.tmpl Schuß (die nativ von KnockOut unterstützt wird), und das Templating Engine wird rund 400 Zeilen in etwas mehr als 3 Sekunden ziehen. Dies scheint der beste Ansatz zu sein, abgesehen von einer Lösung, bei der beim Scrollen dynamisch mehr Daten geladen werden.

83

Wie in den Kommentaren vorgeschlagen.

Knockout verfügt über eine eigene native Template-Engine, die mit den Bindungen (foreach, with) verknüpft ist. Es unterstützt auch andere Template Engines, nämlich jquery.tmpl. Lesen Sie hier für weitere Details. Ich habe kein Benchmarking mit verschiedenen Engines durchgeführt, also weiß ich nicht, ob es helfen wird. Wenn Sie Ihren vorherigen Kommentar lesen, können Sie in IE7 möglicherweise Schwierigkeiten haben, die gewünschte Leistung zu erzielen.

Nebenbei unterstützt KO jede Js-Template-Engine, wenn jemand den Adapter dafür geschrieben hat. Vielleicht möchten Sie andere ausprobieren, da jquery tmpl durch JsRender ersetzt werden soll.

16
madcapnmckay

Bitte sehen Sie: Knockout.js Performance Gotcha # 2 - Manipulieren beobachtbarerArrays

Ein besseres Muster ist es, einen Verweis auf unser zugrundeliegendes Array zu erhalten. Drücken Sie auf das Array und rufen Sie .valueHasMutated () auf. Jetzt erhalten unsere Abonnenten nur eine Benachrichtigung, dass das Array geändert wurde.

50
Jim G.

Verwenden Sie pagination mit KO zusätzlich zu $ ​​.map.

Ich hatte das gleiche Problem mit großen Datensätzen von 1400 Datensätzen, bis ich mit Knockout paging verwendete. Die Verwendung von $.map zum Laden der Datensätze machte einen großen Unterschied, aber die DOM-Renderzeit war immer noch abscheulich. Dann habe ich versucht, die Paginierung zu verwenden, und dadurch wurde meine Datensatzbeleuchtung schnell und benutzerfreundlicher. Bei einer Seitengröße von 50 wurde der Datensatz weniger überfordert und die Anzahl der DOM-Elemente wurde drastisch reduziert.

Es ist sehr einfach mit KO zu tun:

http://jsfiddle.net/rniemeyer/5Xr2X/

13
Tim Santeford

KnockoutJS hat einige großartige Tutorials, insbesondere das Laden und Speichern von Daten

In ihrem Fall ziehen sie Daten mit getJSON() ab, was extrem schnell ist. Aus ihrem Beispiel:

function TaskListViewModel() {
    // ... leave the existing code unchanged ...

    // Load initial state from server, convert it to Task instances, then populate self.tasks
    $.getJSON("/tasks", function(allData) {
        var mappedTasks = $.map(allData, function(item) { return new Task(item) });
        self.tasks(mappedTasks);
    });    
}
11
deltree

Gib KoGrid einen Blick. Es verwaltet das Rendern der Zeilen auf intelligente Weise, so dass es performanter ist.

Wenn Sie versuchen, 400 Zeilen mit einer foreach-Bindung an eine Tabelle zu binden, werden Sie Schwierigkeiten haben, so viel durch KO in das DOM zu schieben.

KO führt einige sehr interessante Dinge mit der foreach-Bindung durch, von denen die meisten sehr gute Operationen sind, die jedoch mit der Größe Ihres Arrays immer weniger werden.

Ich bin den langen, dunklen Weg gegangen, zu versuchen, große Datensätze an Tabellen/Gitter zu binden, und Sie müssen am Ende die Daten lokal aufspalten.

KoGrid macht das alles. Es wurde erstellt, um nur die Zeilen zu rendern, die der Betrachter auf der Seite sehen kann, und virtualisiert die anderen Zeilen, bis sie benötigt werden. Ich denke, dass Sie die Perfektion von 400 Gegenständen besser finden, als Sie erleben.

9
ericb

Um zu verhindern, dass der Browser beim Rendern eines sehr großen Arrays blockiert wird, besteht die Möglichkeit, das Array so zu "drosseln", dass nur wenige Elemente gleichzeitig hinzugefügt werden und sich dazwischen ein Schlaf befindet. Hier ist eine Funktion, die genau das tut:

function throttledArray(getData) {
    var showingDataO = ko.observableArray(),
        showingData = [],
        sourceData = [];
    ko.computed(function () {
        var data = getData();
        if ( Math.abs(sourceData.length - data.length) / sourceData.length > 0.5 ) {
            showingData = [];
            sourceData = data;
            (function load() {
                if ( data == sourceData && showingData.length != data.length ) {
                    showingData = showingData.concat( data.slice(showingData.length, showingData.length + 20) );
                    showingDataO(showingData);
                    setTimeout(load, 500);
                }
            })();
        } else {
            showingDataO(showingData = sourceData = data);
        }
    });
    return showingDataO;
}

Je nach Anwendungsfall kann dies zu einer erheblichen Verbesserung der UX führen, da der Benutzer möglicherweise nur den ersten Stapel von Zeilen sieht, bevor er einen Bildlauf durchführen muss.

4
teh_senaus

Die Verwendung von Push (), die variable Argumente akzeptiert, ergab in meinem Fall die beste Leistung. __ 1300 Zeilen wurden für 5973 ms geladen (~ 6 Sekunden). Bei dieser Optimierung betrug die Ladezeit 914 ms (<1 Sek.).
Das ist eine Verbesserung von 84,7%!

Weitere Informationen unter Elemente in ein ObservableArray verschieben

this.projects = ko.observableArray( [] ); //Bind to empty array at startup

this.loadData = function (data) //Called when AJAX method returns
{
   var arrMappedData = ko.utils.arrayMap(data, function (item) {
       return new ResultRow(item);
   });
   //take advantage of Push accepting variable arguments
   this.projects.Push.apply(this.projects, arrMappedData);
};
4
mitaka

Ich hatte mit so großen Datenmengen zu tun, dass valueHasMutated wie ein Zauber wirkt.

View Model:

this.projects([]); //make observableArray empty --(1)

var mutatedArray = this.projects(); -- (2)

this.loadData = function (data) //Called when AJAX method returns
{
ko.utils.arrayForEach(data,function(item){
    mutatedArray.Push(new ResultRow(item)); -- (3) // Push to the array(normal array)  
});  
};
 this.projects.valueHasMutated(); -- (4) 

Nach dem Aufruf von (4) werden die Daten in den erforderlichen ObservableArray geladen, der automatisch this.projects ist.

wenn Sie Zeit haben, schauen Sie sich das an und falls es Probleme gibt, lassen Sie es mich wissen 

Trick hier: Wenn Sie so vorgehen, wenn Abhängigkeiten (berechnet, abonniert usw.) auf Push-Ebene vermieden werden können und wir sie nach dem Aufruf von (4) auf einmal ausführen können.

4
super cool

Eine mögliche Problemumgehung besteht in Kombination mit der Verwendung von jQuery.tmpl darin, Elemente asynchron auf das beobachtbare Array zu schieben, und zwar mit setTimeout;

var self = this,
    remaining = data.length;

add(); // Start adding items

function add() {
  self.projects.Push(data[data.length - remaining]);

  remaining -= 1;

  if (remaining > 0) {
    setTimeout(add, 10); // Schedule adding any remaining items
  }
}

Auf diese Weise kann der Browser/knockout.js, wenn Sie nur ein einzelnes Element gleichzeitig hinzufügen, das DOM entsprechend bearbeiten, ohne dass der Browser für einige Sekunden vollständig gesperrt ist, sodass der Benutzer die Liste gleichzeitig scrollen kann.

1
gnab

Ich habe mit Leistung experimentiert und habe zwei Beiträge, von denen ich hoffe, dass sie nützlich sein könnten.

Meine Experimente konzentrieren sich auf die DOM-Manipulationszeit. Bevor Sie sich diesem Punkt widmen, sollten Sie unbedingt die oben genannten Punkte befolgen, bevor Sie ein JS-Array erstellen, bevor Sie ein beobachtbares Array erstellen.

Wenn jedoch die DOM-Manipulationszeit immer noch im Weg ist, kann dies helfen:


1: Ein Muster, mit dem ein Laden-Drehfeld um das langsame Rendern gewickelt und dann mit afterRender ausgeblendet wird

http://jsfiddle.net/HBYyL/1/

Dies ist nicht wirklich eine Behebung des Leistungsproblems, zeigt jedoch, dass eine Verzögerung wahrscheinlich unvermeidlich ist, wenn Sie Tausende von Objekten durchlaufen und ein Muster verwendet, bei dem Sie sicherstellen können, dass vor dem langen KO-Vorgang ein Ladedreher angezeigt wird es danach. So verbessert es zumindest die UX.

Stellen Sie sicher, dass Sie einen Spinner laden können:

// Show the spinner immediately...
$("#spinner").show();

// ... by using a timeout around the operation that causes the slow render.
window.setTimeout(function() {
    ko.applyBindings(vm)  
}, 1)

Verstecken Sie den Spinner:

<div data-bind="template: {afterRender: hide}">

was auslöst:

hide = function() {
    $("#spinner").hide()
}

2: Verwenden der HTML-Bindung als Hack

Ich erinnerte mich an eine alte Technik aus der Zeit, als ich mit Opera an einer Set-Top-Box arbeitete und mit DOM-Manipulation eine Benutzeroberfläche aufbaute. Es war erschreckend langsam, daher bestand die Lösung darin, große HTML-Blöcke als Zeichenfolgen zu speichern und die Zeichenfolgen durch Festlegen der Eigenschaft innerHTML zu laden. 

Etwas Ähnliches kann erreicht werden, indem die HTML-Bindung verwendet wird und ein berechneter Code berechnet wird, der den HTML-Code für die Tabelle als großen Textblock ableitet und dann in einem Durchgang anwendet. Dies behebt zwar das Leistungsproblem, der massive Nachteil besteht jedoch darin, dass die Möglichkeiten, die mit der Bindung in jeder Tabellenzeile möglich sind, stark eingeschränkt werden. 

Hier ist eine Geige, die diesen Ansatz zeigt, zusammen mit einer Funktion, die aus den Tabellenzeilen aufgerufen werden kann, um ein Element auf eine vage KO-artige Weise zu löschen. Offensichtlich ist dies nicht so gut wie das richtige KO, aber wenn Sie wirklich eine atemberaubende (ish) Performance benötigen, ist dies eine mögliche Problemumgehung.

http://jsfiddle.net/9ZF3g/5/

1
sifriday

Ich habe auch bemerkt, dass die Knockout-Template-Engine im IE langsamer arbeitet.

0
Marcello

Wenn Sie den IE verwenden, schließen Sie die Dev-Tools.

Das Öffnen der Entwicklertools in IE verlangsamt diesen Vorgang erheblich. Ich füge einem Array ~ 1000 Elemente hinzu. Wenn die Entwicklungswerkzeuge geöffnet sind, dauert dies etwa 10 Sekunden, und IE friert ein, während dies geschieht. Wenn ich die Dev-Tools schließe, ist die Operation sofort und ich sehe keine Verlangsamung im IE.

0
Jon List