Knockout.js, yarı büyük veri kümelerinde inanılmaz derecede yavaş


86

Nakavt. veri (yaklaşık 400 satır).

Modelimde aşağıdaki koda sahibim:

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

Sorun, foryukarıdaki döngünün yaklaşık 400 satırla yaklaşık 30 saniye sürmesidir. Ancak kodu şu şekilde değiştirirsem:

this.loadData = function (data)
{
   var testArray = []; //<-- Plain ol' Javascript array
   for(var i = 0; i < data.length; i++)
   {
      testArray.push(new ResultRow(data[i]));
   }
};

Ardından fordöngü göz açıp kapayıncaya kadar tamamlanır. Başka bir deyişle, pushKnockout'un observableArraynesnesinin yöntemi inanılmaz derecede yavaştır.

İşte şablonum:

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

Sorularım:

  1. Verilerimi (AJAX yönteminden gelen) gözlemlenebilir bir koleksiyona bağlamanın doğru yolu bu mu?
  2. Sanırım pushonu her çağırdığımda yoğun bir yeniden hesaplama yapıyor, örneğin bağlı DOM nesnelerini yeniden inşa etmek gibi. Ya bu itirazı ertelemenin bir yolu var mı, yoksa tüm eşyalarımı aynı anda içeri itmenin bir yolu var mı?

Gerekirse daha fazla kod ekleyebilirim, ancak konuyla ilgili olanın bu olduğundan oldukça eminim. Çoğunlukla, sitedeki Nakavt eğitimlerini takip ediyordum.

GÜNCELLEME:

Aşağıdaki tavsiyeye göre kodumu güncelledim:

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

Ancak this.projects()yine de 400 satır için yaklaşık 10 saniye sürer. Nakavt olmadan bunun ne kadar hızlı olacağından emin değilim (sadece DOM aracılığıyla satırlar ekleyerek), ancak 10 saniyeden çok daha hızlı olacağını hissediyorum.

GÜNCELLEME 2:

Aşağıdaki diğer tavsiyelere göre, jQuery.tmpl'e bir şans verdim (doğal olarak KnockOut tarafından destekleniyor) ve bu şablon oluşturma motoru 3 saniyeden fazla bir sürede yaklaşık 400 satır çekecek. Bu, siz kaydırdıkça daha fazla veriyi dinamik olarak yükleyecek bir çözümün yanı sıra en iyi yaklaşım gibi görünüyor.


1
Nakavt foreach bağlama mı yoksa foreach ile şablon ciltleme mi kullanıyorsunuz? Merak ediyorum, şablon kullanmanın ve yerel şablon motoru yerine jquery tmpl eklemenin bir fark yaratıp yaratmayacağını merak ediyorum.
madcapnmckay

1
@MikeChristensen - Knockout, (foreach, with) bağlamalarla ilişkili kendi yerel şablon motoruna sahiptir. Ayrıca, jquery.tmpl gibi diğer şablon motorlarını da destekler. Daha fazla ayrıntı için burayı okuyun . Farklı motorlarla herhangi bir kıyaslama yapmadım, bu yüzden yardımcı olup olmayacağını bilmiyorum. Önceki yorumunuzu okurken, IE7'de peşinde olduğunuz performansı elde etmekte zorlanabilirsiniz.
madcapnmckay

2
Birkaç ay önce IE7 aldığımızı düşünürsek, sanırım IE9 2019 yaz civarında piyasaya sürülecek. Oh, hepimiz WinXP kullanıyoruz .. Blech.
Mike Christensen

1
ps, Yavaş görünmesinin nedeni, gözlemlenebilir diziye ayrı ayrı 400 öğe eklemenizdir . Gözlemlenebilirdeki her değişiklik için, bu diziye bağlı olan her şey için görünümün yeniden oluşturulması gerekir. Karmaşık şablonlar ve eklenecek birçok öğe için, diziyi farklı bir örneğe ayarlayarak tek seferde güncelleyebileceğiniz zaman bu çok fazla ek yüktür. En azından o zaman yeniden oluşturma bir kez yapılacaktır.
Jeff Mercado

1
Daha hızlı ve temiz bir yol buldum (kutunun dışında hiçbir şey yok). kullanıyor valueHasMutated. zamanın varsa cevabı kontrol et.
süper serin

Yanıtlar:


16

Yorumlarda önerildiği gibi.

Nakavt, bağlamalarla (foreach, with) ilişkilendirilmiş kendi yerel şablon motoruna sahiptir. Ayrıca, jquery.tmpl gibi diğer şablon motorlarını da destekler. Burada okuyunDaha fazla ayrıntı için . Farklı motorlarla herhangi bir kıyaslama yapmadım, bu yüzden yardımcı olup olmayacağını bilmiyorum. Önceki yorumunuzu okurken, IE7'de peşinde olduğunuz performansı elde etmekte zorlanabilirsiniz.

