Нокаут.js невероятно медленный при полу-больших наборах данных
Я только начинаю с нокаутом.js (всегда хотел попробовать, но теперь у меня наконец есть оправдание!)- Однако я сталкиваюсь с некоторыми действительно плохими проблемами производительности при привязке таблицы к относительно небольшому набору данных (около 400 строк или около того).
в моей модели, у меня есть следующий код:
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!
}
};
вопрос for
цикл выше занимает около 30 секунд или около того с около 400 строк. Однако, если я изменю код к:
this.loadData = function (data)
{
var testArray = []; //<-- Plain ol' Javascript array
for(var i = 0; i < data.length; i++)
{
testArray.push(new ResultRow(data[i]));
}
};
тут for
цикл завершается в мгновение ока. Другими словами,push
метод объект невероятно медленный.
вот мой шаблон:
<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>
Мои Вопросы:
- это правильный способ привязать мои данные (которые поступают из метода AJAX) к наблюдаемой коллекции?
- я жду
push
делает некоторые тяжелые re-calc каждый раз, когда я называю это, такие как, возможно, восстановление связанных объектов DOM. Есть ли способ либо отложить этот пересчет, либо, возможно, нажать все мои предметы сразу?
я могу добавить больше кода, если это необходимо, но я уверен, что это то, что имеет значение. По большей части я просто следовал нокаутом учебники с сайта.
обновление:
согласно приведенному ниже совету, я обновил свой код:
this.loadData = function (data)
{
var mappedData = $.map(data, function (item) { return new ResultRow(item) });
this.projects(mappedData);
};
однако, this.projects()
все еще занимает около 10 секунд для 400 строки. Я признаю, что я не уверен, как быстро это будет без нокаут (просто добавление строк через DOM), но у меня есть ощущение, что это будет намного быстрее, чем 10 секунд.
обновление 2:
за другие советы ниже, я дал jQuery.tmpl выстрел (который изначально поддерживается нокаутом), и этот шаблонный движок будет рисовать около 400 строк всего за 3 секунды. Это кажется лучшим подходом, за исключением решения это будет динамически загружать больше данных при прокрутке.
12 ответов:
как предлагается в комментариях.
Knockout имеет собственный собственный механизм шаблонов, связанный с привязками (foreach, with). Он также поддерживает другие шаблонные движки, а именно jquery.тмпл. Читайте здесь для более подробной информации. Я не делал никакого бенчмаркинга с разными двигателями, поэтому не знаю, поможет ли это. Читая ваш предыдущий комментарий, в IE7 вы можете бороться, чтобы получить производительность, которую вы ищете.
кроме того, ко поддерживает какие-либо JS шаблонизатор двигатель, если кто-то написал адаптер для нее есть. Вы можете попробовать другие там, как jquery tmpl должен быть заменен на JsRender.
смотрите: нокаут.JS Performance Gotcha #2-манипулирование observableArrays
лучший шаблон-получить ссылку на наш базовый массив, нажать на него, а затем вызвать .valueHasMutated (). Теперь наши абоненты получат только одно уведомление о том, что массив изменился.
использовать разбиение на страницы С KO в дополнение к использованию $.карта.
У меня была такая же проблема с большими наборами данных из 1400 записей, пока я не использовал пейджинг с нокаутом. Используя
$.map
загрузка записей действительно имела огромное значение, но время рендеринга DOM все еще было отвратительным. Затем я попытался использовать разбиение на страницы, и это сделало мой dataset lighting быстрым, а также более удобным для пользователя. Размер страницы 50 сделал набор данных намного менее подавляющим и уменьшил количество элементов DOM драматический.его очень легко сделать с KO:
KnockoutJS имеет некоторые большие учебники, особенно тот, о загрузке и сохранении данных
в их случае они извлекают данные с помощью
getJSON()
что очень быстро. Из их примера: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); }); }
дать KoGrid посмотреть. Он разумно управляет рендерингом строк, чтобы он был более производительным.
Если вы пытаетесь привязать 400 строк таблицы с помощью
foreach
привязка, у вас будут проблемы с тем, чтобы протолкнуть это через KO в DOM.KO делает некоторые очень интересные вещи, используя
foreach
привязка, большинство из которых являются очень хорошими операциями, но они начинают разрушаться на perf как размер вашего массива растет.Я был на длинной темной дороге, пытаясь привязать большие наборы данных к таблицам/сеткам, и вам в конечном итоге нужно разбить / страница данных локально.
KoGrid это все. Он был построен только для визуализации строк, которые зритель может видеть на странице, а затем виртуализировать другие строки, пока они не понадобятся. Я думаю, что вы найдете его perf на 400 предметов, чтобы быть намного лучше, чем вы испытываете.
решение, чтобы избежать блокировки браузера при рендеринге очень большого массива, заключается в том, чтобы "дросселировать" массив таким образом, чтобы только несколько элементов добавлялись одновременно, с промежуточным сном. Вот функция, которая будет делать именно это:
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; }
в зависимости от вашего варианта использования это может привести к массовому улучшению UX, так как пользователь может видеть только первую партию строк перед прокруткой.
использование push () принятие переменных аргументов дало лучшую производительность в моем случае. 1300 строк загружались в течение 5973ms (~6 сек.). При такой оптимизации время загрузки сократилось до 914 МС ( Это 84.7 % улучшение!
более подробная информация Pushing items to an 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); };
Я имел дело с такими огромными объемами данных, поступающих в для меня
valueHasMutated
работал как шарм .Модель Вид :
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)
после вызова
(4)
массив данных будет загружен в требуемый observableArray, который являетсяthis.projects
автоматически .если у вас есть время взглянуть на это и просто в случае каких-либо проблем дайте мне знать
трюк здесь : делая так , если в случае каких-либо зависимостей (данные,подписывается и т. д.) можно избежать на уровне толчка, и мы можем заставить их выполнять сразу после вызова
(4)
.
я экспериментировал с производительностью, и у меня есть два вклада, которые, я надеюсь, могут быть полезны.
мои эксперименты сосредоточены на времени манипуляции DOM. Поэтому, прежде чем вдаваться в это, определенно стоит следовать пунктам выше о том, чтобы толкать в массив JS перед созданием наблюдаемого массива и т. д.
но если время манипуляции DOM все еще мешает вам, то это может помочь:
1: картина для того чтобы обернуть обтекатель втулки нагрузки вокруг медленный рендер, а затем скрыть его с помощью afterRender
на самом деле это не исправление проблемы производительности, но показывает, что задержка, вероятно, неизбежна, если вы зацикливаетесь на тысячах элементов, и он использует шаблон, в котором вы можете убедиться, что у вас есть загрузочный счетчик, который появляется перед длительной операцией KO, а затем скрывает его. Так что это улучшает UX, по крайней мере.
убедитесь, что вы можете загрузить 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)
скрыть блесны:
<div data-bind="template: {afterRender: hide}">
какие триггеры:
hide = function() { $("#spinner").hide() }
2: Использование привязки html в качестве взлома
я вспомнил старую технику, когда я работал над телеприставкой с Opera, создавая пользовательский интерфейс с использованием манипуляции DOM. Это было ужасно медленно, поэтому решение состояло в том, чтобы хранить большие куски HTML в виде строк и загружать строки, устанавливая свойство innerHTML.
что-то подобное может быть достигается с помощью привязки html и вычисляется, что получает HTML для таблицы в виде большого куска текста, а затем применяет его на одном дыхании. Это устраняет проблему производительности, но массовый недостаток заключается в том, что он серьезно ограничивает то, что вы можете сделать с привязкой внутри каждой строки таблицы.
вот скрипка, которая показывает этот подход, вместе с функцией, которая может быть вызвана изнутри строк таблицы, чтобы удалить элемент неопределенно-KO-подобным образом. Очевидно, что это не так хорошо, как правильный KO, но если вам действительно нужна производительность blazing (ish), это возможный обходной путь.
возможный обходной путь, в сочетании с использованием jQuery.tmpl, заключается в том, чтобы одновременно передавать элементы в наблюдаемый массив асинхронным образом, используя 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 } }
таким образом, когда вы добавляете только один элемент за раз, браузер / нокаут.js может занять свое время, чтобы управлять DOM соответственно, без браузера полностью заблокирован в течение нескольких секунд, так что пользователь может прокручивать список одновременно.
Я также заметил, что Knockout JS template engine работает медленнее в IE, Я заменил его подчеркиванием.js, работает намного быстрее.
при использовании IE попробуйте закрыть инструменты разработки.
открытие инструментов разработчика в IE значительно замедляет эту операцию. Я добавляю ~1000 элементов в массив. При открытии инструментов разработки это занимает около 10 секунд, и IE зависает, пока это происходит. Когда я закрываю инструменты разработки, операция выполняется мгновенно, и я не вижу замедления в IE.