Object.observe() ile veri bağlama devrimleri

Addy Osmani
Addy Osmani

Giriş

Bir devrim yaklaşıyor. JavaScript'e, veri bağlama hakkında bildiğinizi düşündüğünüz her şeyi değiştirecek yeni bir ekleme var. Bu değişiklik aynı zamanda MVC kitaplıklarınızın kaç tanesinin düzenleme ve güncelleme için modelleri gözlemleme yaklaşımını değiştirecektir. Mülk gözlemini önemseyen uygulamalarda bazı hoş performans artışlarına hazır mısınız?

Tamam. Daha fazla gecikme yaşamadan Object.observe()'in Chrome 36 kararlı sürüme geçtiğini duyurmaktan mutluluk duyuyorum. [OOOO. CROWD GOES WILD] başlıklı makaleyi inceleyin.

Gelecekteki bir ECMAScript standardının parçası olan Object.observe(), JavaScript nesnelerinde yapılan değişiklikleri eşzamansız olarak gözlemlemeye yarayan bir yöntemdir. Ayrı bir kitaplığa ihtiyaç duyulmaz. Gözlemcinin, gözlemlenen bir dizi nesnede gerçekleşen değişiklikler kümesini tanımlayan, zaman sıralı bir değişiklik kaydı dizisi almasını sağlar.

// Let's say we have a model with data
var model = {};

// Which we then observe
Object.observe(model, function(changes){

    // This asynchronous callback runs
    changes.forEach(function(change) {

        // Letting us know what changed
        console.log(change.type, change.name, change.oldValue);
    });

});

Yapılan her değişiklik bildirilir:

Değişiklik bildirildi.

Object.observe() ile (Ben O.o() veya Oooooooo) iki yönlü veri bağlamayı çerçeveye ihtiyaç duymadan uygulayabilirsiniz.

Bu, kullanmamanız gerektiği anlamına gelmez. Karmaşık iş mantığına sahip büyük projeler için dikkatli çerçeveler paha biçilmezdir ve bunları kullanmaya devam etmelisiniz. Bu uygulamalar yeni geliştiricilerin oryantasyonunu basitleştirir, daha az kod bakımı gerektirir ve genel görevlerin nasıl gerçekleştirileceğine dair kalıplar uygular. İhtiyacınız olmadığında, Polymer (O.o() işlevinden yararlanan) gibi daha küçük ve daha odaklanmış kitaplıklar kullanabilirsiniz.

Bir çerçeve veya müzik videosu* kitaplığını yoğun bir şekilde kullanıyor olsanız bile O.o(), aynı API'yi korurken daha hızlı ve basit bir uygulama işlemiyle sağlıklı performans iyileştirmeleri sağlayabilir. Örneğin, geçen yıl Angular tarafından yapılan bir karşılaştırmada, bir modelde değişiklik yapılan bir karşılaştırmada, kirli kontrol işleminin güncelleme başına 40 ms ve O.o() işlevinin güncelleme başına 1-2 ms sürdüğü (20-40 kat daha hızlı bir iyileştirme) olduğu belirlenmiştir.

Onlarca karmaşık koda gerek olmadan veri bağlama özelliği, artık değişiklikler için yoklama yapmanıza gerek olmadığı anlamına gelir. Böylece pil ömrü uzar!

Hâlihazırda O.o() satın aldıysanız özellik tanıtımına atlayın veya çözümün çözdüğü sorunlar hakkında daha fazla bilgi edinmek için okumaya devam edin.

Neyi gözlemlemek istiyoruz?

Veri gözlemi söz konusu olduğunda genellikle bazı belirli değişiklik türlerine dikkat etmek kastedilmektedir:

  • Ham JavaScript nesnelerinde yapılan değişiklikler
  • Tesis eklendiğinde, değiştirildiğinde veya silindiğinde
  • Dizilerde öğelerin eklendiği ve çıkarıldığı durumlar
  • Nesnenin prototipinde yapılan değişiklikler

Veri bağlamanın önemi

Model görünümü ile kontrollerin ayrılmasını önemsediğinizde veri bağlama önem kazanmaya başlar. HTML harika bir bildirim mekanizmasıdır, ancak tamamen statiktir. İdeal olarak, verileriniz ile DOM arasındaki ilişkiyi bildirmeniz ve DOM'u güncel tutmanız yeterlidir. Bu yöntem, avantaj sağlar ve yalnızca uygulamanızın dahili durumu veya sunucu arasında DOM'ye/DOM'dan veri gönderen, tekrarlayan kodlar yazmaya yönelik zaman kazandırır.