Bir kenara, KO herhangi bir js şablonlama motorunu destekler, eğer birisi adaptörü onun için yazdıysa. Jquery tmpl'nin yerini JsRender alacağından , diğerlerini denemek isteyebilirsiniz .


Daha iyi performans elde ediyorum, jquery.tmplbu yüzden bunu kullanacağım. Fazladan zamanım varsa, diğer motorları araştırabilir ve kendi motorumu yazabilirim. Teşekkürler!
Mike Christensen

1
@MikeChristensen - data-bindjQuery şablonunuzda hâlâ ifadeler mi kullanıyorsunuz yoksa $ {code} sözdizimini mi kullanıyorsunuz?
ericb

@ericb - Yeni kodla, ${code}sözdizimi kullanıyorum ve çok daha hızlı. Ayrıca Underscore.js'yi çalıştırmaya çalışıyorum, ancak henüz şansım olmadı ( <% .. %>sözdizimi ASP.NET ile çakışıyor) ve henüz JsRender desteği görünmüyor.
Mike Christensen

1
@MikeChristensen - tamam, o zaman bu mantıklı. KO'nun yerel şablon motoru mutlaka o kadar verimsiz değildir. $ {Kod} sözdizimini kullandığınızda, bu öğeler üzerinde herhangi bir veri bağlama işlemi elde edemezsiniz (bu da performansı artırır). Bu nedenle, a'nın bir özelliğini değiştirirseniz ResultRow, kullanıcı arayüzünü güncellemeyecektir ( projectstablonuzun yeniden oluşturulmasını zorlayacak gözlemlenebilir Diziyi güncellemeniz gerekir). Verileriniz salt
okunursa

4
Necromancy! jquery.tmpl artık geliştirilmiyor
Alex Larzelere


13

Sayfalandırmayı kullan$ .Map kullanmaya ek olarak KO ile .

Knockout ile sayfalamayı kullanana kadar 1400 kayıtlık büyük veri kümelerinde aynı sorunu yaşadım. Kullanma$.mapKayıtları yüklemek için büyük bir fark yarattı, ancak DOM oluşturma süresi yine de çirkindi. Sonra sayfalamayı kullanmayı denedim ve bu, veri kümemi aydınlatmamın hızlı ve kullanıcı dostu olmasını sağladı. 50 sayfa boyutu, veri kümesini çok daha az ezici hale getirdi ve DOM öğelerinin sayısını önemli ölçüde azalttı.

KO ile yapmak çok kolay:

http://jsfiddle.net/rniemeyer/5Xr2X/


11

KnockoutJS, özellikle veri yükleme ve kaydetme ile ilgili bazı harika eğitimlere sahiptir.

Onların durumunda, getJSON()son derece hızlı olan verileri çekerler . Örneklerinden:

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

1
Kesinlikle büyük bir gelişme, ancak self.tasks(mappedTasks)çalışması yaklaşık 10 saniye sürüyor (400 sıra ile). Bunun hala kabul edilemez olduğunu düşünüyorum.
Mike Christensen

10 saniyenin kabul edilmediğine katılıyorum. Nakavtları kullanarak haritadan neyin daha iyi olduğundan emin değilim, bu yüzden bu soruyu seveceğim ve daha iyi bir cevap bekleyeceğim.
deltree

1
Tamam. Cevap +1, hem kodumu basitleştirmek hem de hızı önemli ölçüde artırmak için kesinlikle bir hak ediyor . Belki birisinin darboğazın ne olduğuna dair daha ayrıntılı bir açıklaması vardır.
Mike Christensen

9

Ver KoGrid bir göz. Satır oluşturma işleminizi akıllıca yöneterek daha performanslı olmasını sağlar.

400 satırı bir tabloya bağlamaya çalışıyorsanız foreach bağlama KO yoluyla DOM'a bu kadarını iletmekte sorun yaşarsınız.

KO, foreachbağlama kullanarak bazı çok ilginç şeyler yapar , bunların çoğu çok iyi işlemlerdir, ancak dizinizin boyutu büyüdükçe bunlar mükemmel olarak bozulmaya başlar.

Büyük veri kümelerini tablolara / ızgaralara bağlamanın uzun ve karanlık yolundan geçtim ve sonunda verileri yerel olarak bölmek / sayfalandırmak zorunda kalıyorsunuz.

