JavaScript'im Tarayıcınızı Neredeyse Nasıl Çökertti ve Nasıl Düzeltdim?


WP Migrate DB Pro 1.6'yı piyasaya sürmeden yaklaşık bir hafta önce, gevşek kanalımızda Ashley Rich tarafından kalbim kırıldı. Bunun gibi bir şey gitti:

/ ——dramatizasyon—— /
@bradt: 1.6 kullanıma hazır mı?
@jrgould: Evet! Gitmeye hazırız! Kesinlikle hiçbir şey yanlış gidemez.
@a5hleyrich: Hey @jrgould, yeni kullanıcı arayüzü harika görünüyor ama çok fazla medyam olduğunda çöküyor
@jrgould: Ne? Hayır. 1000 medya dosyasıyla test ettim. Yeni bir dizüstü bilgisayar alın.
@a5hleyrich: 10k ataşmanım var ve kromu kilitliyor.
@jrgould: Hayır…
@a5hleyrich: Üzgünüm dostum.
@jrgould: 😢

Sızıntıyı Bulma

Ardından, tarayıcıların taramayı yavaşlatmasına ve çok büyük miktarda medya dosyası taşınırken genellikle kilitlenmesine neyin neden olduğu konusunda herkes tarafından epeyce spekülasyon yapıldı. Açıkça bu bir hafıza sorunu, ama buna ne sebep oluyor?

İlk olarak, biraz arka plan. WP Migrate DB Pro'nun yeni kullanıcı arayüzünü görmüş olabilirsiniz, ancak muhtemelen kodu incelememişsinizdir. Sizi buna zorlamayacağım, ancak ilgilenenler için, CodePen üzerinde ilk çalışmaya başladığımda bir araya getirdiğim erken bir "taslak":

CodePen'de JRGould (@JRGould) tarafından kaleme alınan Kalem Omurgasına bakın.

Geçiş ilerleme kullanıcı arabirimi, Omurga modelleri ve görünümleri kullanılarak oluşturulmuştur. Her ilerleme çubuğu bir görünüm ve bir modelle temsil edildi. Bir taşımadaki (yedekleme, taşıma, medya) her bir "aşama" aynı zamanda tüm bireysel ilerleme çubuklarını içeren ve yöneten bir Omurga görünümü ve modeli ile temsil edilir. Son olarak, bir bütün olarak geçiş, aşamaları içeren ve yöneten tek bir Omurga görünümü ve modeli ile temsil edilir. Taşımayı gerçekten çalıştırmak için Backbone'u kullanmıyoruz, bu nedenle modeller daha çok görünüm modelleri gibi ele alınıyor ve koleksiyonları veya Backbone'un Yönlendirici veya Eşitleme modülleri gibi daha uygulama odaklı özelliklerinden herhangi birini kullanmıyoruz.

Yeni kullanıcı arayüzü, taşıdığımız her medya dosyası için küçük resimler içeren bir ilerleme çubuğu gösterir, böylece tümü DOM'a eklenen 30-50 bin ilerleme çubuğu olan 10k medya dosyaları bulunur. Bu kadar çok bellek kullanan ve işleri yavaşlatan şeyin bu olması gerektiğini düşünerek, koda daldım ve görüşlerimin ilerleme çubuklarını görüntüleme şeklini yeniden düzenledim, böylece DOM'a herhangi bir zamanda yalnızca birkaç yüz düğüm eklenecek, gerisi olacaktır. DOM'dan çıkarılır ve sorgulanabilmeleri için saklanır. Sürprizime ve dehşetime göre, bu pek yardımcı olmadı.

Bir sonraki adım, Backbone'un kendisini suçlamaktı. WordPress yöneticisine eklendiği için kullandım, ancak eski ve muhtemelen aptal ve geriatrik aptallığı nedeniyle çok fazla bellek kullanıyor.

Bu yüzden Backbone'u yeniden yazdım.

Tamam, belki Backbone'u yeniden yazmadım. Ancak, onları tek bir nesneye saran Omurga modeli ve görünümü uygulamamın yerine bir yedek yazdım. Bu yeni model prototipi, Backbone modelini genişletmek ve set , get , on , off ve trigger gibi bazı temel yöntemleri içeren görünümü extend için yazdığım kodun bir kısmını yeniden kullanmama izin verdi, şöyle bir şeye benziyordu:

 myModel = function( userProps ) { var ret = { attributes: { }, events: {}, get: function( prop ) { return this.attributes[ prop ]; }, set: function( prop, val ) { this.trigger('change', prop, this.attributes[prop], val) ; this.trigger('change:' + prop, prop, this.attributes[prop], val) ; return this.attributes[ prop ] = val; }, on: function( event, callback ) { if ( ! this.events[ event ] ) { this.events[ event ] = []; } this.events[ event ].push( callback ) }, off: function( event, callback ) { if ( ! this.events[ event ] ) { return } var callbacks = this.events[ event ]; if ( 'function' === typeof callback ) { var index = callbacks.indexOf( callback ); if ( -1 !== index ) { callbacks.splice( index, 1); this.events[ event ] = callbacks; return; } } else { this.events[ event ] = [] return; } }, trigger: function( event ) { var self = this; var args = Array.prototype.slice.apply( arguments, [1] ); if( this.events[ event ] && this.events[ event ].length ) { var eventCallbacks = this.events[ event ].slice(); while ( eventCallbacks.length ) { var ev = eventCallbacks.shift(); ev.apply(self, args); } } }, }; if ( 'object' === typeof userProps ) { _.each( userProps, function( prop, index ){ ret[index] = prop; }, this ); } return ret; };

