Я только начинаю работать с Knockout.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
метод объекта Knockout observableArray
невероятно медленный.
Вот мой шаблон:
<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
, что каждый раз, когда я его вызываю, выполняется тяжелая перерасчет, например, возможно, перестройка связанных объектов DOM. Есть ли способ отложить этот пересчет или, возможно, отправить все мои элементы сразу?
При необходимости я могу добавить больше кода, но я почти уверен, что это то, что нужно. По большей части я просто следил за учебниками Knockout с сайта.
ОБНОВИТЬ:
Следуя приведенному ниже совету, я обновил свой код:
this.loadData = function (data)
{
var mappedData = $.map(data, function (item) { return new ResultRow(item) });
this.projects(mappedData);
};
Однако this.projects()
для 400 строк по-прежнему требуется около 10 секунд. Признаюсь, я не уверен, насколько быстро это было бы без Knockout (просто добавление строк через DOM), но мне кажется, что это было бы намного быстрее, чем 10 секунд.
ОБНОВЛЕНИЕ 2:
Следуя другим советам, приведенным ниже, я попробовал jQuery.tmpl (который изначально поддерживается KnockOut), и этот шаблонизатор отрисует около 400 строк всего за 3 секунды. Это кажется лучшим подходом, если не считать решения, которое динамически загружало бы больше данных при прокрутке.
источник
valueHasMutated
делает это. проверьте ответ, если есть время.Ответы:
Как было предложено в комментариях.
Knockout имеет собственный собственный шаблонизатор, связанный с привязками (foreach, with). Он также поддерживает другие механизмы шаблонов, а именно jquery.tmpl. Подробнее читайте здесь . Я не проводил тестов с разными движками, поэтому не знаю, поможет ли это. Читая ваш предыдущий комментарий, в IE7 вы можете с трудом добиться желаемой производительности.
Кроме того, KO поддерживает любой движок шаблонов js, если кто-то написал для него адаптер. Возможно, вы захотите попробовать другие, поскольку jquery tmpl должен быть заменен на JsRender .
источник
jquery.tmpl
поэтому я воспользуюсь этим. Я мог бы изучить другие движки, а также написать свой собственный, если у меня будет немного свободного времени. Благодарность!data-bind
операторы в своем шаблоне jQuery или используете синтаксис $ {code}?${code}
синтаксис, и он намного быстрее. Я также пытался заставить работать Underscore.js, но пока не повезло (<% .. %>
синтаксис мешает ASP.NET), и, похоже, еще нет поддержки JsRender.ResultRow
, он не обновит пользовательский интерфейс (вам нужно будет обновитьprojects
observableArray, что приведет к повторному рендерингу вашей таблицы). $ {} определенно может быть выгодным, если ваши данные в значительной степени предназначены только для чтенияСм. Статью Knockout.js Performance Gotcha # 2 - Управление наблюдаемыми массивами
источник
Используйте нумерацию страниц с помощью KO в дополнение к использованию $ .map.
У меня была такая же проблема с большими наборами данных из 1400 записей, пока я не использовал разбиение на страницы с нокаутом. Использование
$.map
для загрузки записей имело огромное значение, но время рендеринга DOM по-прежнему оставалось ужасным. Затем я попытался использовать разбиение на страницы, и это сделало мой набор данных более быстрым, а также более удобным для пользователя. Размер страницы 50 сделал набор данных менее громоздким и резко уменьшил количество элементов DOM.Это очень просто сделать с помощью КО:
http://jsfiddle.net/rniemeyer/5Xr2X/
источник
В 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); }); }
источник
self.tasks(mappedTasks)
запуск занимает около 10 секунд (с 400 строками). Я считаю, что это все еще неприемлемо.+1
как упрощения моего кода, так и значительного увеличения скорости. Возможно, у кого-то есть более подробное объяснение того, в чем проблема.Дайте KoGrid вид. Он разумно управляет рендерингом вашей строки, чтобы он был более производительным.
Если вы пытаетесь привязать 400 строк к таблице с помощью
foreach
привязки, у вас возникнут проблемы с тем, чтобы протолкнуть так много строк через KO в DOM.KO делает некоторые очень интересные вещи, используя
foreach
привязку, большинство из которых являются очень хорошими операциями, но они начинают ломаться при производительности по мере роста размера вашего массива.Я прошел длинный темный путь, пытаясь привязать большие наборы данных к таблицам / сеткам, и в итоге вам нужно было разбить / разбить данные на локальную страницу.
KoGrid все это делает. Он был построен, чтобы отображать только те строки, которые зритель может видеть на странице, а затем виртуализировать другие строки, пока они не понадобятся. Я думаю, вы найдете его производительность по 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 строк загружались за 5973 мс (~ 6 секунд). Благодаря этой оптимизации время загрузки сократилось до 914 мс (<1 секунды),
что на 84,7% лучше!
Дополнительная информация в разделе Отправка элементов в 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
автоматически.если у вас есть время, взгляните на это и на всякий случай дайте мне знать
Уловка здесь: поступая так, если в случае каких-либо зависимостей (вычисленных, подписок и т. Д.) Можно избежать на уровне push, и мы можем заставить их выполняться за один раз после вызова
(4)
.источник
push
, проблема в том, что даже один вызов push приведет к долгому времени рендеринга. Если массив имеет 1000 элементов, привязанных к aforeach
, нажатие одного элемента выполняет повторную визуализацию всего foreach, и вы платите большие временные затраты на визуализацию.Возможный обходной путь, в сочетании с использованием 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 } }
Таким образом, когда вы добавляете только один элемент за раз, browser / knockout.js может не торопиться, чтобы соответствующим образом манипулировать DOM, без полной блокировки браузера на несколько секунд, чтобы пользователь мог одновременно прокручивать список.
источник
Я экспериментировал с производительностью и сделал два вклада, которые, надеюсь, могут быть полезны.
Мои эксперименты сосредоточены на времени манипуляции с DOM. Поэтому, прежде чем вдаваться в это, определенно стоит выполнить приведенные выше пункты о вставке в массив JS перед созданием наблюдаемого массива и т. Д.
Но если время манипуляции с DOM все еще мешает вам, это может помочь:
1: шаблон для обертывания счетчика загрузки вокруг медленного рендера, а затем скрытия его с помощью afterRender
http://jsfiddle.net/HBYyL/1/
На самом деле это не решение проблемы с производительностью, но показывает, что задержка, вероятно, неизбежна, если вы перебираете тысячи элементов, и он использует шаблон, в котором вы можете убедиться, что у вас появится счетчик загрузки перед длительной операцией KO, а затем скрыть это потом. По крайней мере, это улучшает UX.
Убедитесь, что вы можете загрузить счетчик:
// 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, но если вам действительно нужна невероятная производительность, это возможное обходное решение.
http://jsfiddle.net/9ZF3g/5/
источник
Если вы используете IE, попробуйте закрыть инструменты разработчика.
Открытие инструментов разработчика в IE значительно замедляет эту операцию. Я добавляю в массив ~ 1000 элементов. Когда инструменты разработчика открыты, это занимает около 10 секунд, и IE зависает, пока это происходит. Когда я закрываю инструменты разработчика, операция выполняется мгновенно, и я не вижу замедления в IE.
источник
Я также заметил, что шаблонизатор Knockout js работает медленнее в IE, я заменил его на underscore.js, работает намного быстрее.
источник