Je ne fais que commencer avec Knockout.js (j'ai toujours voulu l'essayer, mais maintenant j'ai enfin une excuse!) - Cependant, je rencontre de très mauvais problèmes de performances lors de la liaison d'une table à un ensemble relativement petit de données (environ 400 lignes environ).
Dans mon modèle, j'ai le code suivant:
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!
}
};
Le problème est que la for
boucle ci-dessus prend environ 30 secondes environ avec environ 400 lignes. Cependant, si je change le code en:
this.loadData = function (data)
{
var testArray = []; //<-- Plain ol' Javascript array
for(var i = 0; i < data.length; i++)
{
testArray.push(new ResultRow(data[i]));
}
};
Ensuite, la for
boucle se termine en un clin d'œil. En d'autres termes, la push
méthode de l' observableArray
objet de Knockout est incroyablement lente.
Voici mon modèle:
<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>
Mes questions:
- Est-ce la bonne façon de lier mes données (qui proviennent d'une méthode AJAX) à une collection observable?
- Je m'attends à
push
faire un recalcul lourd à chaque fois que je l'appelle, comme peut-être la reconstruction d'objets DOM liés. Existe-t-il un moyen de retarder ce recalcul, ou peut-être d'insérer tous mes articles en même temps?
Je peux ajouter plus de code si nécessaire, mais je suis presque sûr que c'est ce qui est pertinent. Pour la plupart, je ne faisais que suivre les didacticiels Knockout du site.
MISE À JOUR:
Selon les conseils ci-dessous, j'ai mis à jour mon code:
this.loadData = function (data)
{
var mappedData = $.map(data, function (item) { return new ResultRow(item) });
this.projects(mappedData);
};
Cependant, this.projects()
prend encore environ 10 secondes pour 400 lignes. J'admets que je ne sais pas à quelle vitesse ce serait sans Knockout (juste en ajoutant des lignes via le DOM), mais j'ai le sentiment que ce serait beaucoup plus rapide que 10 secondes.
MISE À JOUR 2:
Par autre conseil ci-dessous, j'ai donné un coup de feu à jQuery.tmpl (qui est nativement pris en charge par KnockOut), et ce moteur de création de modèles dessinera environ 400 lignes en un peu plus de 3 secondes. Cela semble être la meilleure approche, à l'exception d'une solution qui chargerait dynamiquement plus de données lorsque vous faites défiler.
la source
valueHasMutated
fait. vérifiez la réponse si vous avez le temps.Réponses:
Comme suggéré dans les commentaires.
Knockout a son propre moteur de modèle natif associé aux liaisons (foreach, with). Il prend également en charge d'autres moteurs de modèles, à savoir jquery.tmpl. Lisez ici pour plus de détails. Je n'ai pas fait de benchmarking avec différents moteurs, donc je ne sais pas si cela aidera. En lisant votre commentaire précédent, dans IE7, vous aurez peut-être du mal à obtenir la performance que vous recherchez.
En passant, KO prend en charge tout moteur de création de modèles js, si quelqu'un a écrit l'adaptateur pour celui-ci. Vous voudrez peut-être en essayer d'autres, car jquery tmpl doit être remplacé par JsRender .
la source
jquery.tmpl
donc je vais l'utiliser. Je pourrais étudier d'autres moteurs et écrire le mien si j'ai un peu de temps. Merci!data-bind
instructions dans votre modèle jQuery, ou utilisez-vous la syntaxe $ {code}?${code}
syntaxe et c'est beaucoup plus rapide. J'ai également essayé de faire fonctionner Underscore.js, mais je n'ai pas encore eu de chance (la<% .. %>
syntaxe interfère avec ASP.NET), et il ne semble pas encore y avoir de support JsRender.ResultRow
, cela ne mettra pas à jour l'interface utilisateur (vous devrez mettre à jour leprojects
observableArray qui forcera un re-rendu de votre table). $ {} peut certainement être avantageux si vos données sont à peu près en lecture seuleS'il vous plaît voir: Knockout.js Performance Gotcha # 2 - Manipulation observableArrays
la source
Utilisez la pagination avec KO en plus d'utiliser $ .map.
J'ai eu le même problème avec un grand ensemble de données de 1400 enregistrements jusqu'à ce que j'utilise la pagination avec knockout. Utiliser
$.map
pour charger les enregistrements a fait une énorme différence, mais le temps de rendu du DOM était toujours horrible. Ensuite, j'ai essayé d'utiliser la pagination et cela a rendu l'éclairage de mon ensemble de données plus rapide et plus convivial. Une taille de page de 50 a rendu l'ensemble de données beaucoup moins écrasant et a considérablement réduit le nombre d'éléments DOM.C'est très facile à faire avec KO:
http://jsfiddle.net/rniemeyer/5Xr2X/
la source
KnockoutJS propose d'excellents tutoriels, en particulier celui sur le chargement et l'enregistrement de données
Dans leur cas, ils extraient des données en utilisant
getJSON()
ce qui est extrêmement rapide. De leur exemple: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); }); }
la source
self.tasks(mappedTasks)
exécution prend environ 10 secondes (avec 400 lignes). Je pense que ce n'est toujours pas acceptable.+1
pour simplifier mon code et augmenter considérablement la vitesse. Peut-être que quelqu'un a une explication plus détaillée de ce qu'est le goulot d'étranglement.Donnez KoGrid un coup d' oeil. Il gère intelligemment votre rendu de lignes pour qu'il soit plus performant.
Si vous essayez de lier 400 lignes à une table à l'aide d'une
foreach
liaison, vous allez avoir du mal à pousser autant de KO dans le DOM.KO fait des choses très intéressantes en utilisant la
foreach
liaison, dont la plupart sont de très bonnes opérations, mais elles commencent à se détériorer à mesure que la taille de votre tableau augmente.J'ai emprunté la longue route sombre d'essayer de lier de grands ensembles de données à des tables / grilles, et vous finissez par devoir séparer / paginer les données localement.
KoGrid fait tout cela. Il a été conçu pour rendre uniquement les lignes que le spectateur peut voir sur la page, puis virtualiser les autres lignes jusqu'à ce qu'elles soient nécessaires. Je pense que vous trouverez ses performances sur 400 articles bien meilleures que celles que vous rencontrez.
la source
Une solution pour éviter de verrouiller le navigateur lors du rendu d'un très grand tableau consiste à «étrangler» le tableau de sorte que seuls quelques éléments soient ajoutés à la fois, avec un sommeil entre les deux. Voici une fonction qui fera exactement cela:
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; }
En fonction de votre cas d'utilisation, cela peut entraîner une amélioration massive de l'expérience utilisateur, car l'utilisateur peut ne voir que le premier lot de lignes avant de devoir faire défiler.
la source
Profiter de push () acceptant des arguments variables a donné les meilleures performances dans mon cas. 1300 lignes se chargeaient pendant 5973 ms (~ 6 sec.). Avec cette optimisation, le temps de chargement a été réduit à 914 ms (<1 s)
, soit une amélioration de 84,7%!
Plus d'informations sur Pousser des éléments vers un observableArray
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); };
la source
J'ai eu affaire à d'énormes volumes de données qui m'arrivaient
valueHasMutated
fonctionnait comme un charme.Voir le modèle:
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)
Après l'appel
(4)
, les données du tableau seront chargées dans le observableArray requis qui estthis.projects
automatiquement.si vous avez le temps, jetez un œil à ceci et juste au cas où vous auriez un problème, faites-le moi savoir
Astuce ici: En faisant comme ça, si en cas de dépendances (calculées, abonnés, etc.), on peut éviter au niveau push et nous pouvons les faire exécuter en une seule fois après l'appel
(4)
.la source
push
, le problème est que même un seul appel à pousser entraînera de longs temps de rendu. Si un tableau a 1000 éléments liés à aforeach
, pousser un seul élément redonne tout le foreach et vous payez un coût de temps de rendu élevé.Une solution de contournement possible, en combinaison avec l'utilisation de jQuery.tmpl, consiste à pousser des éléments à la fois vers le tableau observable de manière asynchrone, en utilisant 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 } }
De cette façon, lorsque vous n'ajoutez qu'un seul élément à la fois, le navigateur / knockout.js peut prendre son temps pour manipuler le DOM en conséquence, sans que le navigateur ne soit complètement bloqué pendant plusieurs secondes, afin que l'utilisateur puisse faire défiler la liste simultanément.
la source
J'ai expérimenté la performance et j'ai deux contributions qui, je l'espère, pourraient être utiles.
Mes expériences se concentrent sur le temps de manipulation du DOM. Donc, avant d'entrer dans cela, il vaut vraiment la peine de suivre les points ci-dessus à propos de l'insertion dans un tableau JS avant de créer un tableau observable, etc.
Mais si le temps de manipulation du DOM vous gêne toujours, cela peut aider:
1: Un motif pour enrouler un spinner de chargement autour du rendu lent, puis le masquer en utilisant afterRender
http://jsfiddle.net/HBYyL/1/
Ce n'est pas vraiment une solution au problème de performances, mais montre qu'un délai est probablement inévitable si vous bouclez sur des milliers d'éléments et qu'il utilise un modèle dans lequel vous pouvez vous assurer qu'un spinner de chargement apparaît avant la longue opération KO, puis masquez après. Donc, cela améliore au moins l'UX.
Assurez-vous de pouvoir charger un spinner:
// 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)
Cachez le spinner:
<div data-bind="template: {afterRender: hide}">
qui déclenche:
hide = function() { $("#spinner").hide() }
2: Utiliser la liaison html comme un hack
Je me suis souvenu d'une vieille technique de l'époque où je travaillais sur un décodeur avec Opera, la construction de l'interface utilisateur en utilisant la manipulation DOM. C'était terriblement lent, donc la solution était de stocker de gros morceaux de HTML sous forme de chaînes et de charger les chaînes en définissant la propriété innerHTML.
Quelque chose de similaire peut être réalisé en utilisant la liaison html et un calcul qui dérive le HTML de la table sous forme d'un gros morceau de texte, puis l'applique en une seule fois. Cela résout le problème de performances, mais l'inconvénient majeur est que cela limite considérablement ce que vous pouvez faire avec la liaison à l'intérieur de chaque ligne de table.
Voici un violon qui montre cette approche, ainsi qu'une fonction qui peut être appelée à l'intérieur des lignes de la table pour supprimer un élément d'une manière vaguement KO. De toute évidence, ce n'est pas aussi bon qu'un KO correct, mais si vous avez vraiment besoin de performances fulgurantes (ish), c'est une solution de contournement possible.
http://jsfiddle.net/9ZF3g/5/
la source
Si vous utilisez IE, essayez de fermer les outils de développement.
L'ouverture des outils de développement dans IE ralentit considérablement cette opération. J'ajoute ~ 1000 éléments à un tableau. Lorsque les outils de développement sont ouverts, cela prend environ 10 secondes et IE se fige pendant que cela se produit. Lorsque je ferme les outils de développement, l'opération est instantanée et je ne vois aucun ralentissement dans IE.
la source
J'ai également remarqué que le moteur de modèle Knockout js fonctionne plus lentement dans IE, je l'ai remplacé par underscore.js, fonctionne beaucoup plus rapidement.
la source