Veri bağlama, özellikle görünümlerinizde birden fazla öğe içeren veri modellerinizde birden fazla mülk arasındaki ilişkileri bağlamanız gereken karmaşık bir kullanıcı arayüzünüz olduğunda kullanışlıdır. Bugün oluşturmakta olduğumuz tek sayfalık uygulamalarda bu durum oldukça yaygındır.

Verileri tarayıcıda yerel olarak gözlemlemenin bir yolunu oluşturarak JavaScript çerçevelerine (ve yazdığınız küçük yardımcı program kitaplıklarına), dünyanın günümüzde kullandığı bazı yavaş saldırılara dayanmadan model verilerindeki değişiklikleri gözlemlemenin bir yolunu sağlıyoruz.

Dünya bugün nasıl görünüyor?

Yanlış kontrol

Veri bağlama işlemini daha önce nerede gördünüz? Web uygulamalarınızı derlemek için modern bir MV* kitaplığı kullanıyorsanız (ör.Angular, Knockout) muhtemelen model verilerini DOM'ye bağlamaya alışkınsınızdır. Bilgilerimizi tazelemek amacıyla, verilerimizin ve kullanıcı arayüzümüzün her zaman senkronize olmasını sağlamak için phones dizisindeki (JavaScript'te tanımlanır) her telefonun değerini bir liste öğesine bağladığımız Telefon listesi uygulaması örneğini aşağıda bulabilirsiniz:

<html ng-app>
  <head>
    ...
    <script src='angular.js'></script>
    <script src='controller.js'></script>
  </head>
  <body ng-controller='PhoneListCtrl'>
    <ul>
      <li ng-repeat='phone in phones'>
        
        <p></p>
      </li>
    </ul>
  </body>
</html>

ve denetleyicinin JavaScript kodu:

var phonecatApp = angular.module('phonecatApp', []);

phonecatApp.controller('PhoneListCtrl', function($scope) {
  $scope.phones = [
    {'name': 'Nexus S',
     'snippet': 'Fast just got faster with Nexus S.'},
    {'name': 'Motorola XOOM with Wi-Fi',
     'snippet': 'The Next, Next Generation tablet.'},
    {'name': 'MOTOROLA XOOM',
     'snippet': 'The Next, Next Generation tablet.'}
  ];
});

Temel model verileri her değiştiğinde, DOM'deki listemiz güncellenir. Angular bunu nasıl başarıyor? Bu, perde arkasında kontrol denen bir işlem yapıyor.

Yanlış kontrol

Devre dışı kontrolde temel fikir şudur: Ne zaman veriler değişebilseydi, kitaplığın gidip özetleme veya değişim döngüsü yoluyla değişip değişmediğini kontrol etmesi gerekir. Angular örneğinde, özet döngüsü, değişiklik olup olmadığını görmek için izlenmek üzere kaydedilen tüm ifadeleri tanımlar. Modelin önceki değerleri hakkında bilgi sahibi olur ve bu değerler değiştiyse bir değişiklik etkinliği tetiklenir. Bir geliştirici için buradaki asıl avantaj, oldukça iyi oluşturulan ve kullanımı kolay olan ham JavaScript nesne verilerini kullanabiliyor olmanızdır. Kötü tarafı, kötü algoritmik davranışa sahip olması ve potansiyel olarak çok pahalı olmasıdır.

Yanlış kontrol.

Bu işlemin gideri, gözlemlenen nesnelerin toplam sayısıyla orantılıdır. Çok fazla hatalı kontrol gerçekleştirmem gerekebilir. Ayrıca, veriler değişebilecek olduğunda, hatalı kontrolü tetiklemek için bir yönteme ihtiyaç duyabilirsiniz. Bu amaçla kullanılan pek çok zekice hile çerçevesi vardır. Bu gelişmenin mükemmel olup olmayacağı belirsiz.