KoGrid hepsini yapıyor. Yalnızca izleyicinin sayfada görebileceği satırları oluşturmak ve ardından diğer satırları ihtiyaç duyulana kadar sanallaştırmak için oluşturulmuştur. 400 üründeki performansını deneyimlediğinizden çok daha iyi bulacağınızı düşünüyorum.


1
Bu, IE7'de tamamen bozuk görünüyor (örneklerin hiçbiri çalışmıyor), aksi takdirde bu harika olurdu!
Mike Christensen

Baktığımıza sevindim - KoGrid hala aktif geliştirme aşamasında. Ancak, bu en azından performansla ilgili sorunuza cevap veriyor mu?
ericb

1
Evet! Varsayılan KO şablon motorunun oldukça yavaş olduğu konusundaki ilk şüphemi doğruluyor. KoGrid'i kobay için birisine ihtiyacın olursa, memnun olurum. Tam olarak ihtiyacımız olan şey gibi görünüyor!
Mike Christensen

Lanet olsun. Bu gerçekten güzel görünüyor! Ne yazık ki, uygulamamın kullanıcılarının% 50'sinden fazlası IE7 kullanıyor!
Jim G.

İlginç, bugünlerde IE11'i gönülsüzce desteklemek zorundayız. Son 7 yılda işler gelişti.
MrBoJangles

5

Çok büyük bir dizi oluştururken tarayıcıyı kilitlemekten kaçınmanın bir çözümü, diziyi, arada bir uyku ile bir seferde yalnızca birkaç öğe eklenecek şekilde 'daraltmaktır'. İşte tam da bunu yapacak bir işlev:

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

Kullanıcı, kaydırma yapmadan önce yalnızca ilk satır grubunu görebileceğinden, kullanım durumunuza bağlı olarak bu, büyük bir UX iyileştirmesine neden olabilir.


Bu çözümü beğendim, ancak her yinelemede setTimeout yerine yalnızca setTimout'u her 20 veya daha fazla yinelemede çalıştırmanızı öneririm çünkü her seferinde yüklenmesi çok uzun sürer. Bunu +20 ile yaptığınızı görüyorum, ancak ilk bakışta benim için açık değildi.
charlierlee

5

Değişken bağımsız değişkenleri kabul eden push () avantajından yararlanmak benim durumumda en iyi performansı verdi. 5973 ms (~ 6 saniye) için 1300 satır yükleniyordu. Bu optimizasyonla yükleme süresi 914 ms'ye (<1 sn.) Düştü
Bu% 84,7 iyileşme!

Öğeleri gözlemlenebilir bir diziye gönderme konusunda daha fazla bilgi

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

4

Benim için gelen bu kadar büyük hacimli verilerle uğraşmak valueHasMutatedbir cazibe gibi çalıştı.

Modeli Görüntü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) 

(4)Dizi verisi çağrıldıktan sonra , this.projectsotomatik olarak gerekli olan observableArray'e yüklenecektir .

zamanın varsa buna bir bak ve herhangi bir sorun olursa bana haber ver

Buradaki püf noktası: Bunu yaparak, herhangi bir bağımlılık durumunda (hesaplanan, abone vb.) Push seviyesinde önlenebilir ve çağrıdan sonra bir seferde onları çalıştırabiliriz (4).


1
Sorun, çok fazla çağrı değil push, tek bir push çağrısı bile uzun render sürelerine neden olacak. Bir dizide foreacha'ya bağlı 1000 öğe varsa , tek bir öğenin itilmesi tüm foreach'i yeniden oluşturur ve büyük bir oluşturma süresi maliyeti ödersiniz.
Hafif

1

JQuery.tmpl kullanımıyla birlikte olası bir çözüm, öğeleri setTimeout kullanarak eşzamansız bir şekilde gözlemlenebilir diziye bir seferde göndermektir;

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

Bu şekilde, bir seferde yalnızca tek bir öğe eklediğinizde, browser / knockout.js, tarayıcının birkaç saniye boyunca tamamen engellenmesine gerek kalmadan, kullanıcının listeyi eşzamanlı olarak kaydırabilmesi için DOM'u uygun şekilde işlemek için zaman alabilir.


2
Bu, N sayıda DOM güncellemesini zorlayacak ve her şeyi aynı anda yapmaktan çok daha uzun olan toplam oluşturma süresine neden olacaktır.
Fredrik C

Bu elbette doğrudur. Bununla birlikte, önemli olan, N'nin büyük bir sayı olması ve bir öğeyi projeler dizisine itmenin önemli miktarda başka DOM güncellemelerini veya hesaplamaları tetiklemesinin, tarayıcının donmasına ve sekmeyi kapatmanızı teklif etmesidir. Her öğe için veya her 10, 100 veya başka sayıda öğe başına bir zaman aşımı olması durumunda, tarayıcı yine de yanıt verir.
gnab

