[javascript] Knockout.js ist unglaublich langsam unter halbgroßen Datensätzen


5 Answers

Bitte beachten Sie: Knockout.js Performance Gotcha # 2 - Manipulation beobachtbarerArrays

Ein besseres Muster ist es, einen Verweis auf unser zugrunde liegendes Array zu erhalten, drücken Sie darauf und rufen Sie dann .valueHasMutated () auf. Jetzt erhalten unsere Abonnenten nur eine Benachrichtigung, die angibt, dass sich das Array geändert hat.

Question

Ich fange gerade mit Knockout.js an (wollte es immer ausprobieren, aber jetzt habe ich endlich eine Entschuldigung!) - Allerdings stoße ich auf einige wirklich schlechte Leistungsprobleme, wenn ich einen Tisch mit einem relativ kleinen Satz verbinde Daten (etwa 400 Zeilen oder so).

In meinem Modell habe ich 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 for Schleife ungefähr 30 Sekunden oder so mit ungefähr 400 Zeilen dauert. Wenn ich jedoch den Code zu:

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 schließt die for Schleife im Handumdrehen. Mit anderen Worten, die push Methode von Knockouts observableArray Objekt 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 erwarte push dass push jedes Mal, wenn ich es nenne, etwas rekalziert, etwa um gebundene DOM-Objekte neu aufzubauen. Gibt es eine Möglichkeit, diese Neukalibrierung zu verzögern oder alle meine Artikel gleichzeitig einzubringen?

Ich kann bei Bedarf mehr Code hinzufügen, aber ich bin mir ziemlich sicher, dass dies relevant ist. Zum größten Teil folgte ich nur den Knockout Tutorials von der Seite.

AKTUALISIEREN:

Nach dem folgenden Rat habe ich meinen Code aktualisiert:

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

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

UPDATE 2:

Nach anderen Ratschlägen habe ich jQuery.tmpl eine Aufnahme gegeben (die nativ von KnockOut unterstützt wird), und diese Templating-Engine wird ungefähr 400 Zeilen in etwas mehr als 3 Sekunden zeichnen. Dies scheint der beste Ansatz zu sein, mit Ausnahme einer Lösung, bei der beim Scrollen dynamisch mehr Daten geladen werden.




Ich habe mich mit so riesigen Datenmengen beschäftigt, die für mich einen valueHasMutated haben. valueHasMutated wirkte wie ein Zauber.

Modell anzeigen:

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) Array-Daten in das erforderliche observableArray geladen, was automatisch this.projects .

Wenn Sie Zeit haben, schauen Sie sich das an und nur für den Fall, dass es Probleme gibt, lassen Sie es mich wissen

Trick hier: Auf diese Weise, wenn im Falle irgendwelcher Abhängigkeiten (berechnet, abonniert usw.) auf Push-Ebene vermieden werden kann, können wir sie nach dem Aufruf (4) auf einmal ausführen lassen.




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

In ihrem Fall ziehen sie Daten mit getJSON() was extrem schnell ist. Von 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);
    });    
}



Wenn Sie IE verwenden, versuchen Sie, die Entwicklungstools zu schließen.

Wenn die Entwicklerwerkzeuge im IE geöffnet sind, wird diese Operation erheblich verlangsamt. Ich füge ~ 1000 Elemente zu einem Array hinzu. Wenn die Dev-Tools geöffnet sind, dauert dies ungefähr 10 Sekunden und IE friert währenddessen ein. Wenn ich die Dev-Tools schließe, ist die Operation sofort und ich sehe keine Verlangsamung in IE.




Eine Lösung, um zu verhindern, dass der Browser beim Rendern eines sehr großen Arrays gesperrt wird, besteht darin, das Array so zu "drosseln", dass nur einige Elemente gleichzeitig hinzugefügt werden, während dazwischen ein Ruhezustand besteht. Hier ist eine Funktion, die genau das tun wird:

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

Abhängig von Ihrem Anwendungsfall kann dies zu einer erheblichen UX-Verbesserung führen, da der Benutzer möglicherweise nur die erste Reihe von Zeilen sehen kann, bevor er scrollen muss.




Eine mögliche Problemumgehung in Kombination mit jQuery.tmpl besteht darin, Elemente gleichzeitig in das beobachtbare Array zu verschieben, wobei setTimeout verwendet wird.

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 jeweils ein einzelnes Element hinzufügen, sich die Zeit nehmen, das DOM entsprechend zu manipulieren, ohne dass der Browser für einige Sekunden vollständig blockiert wird, sodass der Benutzer gleichzeitig durch die Liste blättern kann.




Related