Web ekosistemi, yenilik yapma ve kendi bildirim mekanizmalarını geliştirme konusunda daha yetenekli olmalıdır. Örneğin,

  • Kısıtlamaya dayalı model sistemleri
  • Otomatik kalıcılık sistemleri (ör.IndexedDB veya localStorage'da devam eden değişiklikler)
  • Container nesneleri (Köz, Omurga)

Container nesneleri, bir çerçevenin içinde verileri tutan nesneler oluşturduğu yerdir. Verilere erişenleri vardır. Belirlediğiniz veya aldığınız verileri yakalayıp dahili olarak yayınlayabilirler. Bu iyi sonuç veriyor. Nispeten güçlüdür ve iyi algoritmik davranışa sahiptir. Ember'ın kullanıldığı container nesnelerine ilişkin bir örneği aşağıda bulabilirsiniz:

// Container objects
MyApp.president = Ember.Object.create({
  name: "Barack Obama"
});
 
MyApp.country = Ember.Object.create({
  // ending a property with "Binding" tells Ember to
  // create a binding to the presidentName property
  presidentNameBinding: "MyApp.president.name"
});
 
// Later, after Ember has resolved bindings
MyApp.country.get("presidentName");
// "Barack Obama"
 
// Data from the server needs to be converted
// Composes poorly with existing code

Burada nelerin değiştiğini keşfetmenin maliyeti değişen şeylerin sayısıyla orantılıdır. Başka bir sorun da şu anda bu farklı türde nesne kullanıyorsunuz. Genel olarak, sunucudan aldığınız verileri gözlemlenebilir olmaları için bu nesnelere dönüştürmeniz gerekir.

Çoğu kod kodun ham veriler üzerinde çalışabileceğini varsaydığından, bu yöntem mevcut JS kodunda özellikle iyi oluşturmaz. Bu özel nesneler için uygun değildir.

Introducing Object.observe()

İdeal olanı, her iki tarafın da en iyisi. Her şeyi sürekli kontrol etmek zorunda kalmadan VE'yi seçersek ham veri nesnelerini (normal JavaScript nesneleri) desteğiyle verileri gözlemlemenin bir yoludur. İyi algoritmik davranışı olan bir şey. İyi bestelenen ve platforma eklenen bir şey. Object.observe() ürününün sunduğu avantajlar da bu.

Bir nesneyi gözlemlememize, özelliklerini değiştirmemize ve nelerin değiştiğine ilişkin değişiklik raporunu görmemize olanak tanır. Teoride yeterince kod yazalım.

Object.observe()

Object.observe() ve Object.unobserve()

Bir modeli temsil eden basit bir vanilya JavaScript nesneye sahip olduğumuzu düşünelim:

// A model can be a simple vanilla object
var todoModel = {
  label: 'Default',
  completed: false
};

Daha sonra, nesnede yapılan her değişiklik (değişiklik) için bir geri çağırma belirtebiliriz:

function observer(changes){
  changes.forEach(function(change, i){
      console.log('what property changed? ' + change.name);
      console.log('how did it change? ' + change.type);
      console.log('whats the current value? ' + change.object[change.name]);
      console.log(change); // all changes
  });
}

Ardından, bu değişiklikleri O.o() kullanarak gözlemleyebiliriz. Bunu da, nesneyi ilk bağımsız değişkenimiz, geri çağırmayı ise ikinci bağımsız değişkenimiz olarak iletiriz:

Object.observe(todoModel, observer);

Yapılacaklar modeli nesnemizde bazı değişiklikler yapmaya başlayalım:

todoModel.label = 'Buy some more milk';

Konsola baktığımızda bazı faydalı bilgiler olduğunu görürüz. Hangi özelliğin, nasıl değiştirildiğini ve yeni değerin ne olduğunu biliyoruz.

Konsol raporu

Harika! Hoşça kalın, kirli kontrol uygulaması! Mezar taşınızın Comic Sans ile oyulmuş olması gerekiyor. Başka bir özelliği değiştirelim. Bu sefer completeBy:

todoModel.completeBy = '01/01/2014';

Gördüğümüz gibi bir kez daha başarılı bir şekilde değişiklik raporu alıyoruz:

Değişiklik raporu.

Harika. Şimdi 'completed' özelliğini nesnemizden silmeye karar verirsek ne olur?

delete todoModel.completed;
Sona erenler

Gördüğümüz gibi, döndürülen değişiklikler raporu silme işlemiyle ilgili bilgiler içerir. Beklendiği gibi, özelliğin yeni değeri artık tanımsız. Artık mülklerin ne zaman eklendiğini öğrenebileceğinizi biliyoruz. Silindiğinde Temel olarak, bir nesne üzerindeki özellik grubudur ("yeni", "silindi", "yeniden yapılandırılmış") ve bunun prototip değişikliğidir (proto).

Tüm gözlem sistemlerinde olduğu gibi, değişiklikleri dinlemeyi durdurmak için bir yöntem de mevcuttur. Bu örnekte, O.o() ile aynı imzaya sahip olan ancak aşağıdaki gibi çağrılabilecek Object.unobserve() yöntemidir:

Object.unobserve(todoModel, observer);

Aşağıda görebileceğiniz gibi, bu çalıştırıldıktan sonra nesnede yapılan değişiklikler artık bir değişiklik kaydı listesinin döndürülmesine neden olmaz.

Mutasyonlar

İlgilenilen değişiklikleri belirtme

Bu nedenle, gözlemlenen bir nesnede yapılan değişikliklerin bir listesini nasıl geri getireceğimizin arkasındaki temel bilgileri inceledik. Bir nesnede yapılan değişikliklerin tümü yerine yalnızca bir alt kümesiyle ilgileniyorsanız ne olur? Herkesin bir spam filtresi olması gerekir. Gözlemciler sadece duymak istedikleri değişiklik türlerini bir kabul listesi aracılığıyla belirtebilir. Bu, O.o() için üçüncü bağımsız değişken kullanılarak aşağıdaki gibi belirtilebilir:

Object.observe(obj, callback, optAcceptList)

Bunun nasıl kullanılabileceğini gösteren bir örneği inceleyelim:

// Like earlier, a model can be a simple vanilla object

var todoModel = {
  label: 'Default',
  completed: false

};


// We then specify a callback for whenever mutations 
// are made to the object
function observer(changes){
  changes.forEach(function(change, i){
    console.log(change);
  })

};

// Which we then observe, specifying an array of change 
// types we're interested in

Object.observe(todoModel, observer, ['delete']);

// without this third option, the change types provided 
// default to intrinsic types

todoModel.label = 'Buy some milk'; 

// note that no changes were reported

Ancak etiketi şimdi silersek bu tür değişikliklerin bildirildiğine dikkat edin:

delete todoModel.label;

Bir kabul türleri listesini O.o() olarak belirtmezseniz varsayılan olarak "intrinsic" nesne değişikliği türleri kullanılır (add, update, delete, reconfigure, preventExtensions (bir nesnenin genişletilebilir olamayan bir hale gelmesi için)).

Bildirimler

O.o() ayrıca bildirim kavramını da içerir. Bunlar, telefona gelen rahatsız edici şeylerden çok daha faydalıdır. Bildirimler Dönüşüm Gözlemcileri'ne benzer. Bunlar, mikro görevin sonunda gerçekleşir. Tarayıcı bağlamında bu neredeyse her zaman geçerli etkinlik işleyicinin sonunda yer alır.

Zamanlama iyidir çünkü genellikle bir iş ünitesi biter ve artık gözlemciler işlerini yapmaya başlar. Sıraya dayalı işleme modeli olabilir.

Bildirimci kullanmaya ilişkin iş akışı biraz şuna benzer:

Bildirimler

Bir nesnedeki özelliklerin ne zaman alındığına veya ayarlanacağına ilişkin özel bildirimleri tanımlamak için pratikte bilgilendiricilerin nasıl kullanılabileceğine bir örneğe göz atalım. Gözünüz şu adresteki yorumlarda olsun:

// Define a simple model
var model = {
    a: {}
};

// And a separate variable we'll be using for our model's 
// getter in just a moment
var _b = 2;

// Define a new property 'b' under 'a' with a custom
// getter and setter

Object.defineProperty(model.a, 'b', {
    get: function () {
        return _b;
    },
    set: function (b) {

        // Whenever 'b' is set on the model
        // notify the world about a specific type
        // of change being made. This gives you a huge
        // amount of control over notifications
        Object.getNotifier(this).notify({
            type: 'update',
            name: 'b',
            oldValue: _b
        });

        // Let's also log out the value anytime it gets
        // set for kicks
        console.log('set', b);

        _b = b;
    }
});

// Set up our observer
function observer(changes) {
    changes.forEach(function (change, i) {
        console.log(change);
    })
}

// Begin observing model.a for changes
Object.observe(model.a, observer);
Bildirimler konsolu

Burada, veri özelliklerinin değerinin ne zaman değiştiğini raporlarız ("güncelleme"). Nesnenin uygulamasının bildirmeyi seçtiği diğer her şey (notifier.notifyChange()).

Web platformu üzerinde yıllarca edindiğimiz deneyim bize, eşzamanlı bir yaklaşımın ilk deneyeceğiniz şey olduğunu öğretti; çünkü en kolay olanıdır. Sorun, temelde tehlikeli bir işleme modelinin oluşturulmasıdır. Kod yazıyor ve bir nesnenin özelliğini güncelleme diyorsanız, o nesnenin özelliğini güncellemek, rastgele bir kod oluşturarak istediğiniz şeyi yapmak istemenize yol açabilir. Bir işlevin ortasında çalışırken varsayımlarınızın geçersiz kılınması ideal değildir.

Gözlemciyseniz ideal koşullarda biri bir şeyin ortasındayken telefonla aranmak istemezsiniz. Dünyanın tutarsız bir durumu üzerinde çalışmak için sizden izin istenmesini istemezsiniz. Çok daha fazla hata kontrolü yapmak. Daha fazla sayıda kötü duruma tolerans göstermeye çalışmak ve genel olarak, üzerinde çalışılması zor bir model. Eş zamansızlarla başa çıkmak daha zordur ancak aslında daha iyi bir modeldir.

Bu sorunun çözümü, sentetik değişiklik kayıtlarıdır.

Yapay değişiklik kayıtları

Temel olarak, erişimcilerinize veya hesaplanan mülklere sahip olmak isterseniz bu değerler değiştiğinde bunu bildirmek sizin sorumluluğunuzdadır. Biraz ekstra çalışma gerektirir, ancak bu mekanizmanın bir tür birinci sınıf özelliği olarak tasarlanmıştır ve bu bildirimler, temel veri nesnelerinden gelen diğer bildirimlerle birlikte teslim edilir. Veri özelliklerinden.

Yapay değişiklik kayıtları

Erişimcilerin ve hesaplanan özelliklerin gözlemlenmesi, O.o() işlevinin başka bir parçası olan notifier.notify ile çözülebilir. Çoğu gözlem sistemi türetilmiş değerleri bir şekilde gözlemlemeyi ister. Bunu yapmanın birçok yolu vardır. O.o "doğru" yöntem konusunda hiçbir yargıda bulunmaz. Hesaplanan mülkler, dahili (gizli) durum değiştiğinde notify erişimci olmalıdır.

Web geliştiricileri de kitaplıkların, hesaplanan özelliklere yönelik bildirim ve çeşitli yaklaşımları kolaylaştırmaya (ve ortak metni azaltmaya) yardımcı olmasını beklemelidir.

Bir sonraki örneğimiz olan çevre sınıfını oluşturalım. Buradaki fikir, bu dairenin ve yarıçap özelliğinin olduğudur. Bu durumda, yarıçap bir erişimcidir ve değeri değiştiğinde aslında değerin değiştiğini bildirecektir. Bu, bu nesne veya başka bir nesnede yapılan diğer tüm değişikliklerle birlikte yayınlanacak. Esas olarak, bir nesneyi uyguluyorsanız sentetik veya hesaplanmış özelliklere sahip olmak istersiniz ya da bunun nasıl işleyeceğine dair bir strateji seçmeniz gerekir. Bunu yaptığınızda, bu, bir bütün olarak sisteminize uyum sağlayacaktır.

Bu özelliğin Geliştirici Araçları'nda çalıştığını görmek için kodu atlayın.

function Circle(r) {
  var radius = r;
 
  var notifier = Object.getNotifier(this);
  function notifyAreaAndRadius(radius) {
    notifier.notify({
      type: 'update',
      name: 'radius',
      oldValue: radius
    })
    notifier.notify({
      type: 'update',
      name: 'area',
      oldValue: Math.pow(radius * Math.PI, 2)
    });
  }
 
  Object.defineProperty(this, 'radius', {
    get: function() {
      return radius;
    },
    set: function(r) {
      if (radius === r)
        return;
      notifyAreaAndRadius(radius);
      radius = r;
    }
  });
 
  Object.defineProperty(this, 'area', {
    get: function() {
      return Math.pow(radius, 2) * Math.PI;
    },
    set: function(a) {
      r = Math.sqrt(a/Math.PI);
      notifyAreaAndRadius(radius);
      radius = r;
    }
  });
}
 
function observer(changes){
  changes.forEach(function(change, i){
    console.log(change);
  })
}
Yapay değişiklik kayıtları konsolu

Erişimci özellikleri

Erişimci özellikleriyle ilgili kısa bir not. Daha önce, veri özellikleri için yalnızca değer değişikliklerinin gözlemlenebilir olduğunu belirtmiştik. Hesaplanan mülkler veya erişimciler için değildir. Bunun nedeni, JavaScript'in gerçekte erişimciler için değerde değişiklik yapma fikrine sahip olmamasıdır. Erişimci yalnızca bir işlev koleksiyonudur.

Bir erişimciye JavaScript atarsanız yalnızca işlevi orada çağırır ve bakış açısından hiçbir şey değişmemiştir. Bazı kodlar çalıştırma fırsatı verdi.

Sorun, semantik olarak yukarıdaki değer atamamıza (5 değerine 5) bakabiliriz. Burada ne olduğunu bilmemiz gerekir. Bu aslında çözülemeyen bir sorun. Örnekte bunun nedeni gösterilmektedir. Bu rastgele bir kod olabileceği için hiçbir sistemin bunun ne anlama geldiğini bilmesi mümkün değildir. Bu durumda istediği her şeyi yapabilir. Her erişimde değeri güncellediğinden, değerin değişip değişmediğini sormak pek mantıklı değildir.

Bir geri çağırma ile birden çok nesneyi gözlemleme

O.o() ile kullanabileceğiniz bir başka kalıp da tek bir geri çağırma gözlemleyicisi kavramıdır. Bu, tek bir geri çağırmanın birçok farklı nesne için bir "gözlemci" olarak kullanılmasına olanak tanır. Geri çağırma, "mikro görevin sonunda" gözlemlediği tüm nesnelere değişikliklerin tamamını iletir (Dönüşüm Gözlemcileri ile benzerliğe dikkat edin).

Bir geri çağırma ile birden çok nesneyi gözlemleme

Büyük ölçekli değişiklikler

Belki de gerçekten büyük bir uygulama üzerinde çalışıyorsunuz ve düzenli olarak büyük ölçekli değişikliklerle çalışmak zorundasınız. Nesneler, çok sayıda özellik değişikliğini yayınlamak yerine, çok sayıda mülkü daha küçük bir şekilde etkileyecek daha büyük anlamsal değişiklikleri tanımlamak isteyebilir.

O.o() buna iki yardımcı program ile yardımcı olur: Daha önce tanıttığımız notifier.performChange() ve notifier.notify().

Büyük ölçekli değişiklikler

Bunu, bazı matematik yardımcı programları (çarpma, artım, artımla ve artırma) içeren bir Thingy nesnesi tanımladığımız yerde büyük ölçekli değişikliklerin nasıl açıklanabileceğine bakalım. Bir yardımcı program her kullanıldığında, sisteme bir çalışma koleksiyonunun belirli bir değişiklik türünü içerdiğini bildirir.

Örneğin: notifier.performChange('foo', performFooChangeFn);

function Thingy(a, b, c) {
  this.a = a;
  this.b = b;
}

Thingy.MULTIPLY = 'multiply';
Thingy.INCREMENT = 'increment';
Thingy.INCREMENT_AND_MULTIPLY = 'incrementAndMultiply';


Thingy.prototype = {
  increment: function(amount) {
    var notifier = Object.getNotifier(this);

    // Tell the system that a collection of work comprises 
    // a given changeType. e.g
    // notifier.performChange('foo', performFooChangeFn);
    // notifier.notify('foo', 'fooChangeRecord');
    notifier.performChange(Thingy.INCREMENT, function() {
      this.a += amount;
      this.b += amount;
    }, this);

    notifier.notify({
      object: this,
      type: Thingy.INCREMENT,
      incremented: amount
    });
  },

  multiply: function(amount) {
    var notifier = Object.getNotifier(this);

    notifier.performChange(Thingy.MULTIPLY, function() {
      this.a *= amount;
      this.b *= amount;
    }, this);

    notifier.notify({
      object: this,
      type: Thingy.MULTIPLY,
      multiplied: amount
    });
  },

  incrementAndMultiply: function(incAmount, multAmount) {
    var notifier = Object.getNotifier(this);

    notifier.performChange(Thingy.INCREMENT_AND_MULTIPLY, function() {
      this.increment(incAmount);
      this.multiply(multAmount);
    }, this);

    notifier.notify({
      object: this,
      type: Thingy.INCREMENT_AND_MULTIPLY,
      incremented: incAmount,
      multiplied: multAmount
    });
  }
}

Ardından, nesnemiz için iki gözlemci tanımlarız: Biri değişiklikler için her şeyi kapsayan, diğeri ise yalnızca tanımladığımız belirli kabul türleri (Thingy.INCREMENT, Thingy.MULTIPLY, Thingy.INCREMENT_AND_MULTIPLY) hakkında rapor oluşturur.

var observer, observer2 = {
    records: undefined,
    callbackCount: 0,
    reset: function() {
      this.records = undefined;
      this.callbackCount = 0;
    },
};

observer.callback = function(r) {
    console.log(r);
    observer.records = r;
    observer.callbackCount++;
};

observer2.callback = function(r){
    console.log('Observer 2', r);
}


Thingy.observe = function(thingy, callback) {
  // Object.observe(obj, callback, optAcceptList)
  Object.observe(thingy, callback, [Thingy.INCREMENT,
                                    Thingy.MULTIPLY,
                                    Thingy.INCREMENT_AND_MULTIPLY,
                                    'update']);
}

Thingy.unobserve = function(thingy, callback) {
  Object.unobserve(thingy);
}

Artık bu kodla oynamaya başlayabiliriz. Yeni bir Thingy tanımlayalım:

var thingy = new Thingy(2, 4);

İnceleyin ve ardından bazı değişiklikler yapın. Aman tanrım, çok eğlenceli. Bir sürü şey var.

// Observe thingy
Object.observe(thingy, observer.callback);
Thingy.observe(thingy, observer2.callback);

// Play with the methods thingy exposes
thingy.increment(3);               // { a: 5, b: 7 }
thingy.b++;                        // { a: 5, b: 8 }
thingy.multiply(2);                // { a: 10, b: 16 }
thingy.a++;                        // { a: 11, b: 16 }
thingy.incrementAndMultiply(2, 2); // { a: 26, b: 36 }
Büyük ölçekli değişiklikler

"Gerçekleştirme işlevi" içindeki her şey, "büyük-değişiklik" işi olarak kabul edilir. "Büyük değişiklik"i kabul eden gözlemciler yalnızca "büyük değişiklik" kaydını alır. "İşlevleri gerçekleştir" yapılan çalışmanın sonucundaki temel değişiklikleri almayacak olan gözlemciler.

Dizileri gözlemleme

Bir süredir nesnelerdeki değişiklikleri gözlemlemekten bahsettik. Peki ya diziler? Harika bir soru. Birisi bana "Güzel soru" dediğinde. Yanıtlarını hiç duyamıyorum çünkü bu kadar muhteşem bir soru sorduğum için kendimi tebrik etmekle meşgulüm ama bu sorudan vazgeçerim. Dizilerle çalışmak için de yeni yöntemlerimiz var!

Array.observe(), kendisinde yapılan büyük ölçekli değişiklikleri (ör. birleştirme, kaydırma veya uzunluğunu dolaylı olarak değiştiren herhangi bir şey) "bölme" değişiklik kaydı olarak ele alan bir yöntemdir. Dahili olarak notifier.performChange("splice",...) kullanıyor.

Aşağıda, bir model "dizisi" gözlemlediğimiz ve buna benzer şekilde, temel verilerde herhangi bir değişiklik olduğunda değişikliklerin bir listesini geri aldığımız bir örnek verilmiştir:

var model = ['Buy some milk', 'Learn to code', 'Wear some plaid'];
var count = 0;

Array.observe(model, function(changeRecords) {
  count++;
  console.log('Array observe', changeRecords, count);
});

model[0] = 'Teach Paul Lewis to code';
model[1] = 'Channel your inner Paul Irish';
Dizileri gözlemleme

Performans

O.o() işlevinin bilişimsel performans üzerindeki etkisini düşünmenin yolu, bunu bir okuma önbelleği gibi düşünmektir. Genel olarak, önbellek (önem sırasına göre) aşağıdaki durumlarda çok iyi bir seçenektir:

  1. Okuma sıklığı, yazma sıklığına baskındır.
  2. Okumalar sırasında algoritmik olarak daha iyi performans elde etmek için yazma sırasında harcanan sabit miktarda iş yükünü azaltan bir önbellek oluşturabilirsiniz.
  3. Yazma işlemlerinin sabit zamanlı yavaşlaması kabul edilebilir.

O.o(), 1 gibi kullanım alanları için tasarlanmıştır.

Detaylı kontrol, gözlemlediğiniz tüm verilerin bir kopyasının saklanmasını gerektirir. Bu durum, O.o() ile yapamadığınız değişiklikleri yapmak için yapısal bellek maliyetine neden olur. Sorunsuz kontrol, iyi bir durma aralığı çözümü olsa da temelde sızıntılı bir soyutlama işlevi görür ve uygulamalar için gereksiz karmaşıklık oluşturabilir.

Neden? Doğrudan kontrol, veriler değiştiğinde her zaman çalıştırılmalıdır. Bunu yapmanın çok etkili bir yolu yoktur ve herhangi bir yaklaşımın önemli dezavantajları vardır (örneğin, yoklama aralığının kontrol edilmesi görsel yapılar ve kodla ilgili sorunlar arasındaki yarış koşullarını etkileyebilir). Değişiklik kontrolü ayrıca, gözlemcilerin global düzeyde kaydedilmesini gerektirir. Bu nedenle, bellek sızıntısı riskleri ve yıkım maliyetleri O.o() ile kaçınılır.

Bazı rakamları inceleyelim.

Aşağıdaki karşılaştırma testleri (GitHub'da bulunur), kontrollü kontrol ile O.o() işlevini karşılaştırmamıza olanak tanır. Bunlar, gözlemlenen-Object-Set-Size ve Number-Of-Mutations grafiklerini şeklinde yapılandırılmıştır. Genel sonuç, kirli kontrol performansının gözlemlenen nesne sayısıyla algoritmik olarak orantılı olduğu, O.o() performansı ise yapılan mutasyonların sayısıyla orantılı olmasıdır.

Yanlış kontrol

Hatalı kontrol performansı

Object.observe() açık olan Chrome

Performansı gözlemle

Object.observe() çoklu doldurma

Harika! O.o(), Chrome 36'da kullanılabilir, peki ya diğer tarayıcılarda? Size yardımcı olabiliriz. Polimer'in Observe-JS, O.o() için bir çoklu dolgudur. Bu, varsa yerel uygulamayı kullanır, ancak aksi takdirde onu çoklu doldurur ve üzerine bazı faydalı şekerlemeler ekler. Dünyanın genel görünümünü sunar ve değişimleri özetleyen bir rapor sunar. Ortaya çıkardığı iki güçlü faktör şunlardır:

  1. Yolları gözlemleyebilirsiniz. Yani "foo.bar.baz"ı belirli bir nesneden gözlemlemek istiyorum ve söz konusu yoldaki değer değiştiğinde bunu size bildirecekler. Yola ulaşılamıyorsa değeri tanımsız olarak kabul eder.

Belirli bir nesneden bir yoldaki değeri gözlemleme örneği:

var obj = { foo: { bar: 'baz' } };

var observer = new PathObserver(obj, 'foo.bar');
observer.open(function(newValue, oldValue) {
  // respond to obj.foo.bar having changed value.
});
  1. Dizi birleştirme hakkında bilgi vereceğiz. Dizi birleştirme işlemleri, dizinin eski sürümünü dizinin yeni sürümüne dönüştürmek için gerçekleştirmeniz gereken minimum birleştirme işlemi kümesidir. Bu, dizinin bir dönüşüm türü veya farklı bir görünümüdür. Bu, eski durumdan yeni duruma geçmek için yapmanız gereken minimum iş miktarıdır.

Bir dizide yapılan değişiklikleri minimum birleştirme grubu olarak raporlama örneği:

var arr = [0, 1, 2, 4];

var observer = new ArrayObserver(arr);
observer.open(function(splices) {
  // respond to changes to the elements of arr.
  splices.forEach(function(splice) {
    splice.index; // index position that the change occurred.
    splice.removed; // an array of values representing the sequence of elements which were removed
    splice.addedCount; // the number of elements which were inserted.
  });
});

Çerçeveler ve Object.observe()

Daha önce de belirtildiği gibi, O.o() çerçevelere ve kitaplıklara, bu özelliği destekleyen tarayıcılarda veri bağlama performansını iyileştirme konusunda büyük bir fırsat sunar.

Ember'dan Yehuda Katz ve Erik Bryn, O.o() desteği eklemenin Ember'ın yakın vadeli yol haritasında yer aldığını onayladı. Angular'dan Misko Hervy, Angular 2.0'ın iyileştirilmiş değişiklik algılama özelliği hakkında bir tasarım dokümanı yazdı. Uzun vadeli yaklaşımları, Chrome'un kararlı sürümüne geldiğinde Object.observe() ve o zamana kadar kendi değişiklik algılama yaklaşımı olan Watchtower.js'yi kullanmayı seçeceklerdir. Heyecan verici, heyecan verici.

Sonuçlar

O.o(), web platformuna giderken hemen kullanabileceğiniz güçlü bir eklentidir.

Bu özelliğin zamanla daha fazla tarayıcıda kullanıma sunulacağını ve JavaScript çerçevelerinin yerel nesne gözlem özelliklerine erişerek performansı artırmasını sağlayacağını umuyoruz. Chrome'u hedefleyenlerin Chrome 36'da (ve sonraki sürümlerde) O.o() kullanabilmesi ve bu özelliğin gelecekteki Opera sürümlerinde de kullanıma sunulması gerekir.

O halde, JavaScript çerçevelerinin yazarlarıyla Object.observe() ve uygulamalarınızdaki veri bağlama performansını iyileştirmek için bu biçimi nasıl kullanmayı planladıkları hakkında konuşun. Heyecan verici günler sizi bekliyor.

Kaynaklar

Katkıları ve incelemeleri için Rafael Weinstein, Jake Archibald, Eric Bidelman, Paul Kinlan ve Vivian Cromwell'e teşekkür ederiz.