Bu da işe yaramadı. Bu aslında Omurga modelini ve görünümünü kullanırken olduğundan daha yavaş çalışıyor gibiydi, bu yüzden bu fikri bir kenara attım ve suçluyu aramaya devam ettim.

bu çöp

Oluşturulan DOM düğümlerinin miktarının çok büyük bir etkisi olmadığı ve Backbone'un bellek kullanımını şişirmediği için, JavaScript'te gerçekte bellek sızıntılarına neyin neden olduğu konusunda biraz araştırma yapmaya başladım. Kendimden daha akıllı geliştiriciler tarafından yazılan bazı makaleleri okudum ve çöp toplama denen bir şey hakkında epey bilgi edindim.

Chrome V8 gibi modern JavaScript motorları yüksek düzeyde optimize edilmiştir ve öncekilerden çok daha fazlasını işleyebilir (hepimiz çılgın parçacık demolarını gördük). Kullandıkları numaralardan biri, çok yeni olmasa da, çöp toplama olarak adlandırılır; bu, kullanılan değişkenler ve işlevler gibi şeylerden olabildiğince fazla bellek salmaya çalışan JS motorunun bir özelliğidir. bir nokta ama artık kullanılmıyor.

Peki bunun Omurga ile ne ilgisi var? Geri aramalar, üst nesnelerinin çöp olarak toplanmasını engeller. Bunun nedeni, bir geri aramanın ana nesnenin dışında çağrılmasına rağmen bağlamını korumaya devam etmesidir. Aşağıdakileri göz önünde bulundur:

 var myView = Backbone.View.extend( { init: function() { this.model.on( 'change', this.render, this ); }, render: function() { /* render the view */ } } );

Bu görünüm başlatıldığında, modelin change olayına bir geri arama kaydeder, geri aramayı taşıyan model çöp toplanana kadar tüm nesne çöp toplamadan çıkarılır. Çoğu durumda, bu iyidir, ama ya 50.000 modele bağlı 50.000 görüntülemeniz varsa ve hepsi de sahne modeline bağlıysa? Bu bazı sorunlara neden olacak…


JavaScript: Kullanışlı Parçalar

Her bir ilerleme çubuğunun kendi modeli ve görüntüleme nesneleri ile temsil edilmesi gerçekten güzeldi. Bu, geçiş ilerleme kullanıcı arabiriminin her bileşeninin bir nevi kendisini yönetmesine izin verirken, diğer her şey daha düşük düzeyli bir bileşende yapılan değişikliklere uygun şekilde tepki verirdi. Bu nesnelerin kaldırılması, aşama modelinin ve görünümün artık sırasıyla ilerleme çubuğu modelinin ve görünümün sorumluluklarını devralması gerektiği anlamına gelir. JavaScript'ten herhangi bir yöntem içermeyen 50.000 basit nesne dizisiyle ilgilenmesini istemek çok fazla olmamalı, ancak bu nesnelerdeki değişiklikleri nasıl takip edeceksiniz?

JS'de Array veya Object gibi bir nesne içeren herhangi bir değişkenin değer yerine referansla iletildiğini biliyor muydunuz? İşte bir örnek:

 function changeThings( var1, var2 ) { var1 = 12 * 2; var2.foo = 'changed'; var2 = ''; console.log( var1, var2 ); } function logThings( var1, var2 ){ console.log( var1, var2 ); } var myVal = 12; var myObj = { foo: 'bar', baz: 'bing' } logThings( myVal, myObj ); // 12, Object {foo: "bar", baz: "bing"} changeThings( myVal, myObj ); // 24, "" logThings( myVal, myObj ); // 12, Object {foo: "changed", baz: "bing"}

Bu, büyük bir bellek maliyeti olmayan gerçekten güzel şeyler yapmamızı sağlar. Yeniden düzenleme işleminden sonra ilerleme çubuğu nesnelerini içeren bir dizi buldum, şuna benziyor:

 items: [ { name: 'image001.jpg', size: 1024, transferred: 0 }, { name: 'image002.jpg', size: 1288, transferred: 0 }, ... ]

Ayrıca, Dizi işlevinden ödün vermeden o dizideki nesnelere name göre erişmeme izin veren bir "arama" nesnemiz var:

 itemsLookup: { 'image001.jpg': 0, 'image002.jpg' 1, ... }

