Saya baru saja memulai dengan Knockout.js (selalu ingin mencobanya, tetapi sekarang saya akhirnya punya alasan!) - Namun, saya mengalami beberapa masalah kinerja yang sangat buruk saat mengikat tabel ke kumpulan yang relatif kecil. data (sekitar 400 baris atau lebih).
Dalam model saya, saya memiliki kode berikut:
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!
}
};
Masalahnya adalah for
loop di atas membutuhkan waktu sekitar 30 detik atau lebih dengan sekitar 400 baris. Namun, jika saya mengubah kodenya menjadi:
this.loadData = function (data)
{
var testArray = []; //<-- Plain ol' Javascript array
for(var i = 0; i < data.length; i++)
{
testArray.push(new ResultRow(data[i]));
}
};
Kemudian for
loop selesai dalam sekejap mata. Dengan kata lain, push
metode objek Knockout observableArray
sangat lambat.
Ini template saya:
<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>
Pertanyaan saya:
- Apakah ini cara yang tepat untuk mengikat data saya (yang berasal dari metode AJAX) ke koleksi yang dapat diamati?
- Saya berharap
push
melakukan beberapa penghitungan ulang berat setiap kali saya menyebutnya, seperti mungkin membangun kembali objek DOM yang terikat. Adakah cara untuk menunda penarikan kembali ini, atau mungkin memasukkan semua item saya sekaligus?
Saya dapat menambahkan lebih banyak kode jika diperlukan, tetapi saya cukup yakin inilah yang relevan. Untuk sebagian besar saya hanya mengikuti tutorial Knockout dari situs.
MEMPERBARUI:
Sesuai saran di bawah ini, saya telah memperbarui kode saya:
this.loadData = function (data)
{
var mappedData = $.map(data, function (item) { return new ResultRow(item) });
this.projects(mappedData);
};
Namun, this.projects()
masih membutuhkan waktu sekitar 10 detik untuk 400 baris. Saya akui, saya tidak yakin seberapa cepat ini tanpa Knockout (hanya menambahkan baris melalui DOM), tetapi saya merasa itu akan jauh lebih cepat dari 10 detik.
UPDATE 2:
Berdasarkan saran lain di bawah ini, saya memberi jQuery.tmpl kesempatan (yang secara native didukung oleh KnockOut), dan mesin templating ini akan menarik sekitar 400 baris hanya dalam waktu 3 detik. Ini sepertinya pendekatan terbaik, singkat dari solusi yang akan secara dinamis memuat lebih banyak data saat Anda menggulir.
sumber
valueHasMutated
melakukannya. periksa jawabannya jika Anda punya waktu.Jawaban:
Seperti yang disarankan di komentar.
Knockout memiliki mesin template asli sendiri yang terkait dengan binding (foreach, with). Ini juga mendukung mesin template lain, yaitu jquery.tmpl. Baca di sini untuk lebih jelasnya. Saya belum melakukan pembandingan dengan mesin yang berbeda jadi tidak tahu apakah itu akan membantu. Membaca komentar Anda sebelumnya, di IE7 Anda mungkin kesulitan untuk mendapatkan kinerja yang Anda cari.
Selain itu, KO mendukung mesin templating js, jika seseorang telah menulis adaptor untuk itu. Anda mungkin ingin mencoba yang lain di luar sana karena jquery tmpl akan digantikan oleh JsRender .
sumber
jquery.tmpl
jadi saya akan menggunakannya. Saya mungkin akan menyelidiki mesin lain dan juga menulis mesin saya sendiri jika saya memiliki waktu ekstra. Terima kasih!data-bind
pernyataan dalam template jQuery Anda, atau apakah Anda menggunakan sintaks $ {code}?${code}
sintaks dan jauh lebih cepat. Saya juga telah mencoba untuk membuat Underscore.js bekerja, tetapi belum beruntung (<% .. %>
sintaksnya mengganggu ASP.NET), dan sepertinya belum ada dukungan JsRender.ResultRow
, itu tidak akan memperbarui UI (Anda harus memperbaruiprojects
observableArray yang akan memaksa rendering ulang tabel Anda). $ {} pasti bisa menguntungkan jika data Anda cukup banyak hanya untuk dibacaSilakan lihat: Knockout.js Performance Gotcha # 2 - Memanipulasi observableArrays
sumber
Gunakan pagination dengan KO selain menggunakan $ .map.
Saya memiliki masalah yang sama dengan kumpulan data besar 1400 catatan sampai saya menggunakan paging dengan sistem gugur. Menggunakan
$.map
untuk memuat catatan memang membuat perbedaan besar tetapi waktu render DOM masih mengerikan. Kemudian saya mencoba menggunakan pagination dan itu membuat pencahayaan dataset saya cepat dan juga lebih ramah pengguna. Ukuran halaman 50 membuat kumpulan data tidak terlalu berlebihan dan mengurangi jumlah elemen DOM secara dramatis.Ini sangat mudah dilakukan dengan KO:
http://jsfiddle.net/rniemeyer/5Xr2X/
sumber
KnockoutJS memiliki beberapa tutorial bagus, terutama tentang memuat dan menyimpan data
Dalam kasus mereka, mereka menarik data menggunakan
getJSON()
yang sangat cepat. Dari contoh mereka: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); }); }
sumber
self.tasks(mappedTasks)
membutuhkan waktu sekitar 10 detik untuk berjalan (dengan 400 baris). Saya merasa ini masih belum bisa diterima.+1
untuk menyederhanakan kode saya dan meningkatkan kecepatan secara dramatis. Mungkin seseorang memiliki penjelasan yang lebih detail tentang apa itu bottleneck.Berikan KoGrid lihat. Ini dengan cerdas mengelola rendering baris Anda sehingga lebih berkinerja.
Jika Anda mencoba mengikat 400 baris ke tabel menggunakan
foreach
pengikatan, Anda akan kesulitan mendorong sebanyak itu melalui KO ke DOM.KO melakukan beberapa hal yang sangat menarik menggunakan
foreach
pengikatan, sebagian besar merupakan operasi yang sangat baik, tetapi mereka mulai merusak kinerja seiring dengan bertambahnya ukuran array Anda.Saya telah melalui jalan gelap yang panjang untuk mencoba mengikat kumpulan data besar ke tabel / kisi, dan Anda akhirnya perlu memecah / halaman data secara lokal.
KoGrid melakukan ini semua. Ini dibuat untuk hanya merender baris yang dapat dilihat pemirsa di halaman, dan kemudian memvirtualisasikan baris lain hingga diperlukan. Saya pikir Anda akan menemukan bahwa kinerja 400 item jauh lebih baik daripada yang Anda alami.
sumber
Solusi untuk menghindari penguncian browser saat merender array yang sangat besar adalah dengan 'membatasi' array sedemikian rupa sehingga hanya beberapa elemen yang ditambahkan pada satu waktu, dengan sleep di antaranya. Inilah fungsi yang akan melakukan hal itu:
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; }
Bergantung pada kasus penggunaan Anda, ini dapat menghasilkan peningkatan UX besar-besaran, karena pengguna mungkin hanya melihat kumpulan baris pertama sebelum harus menggulir.
sumber
Mengambil keuntungan dari push () menerima argumen variabel memberikan kinerja terbaik dalam kasus saya. 1300 baris dimuat selama 5973ms (~ 6 detik). Dengan pengoptimalan ini, waktu muat turun menjadi 914ms (<1 detik).
Itu berarti peningkatan 84,7%!
Info selengkapnya di Mendorong item ke 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); };
sumber
Saya telah berurusan dengan data dalam jumlah besar yang masuk untuk saya
valueHasMutated
bekerja seperti pesona.Lihat 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)
Setelah memanggil
(4)
data array akan dimuat ke observableArray yang dibutuhkanthis.projects
secara otomatis.jika Anda punya waktu, lihat ini dan jika ada masalah beri tahu saya
Trik di sini: Dengan melakukan seperti ini, jika dalam kasus dependensi (dihitung, berlangganan dll) dapat dihindari pada level push dan kita dapat membuatnya dieksekusi sekaligus setelah dipanggil
(4)
.sumber
push
, masalahnya adalah bahwa bahkan satu panggilan untuk mendorong akan menyebabkan waktu render yang lama. Jika sebuah array memiliki 1000 item yang terikat ke aforeach
, mendorong satu item merender keseluruhan foreach dan Anda membayar biaya waktu render yang besar.Solusi yang mungkin, dalam kombinasi dengan menggunakan jQuery.tmpl, adalah dengan mendorong item pada suatu waktu ke array yang dapat diamati secara asinkron, menggunakan 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 } }
Dengan cara ini, saat Anda hanya menambahkan satu item dalam satu waktu, browser / knockout.js dapat menggunakan waktu untuk memanipulasi DOM sesuai, tanpa browser diblokir sepenuhnya selama beberapa detik, sehingga pengguna dapat menggulir daftar secara bersamaan.
sumber
Saya telah bereksperimen dengan kinerja, dan memiliki dua kontribusi yang saya harap dapat berguna.
Eksperimen saya fokus pada waktu manipulasi DOM. Jadi sebelum masuk ke ini, ada baiknya mengikuti poin-poin di atas tentang mendorong ke dalam array JS sebelum membuat array yang dapat diamati, dll.
Tetapi jika waktu manipulasi DOM masih menghalangi Anda, ini mungkin membantu:
1: Pola untuk membungkus spinner pemuatan di sekitar rendering lambat, lalu menyembunyikannya menggunakan afterRender
http://jsfiddle.net/HBYyL/1/
Ini sebenarnya bukan perbaikan untuk masalah kinerja, tetapi menunjukkan bahwa penundaan mungkin tidak terhindarkan jika Anda mengulang ribuan item dan ini menggunakan pola di mana Anda dapat memastikan Anda memiliki pemintal pemuatan muncul sebelum operasi KO yang lama, lalu sembunyikan itu sesudahnya. Jadi setidaknya itu meningkatkan UX.
Pastikan Anda dapat memuat 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)
Sembunyikan spinner:
<div data-bind="template: {afterRender: hide}">
yang memicu:
hide = function() { $("#spinner").hide() }
2: Menggunakan pengikatan html sebagai peretasan
Saya teringat teknik lama saat saya mengerjakan set top box dengan Opera, membuat UI menggunakan manipulasi DOM. Itu sangat lambat, jadi solusinya adalah menyimpan potongan besar HTML sebagai string, dan memuat string dengan menyetel properti innerHTML.
Hal serupa dapat dicapai dengan menggunakan pengikatan html dan penghitungan yang menurunkan HTML untuk tabel sebagai potongan besar teks, lalu menerapkannya sekaligus. Ini memperbaiki masalah kinerja, tetapi kerugian besar adalah sangat membatasi apa yang dapat Anda lakukan dengan mengikat di dalam setiap baris tabel.
Berikut biola yang menunjukkan pendekatan ini, bersama dengan fungsi yang bisa dipanggil dari dalam baris tabel untuk menghapus item dengan cara yang samar-samar seperti KO. Jelas ini tidak sebaik KO yang tepat, tetapi jika Anda benar-benar membutuhkan kinerja yang luar biasa, ini adalah solusi yang mungkin.
http://jsfiddle.net/9ZF3g/5/
sumber
Jika menggunakan IE, coba tutup alat dev.
Membuka alat pengembang di IE secara signifikan memperlambat operasi ini. Saya menambahkan ~ 1000 elemen ke sebuah array. Saat alat pengembang terbuka, ini membutuhkan waktu sekitar 10 detik dan IE berhenti saat itu terjadi. Ketika saya menutup alat dev, operasinya instan dan saya tidak melihat ada yang melambat di IE.
sumber
Saya juga memperhatikan bahwa mesin template Knockout js bekerja lebih lambat di IE, saya menggantinya dengan underscore.js, bekerja lebih cepat.
sumber