2
Toplam güncellemenin tarayıcıyı dondurmayacağı genel durumda bunun yanlış bir yaklaşım olduğunu söyleyebilirim, ancak diğer tüm başarısız olduğunda kullanılacak bir şeydir. Bana göre, performans sorunlarının sadece donmaması yerine çözülmesi gereken kötü yazılmış bir uygulama gibi geliyor.
Fredrik C

1
Elbette genel durumda bu yanlış bir yaklaşımdır, bu konuda kimse size karşı çıkmaz. Bu, çok sayıda DOM işlemi yapmanız gerektiğinde tarayıcının donmasını önlemek için bir hack ve kavram kanıtıdır. Birkaç yıl önce, hücre başına birkaç bağlama içeren birkaç büyük HTML tablosunu listelerken buna ihtiyacım vardı, bu da her biri DOM'un durumunu etkileyen binlerce bağlamanın değerlendirilmesiyle sonuçlandı. Excel tabanlı bir masaüstü uygulamasının bir web uygulaması olarak yeniden uygulanmasının doğruluğunu onaylamak için işlevselliğe geçici olarak ihtiyaç duyuldu. Sonra bu çözüm mükemmel bir şekilde çalıştı.
gnab

Yorum çoğunlukla başkalarının okuması ve bunun tercih edilen yol olduğunu varsaymamaları içindi. Ne yaptığını bildiğini sanıyordum.
Fredrik C

1

Performansı deniyorum ve yararlı olacağını umduğum iki katkım var.

Deneylerim, DOM işleme süresine odaklanıyor. Bu nedenle, buna girmeden önce, gözlemlenebilir bir dizi oluşturmadan önce bir JS dizisine itmekle ilgili yukarıdaki noktaları kesinlikle takip etmeye değer.

Ancak DOM işleme zamanı hala yolunuza çıkıyorsa, bu size yardımcı olabilir:


1: Yükleme döndürücüsünü yavaş oluşturmanın etrafına sarmak ve sonra afterRender'ı kullanarak gizlemek için bir desen

http://jsfiddle.net/HBYyL/1/

Bu, performans sorunu için gerçekten bir çözüm değildir, ancak binlerce öğe üzerinde döngü yaparsanız ve uzun KO işleminden önce bir yükleme döndürücüsünün görünmesini sağlayabileceğiniz bir model kullanırsa, gecikmenin muhtemelen kaçınılmaz olduğunu gösterir. daha sonra. Yani en azından UX'i iyileştiriyor.

Bir döndürücü yükleyebileceğinizden emin olun:

// 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)

Çarkı gizle:

<div data-bind="template: {afterRender: hide}">

hangi tetikler:

hide = function() {
    $("#spinner").hide()
}

2: html bağlamayı hack olarak kullanma

Opera ile set üstü kutu üzerinde çalışırken, DOM manipülasyonunu kullanarak kullanıcı arayüzü oluştururken eski bir tekniği hatırladım. Korkunç yavaştı, bu nedenle çözüm, büyük HTML parçalarını dizeler olarak depolamak ve dizeleri innerHTML özelliğini ayarlayarak yüklemekti.

Benzer bir şey, html bağlama ve tablo için HTML'yi büyük bir metin parçası olarak türeten ve daha sonra tek seferde uygulayan bir hesaplama kullanılarak elde edilebilir. Bu, performans sorununu çözer, ancak büyük dezavantajı, her tablo satırının içinde ciltleme ile yapabileceklerinizi ciddi şekilde sınırlandırmasıdır.

İşte bu yaklaşımı, bir öğeyi belirsiz bir KO benzeri bir şekilde silmek için tablo satırlarının içinden çağrılabilen bir işlevle birlikte gösteren bir keman. Açıkçası bu, uygun KO kadar iyi değil, ancak gerçekten parlak (ish) performansa ihtiyacınız varsa, bu olası bir geçici çözümdür.

http://jsfiddle.net/9ZF3g/5/


1

IE kullanıyorsanız, geliştirme araçlarını kapatmayı deneyin.

Geliştirici araçlarının IE'de açık olması bu işlemi önemli ölçüde yavaşlatır. Bir diziye ~ 1000 öğe ekliyorum. Dev araçlarını açarken, bu yaklaşık 10 saniye sürer ve IE bu işlem sırasında donar. Dev araçlarını kapattığımda, işlem anında gerçekleşir ve IE'de yavaşlama görmüyorum.


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.