Artık, modelimizin nesnelerin durumunu takip etme şeklini basitleştirmek için JavaScript'in nesneleri referans yoluyla ilettiği gerçeğini kullanabiliriz. Örneğin, sahne modeline bir ilerleme çubuğu eklemek şöyle görünebilir:

 addItem: function( itemObj ) { var item = _.clone( itemObj ); // Break ref to original obj this.get( 'items' ).push( item ); this.get( 'itemsLookup' )[ item.name ] = this.get( 'items' ).length - 1; this.trigger( 'item:added', item ); }

item kapsamı addItem yöntemine göre ayarlanmış olsa da ( var anahtar sözcüğü sayesinde) öğeyi doğrudan item:added olayına bağlanan herhangi bir geri çağrıya iletebiliriz, çünkü item gerçekten klonlanmış nesneye bir işaretçi tutar. Bu, bu olaya bağlanan herhangi bir geri aramanın yalnızca nesneyi okumak için erişmesine izin vermekle kalmaz, aynı zamanda tüm items dizisini almak zorunda kalmadan doğrudan değiştirebilir veya dizi içindeki öğeye erişmek için itemsLookup nesnesini kullanabilir. Bu, görünümde gerçek DOM öğesini oluşturmak gibi şeyler yapmak için gerçekten kullanışlıdır:

 // stageView.init() { ... this.model.on( 'item:added', function( item ) { if ( !item.$el ) { item.$el = $( '<div />' ).addClass( 'progressBar' ).css( 'width', item.transferred / item.size + '%' ); } }, this );

JavaScript'teki nesne işaretçilerinin bir başka ilginç yönü, tek bir nesnenin herhangi bir sayıda başka nesne tarafından kapsanabilmesidir. Bu, filtreleme veya sıralama yolunda fazla bir şey yapmanıza gerek kalmadan işleri takip etmek için kullanışlıdır. Örneğin, bir ilerleme çubuğu tamamlandı olarak işaretlendiğinde, öğesini DOM'den kaldırır ve daha sonra göstermemiz gerekebileceği ihtimaline karşı kuyruğa koyarız. Böyle bir şey şu şekilde yapılır:

 // stageView.init() { ... this.model.on( 'item:completed', function( item ) { item.$el.remove(); this.itemQueue.push( item.$el ); if ( 100 > this.$el.find( '.progressBar' ).length ) { var $elToShow = this.itemQueue.shift(); this.$el.append( $elToShow ); } }, this );

Burada DOM, "görünür" öğelerin sırası gibi davranır ve itemQueue , gizli öğelerin bir sırası gibi davranır. Bir öğe tamamlandığında, DOM'den kaldırılır ve itemQueue sonuna eklenirken, daha önce gizlenmiş bir öğe kuyruğun başından kaldırılır ve 100'den az görünür öğe varsa DOM'ye geri eklenir. Her ilerleme çubuğu öğesi, sahne modelinin items dizisindeki bir nesnenin yanı sıra DOM'nin kendisinde veya sahne alanı görünümünün itemQueue dizisinde aynı anda bulunur.

Bu, bu tür şeyleri olması gerektiğinden daha kolay hale getirmesi açısından kullanışlıdır ve erişmek için nesnenin kopyalarını almamız veya DOM veya items dizisini yinelememiz gerektiğinden çok daha fazla bellek verimlidir. çeşitli hareketli parçalar.

Toplama

Deneyimli bir programcı için, çöp toplama ve değişkenleri referans olarak iletmek yeni kavramlar olmamalıdır. Ancak JavaScript genellikle beklediğimizden farklı bir canavardır ve bazen onun gerçek, gerçek, Uçan Spagetti Canavarı için dürüst bir programlama dili olduğunu unuturuz. JavaScript'i adım adım ilerlettiğimizde, daha düşük seviyedeki karmaşıklıkların ve daha yüksek seviyedeki özel durumların bazılarını bilmek, uygulamalarımızı gerçekten geliştirebilir veya bozabilir, bu yüzden şunu bilmek isterim: En sevdiğiniz (veya en nefret ettiğiniz) "özellikler" nelerdir? JavaScript? JavaScript'iniz Chrome'u en son çökmeye başladığında ne öğrendiniz? Yorumlarda bize bildirin!

Copyright statement: Unless otherwise noted, this article is Collected from the Internet, please keep the source of the article when reprinting.

Check Also

Divi's Theme Builder ile Özel Global Başlık Nasıl Oluşturulur

Artık Tema Oluşturucu burada olduğuna göre, web sitenizi A'dan Z'ye kurmanıza yardımcı olacak yeni eğitimlere dalmak için sabırsızlanıyoruz. Buna Divi'nin yerleşik seçeneğini kullanarak özel başlıklar oluşturma da dahildir. Bu eğitimde Divi's Theme Builder'ı kullanarak global bir başlık oluşturmaya odaklanacağız. Bu sayfaya veya gönderiye farklı bir başlık atamadıysanız, web sitenizin her yerinde genel bir başlık görünecektir.

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir