Object.observe() ile veri bağlama devrimleri

Addy Osmani
Addy Osmani

Giriş

Bir devrim yaklaşıyor. JavaScript'e, veri bağlama hakkında bildiğiniz her şeyi değiştirecek yeni bir özellik eklendi. Ayrıca, MVC kitaplıklarınızın kaç tanesinin düzenlemeler ve güncellemeler için modelleri gözlemleme yaklaşımını da değiştirecek. Mülk gözlemine önem veren uygulamalarda performans artışı sağlamaya hazır mısınız?

Pekala. Daha fazla gecikmeden, Object.observe() uygulamasının Chrome 36 kararlı sürüme geçtiğini bildirmekten memnuniyet duyarız. [HAYIR. KİTLE ÇOK HEYECANLANIYOR].

Gelecekteki bir ECMAScript standardının parçası olan Object.observe(), ayrı bir kitaplığa ihtiyaç duymadan JavaScript nesnelerindeki değişiklikleri eşzamansız olarak gözlemlemek için kullanılan bir yöntemdir. Gözlemcinin gözlemlenen bir dizi nesnede gerçekleşen değişiklik kümesini açıklayan, zamana göre sıralanmış değişim kayıtları dizisi almasına olanak tanır.

// 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 raporlanır:

Değişiklik bildirildi.

Object.observe() ile (O.o() veya Oooooooo olarak adlandırıyorum) bir çerçeveye ihtiyaç duymadan iki yönlü veri bağlama uygulayabilirsiniz.

Bu, kullanmamanız gerektiği anlamına gelmez. Karmaşık iş mantığına sahip büyük projelerde üzerine fikirlendirilmiş çerçeveler paha biçilmezdir ve bu çerçeveleri kullanmaya devam etmelisiniz. Bunlar yeni geliştiricilerin yönünü basitleştirir, daha az kod bakımı gerektirir ve yaygın görevlerin nasıl gerçekleştirileceğine dair kalıplar uygular. Buna ihtiyacınız olmadığında Polymer gibi daha küçük ve daha odaklanmış kitaplıklar (O.o()'dan zaten yararlanan) kullanabilirsiniz.

Bir çerçeveyi veya MV* kitaplığını yoğun bir şekilde kullanıyor olsanız bile O.o(), aynı API'yi korurken daha hızlı ve basit bir uygulama ile onlara sağlıklı performans iyileştirmeleri sağlama potansiyeline sahiptir. Örneğin, geçen yıl Angular, bir modelde değişiklik yapılan karşılaştırmada, kirli kontrolün güncelleme başına 40 ms, O.o() işlevinin her güncelleme için 1-2 ms sürdüğünü (20-40 kat daha hızlı bir iyileşme) belirledi.

Çok sayıda karmaşık koda ihtiyaç duymadan veri bağlama özelliği, artık değişiklikleri yoklamanıza gerek olmadığı ve pil ömrünün uzadığı anlamına gelir.

Zaten O.o() işlevinde satış yaptıysanız, özellik tanıtımına geçebilir veya çözüm sunduğu sorunlar hakkında daha fazla bilgi edinmek için okumaya devam edebilirsiniz.

Neyi gözlemlemek istiyoruz?

Veri gözleminden bahsederken genellikle belirli türde değişiklikleri takip etmeyi kastediyoruz:

  • Ham JavaScript nesnelerinde yapılan değişiklikler
  • Özellikler eklendiğinde, değiştirildiğinde ve silindiğinde
  • Dizilerde bir araya getirilmiş öğeler olduğunda
  • Nesnenin prototipinde yapılan değişiklikler

Veri bağlamanın önemi

Model görünümü kontrolü ayırmayla ilgili işlemler söz konusu olduğunda 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 beyan etmek ve DOM'yi güncel tutmak istersiniz. Bu özellik, hem avantaj sağlar hem de uygulamanızın dahili durumu veya sunucu arasında DOM'ye veri gönderip alan, gerçekten tekrar eden kodlar yazarken zamandan tasarruf etmenizi sağlar.

Veri bağlama, özellikle veri modellerinizdeki birden fazla mülk ile görünümlerinizdeki birden fazla öğe arasındaki ilişkileri bağlamanız gereken karmaşık bir kullanıcı arayüzünüz olduğunda kullanışlıdır. Bu yöntem, bugün geliştirdiğimiz tek sayfalık uygulamalarda oldukça yaygın.

Tarayıcıdaki verileri yerel olarak gözlemlemenin bir yolunu sunarak JavaScript çerçevelerine (ve yazdığınız küçük yardımcı program kitaplıklarına), günümüzde dünyanın kullandığı yavaş saldırılardan bazılarına gerek kalmadan model verilerindeki değişiklikleri gözlemlemeniz için bir yol sunuyoruz.

Dünyanın bugünkü durumu

Kirlilik kontrolü

Veri bağlamayı daha önce nerede gördünüz? Web uygulamalarınızı geliştirirken modern bir MV* kitaplığı kullanıyorsanız (ör.Angular, Knockout) muhtemelen model verilerini DOM'ye bağlamaya alışkınsınızdır. Verilerimizin ve kullanıcı arayüzünün her zaman senkronize olması için phones dizisindeki (JavaScript'te tanımlanmıştır) her telefonun değerini bir liste öğesine bağladığımız bir 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 denetleyici için JavaScript:

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'daki listemiz güncellenir. Angular bunu nasıl başarıyor? Perde arkasında kirli kontrol denen bir şey yapıyor.

Kirli kontrol

Kirli kontrolün temel fikri, veriler ne zaman değişse bile kitaplığın gidip bir özet veya değişiklik döngüsü aracılığıyla değişip değişmediğini kontrol etmesi gerektiğidir. Angular'da bir ö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 edinir ve bu değerler değiştiyse bir değişiklik etkinliği tetiklenir. Geliştiriciler için bu özelliğin en büyük avantajı, kullanımı keyifli ve oldukça iyi bir şekilde derlenen ham JavaScript nesne verilerini kullanabilmeleridir. Olumsuz yanı, algoritmik çalışma biçiminin kötü olması ve muhtemelen çok pahalı olmasıdır.

Kirli kontrol.

Bu işlemin maliyeti, gözlemlenen nesnelerin toplam sayısıyla orantılıdır. Çok fazla kontrol yapmam gerekebilir. Ayrıca, veriler değiştiğinde geçersiz kontrolü tetikleyecek bir yöntem de gerekebilir. Çerçevelerin bu amaçla kullandığı birçok akıllıca numara vardır. Bunun mükemmel olup olmayacağı kesin değil.

Web ekosisteminin yenilik yapma ve kendi bildirim mekanizmalarını geliştirme konusunda daha fazla becerisi olmalıdır, ör.

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

Kapsayıcı nesneleri, bir çerçevenin içinde verileri barındıran nesneler oluşturduğu yerdir. Verilere erişimcileri vardır. Sizin ayarladığınız veya aldığınız verileri yakalayabilir ve dahili olarak yayınlayabilirler. Bu yöntem işe yarar. Nispeten iyi performans gösterir ve iyi bir algoritmik davranışa sahiptir. Ember kullanan container nesnelerine bir örnek aşağıda verilmiştir:

// 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şfetme maliyeti, değişen öğelerin sayısıyla orantılıdır. Başka bir sorun da bu farklı türde bir nesne kullanmanızdır. Genel olarak, sunucudan aldığınız verileri gözlemlenebilir hale getirmek için bu nesnelere dönüştürmeniz gerekir.

Çoğu kod ham veriler üzerinde çalışabileceğini varsaydığı için bu, mevcut JS koduyla özellikle iyi bir şekilde derlenmez. Bu özel nesne türleri için geçerli değildir.

Introducing Object.observe()

İdeal olarak, her iki tarafın da en iyisini isteriz. Bu, ham veri nesneleri (normal JavaScript nesneleri) desteğiyle verileri gözlemlemenin bir yoludur. Bunu yapmak istersek VE her şeyi sürekli olarak kirlilik kontrolü yapmak zorunda bırakmaz. İyi algoritmik davranışa sahip bir şey. İyi bir şekilde derlenmiş ve platforma yerleştirilmiş bir şey. Object.observe()'ün sunduğu avantajlardan biri de budur.

Bir nesneyi gözlemlememize, özellikleri değiştirmemize ve nelerin değiştiğine dair değişiklik raporunu görmemize olanak tanır. Teoride bu kadar kolay olsa da, biraz da koda göz atalım.

Object.observe()

Object.observe() ve Object.unobserve()

Bir modeli temsil eden basit bir özel JavaScript nesnemiz olduğunu düşünelim:

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

Daha sonra, nesnede her mutasyon (değişiklik) yapıldığında 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
  });
}

Daha sonra, O.o() işlevini kullanarak bu değişiklikleri gözlemleyebilir ve nesneyi ilk bağımsız değişkenimiz olarak, 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';

Konsoldaki bilgilere baktığımızda bazı yararlı bilgiler elde ettik. Hangi özelliğin değiştiğini, nasıl değiştirildiğini ve yeni değerin ne olduğunu biliyoruz.

Konsol raporu

Harika! Güle güle güle güle. Mezar taşınıza Comic Sans yazı tipi kullanılarak yazılmalıdır. 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ıyla bir değişiklik raporu aldık:

Raporu değiştir.

Harika. Şimdi "tamamlandı" özelliği nesnemizden silmeye karar verirsek ne olur?

delete todoModel.completed;
Tamamlandı

Gördüğümüz gibi, döndürülen değişiklikler raporunda silmeyle ilgili bilgiler yer alıyor. Beklenen gibi, mülkün yeni değeri artık "tanımsız". Böylece artık tesislerin ne zaman eklendiğini öğrenebileceğinizi biliyoruz. Silindiğinde Temel olarak, bir nesnenin özelliklerinin ("yeni", "silinmiş", "yeniden yapılandırılmış") grubu ve prototipinin değişmesi (proto).

Her gözlem sisteminde olduğu gibi, değişiklikleri dinlemeyi durdurmak için de bir yöntem vardır. Bu durumda, O.o() ile aynı imzaya sahip olan ancak aşağıdaki şekilde çağrılabilen Object.unobserve() işlevidir:

Object.unobserve(todoModel, observer);

Aşağıda görüldüğü gibi, bu çalıştırıldıktan sonra nesnede yapılan tüm mutasyonlar artık bir değişiklik kaydı listesi döndürmez.

Değişiklikler

İlgilenilen değişikliklerini belirtme

Böylece, gözlemlenen bir nesnede yapılan değişikliklerin bir listesini nasıl geri alabileceğinizi temel alan temel bilgilere değindik. Bir nesnede yapılan değişikliklerin yalnızca bir alt kümesiyle ilgileniyorsanız ne olur? Herkesin spam filtresine ihtiyacı vardır. Gözlemciler, bir kabul listesi aracılığıyla yalnızca duymak istedikleri değişiklik türlerini belirtebilir. Bu, O.o() için üçüncü bağımsız değişken kullanılarak aşağıdaki şekilde belirtilebilir:

Object.observe(obj, callback, optAcceptList)

Bunun nasıl kullanılabileceğiyle ilgili bir örnek üzerinden gidelim:

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

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

delete todoModel.label;

O.o() için bir kabul türleri listesi belirtmezseniz varsayılan olarak "doğal" nesne değişiklik türleri kullanılır (add, update, delete, reconfigure, preventExtensions (bir nesnenin gözlemlenebilir olmadığı durumlarda) için).

Bildirimler

O.o() aynı zamanda bildirim kavramını da içerir. Bunlar, telefonda gördüğünüz rahatsız edici şeylerden çok daha fazla faydalıdır. Bildirimler, Mutasyon Gözlemcilerine benzer. 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.

Genellikle bir iş birimi tamamlandığı için gözlemcilerin işlerini yapmasına olanak tanıdığı için zamanlama iyidir. Bu güzel bir sıraya dayalı işleme modeli.

Bildirim gönderen kullanmayla ilgili iş akışı aşağıdaki gibidir:

Bildirimler

Bir nesnenin özellikleri alındığında veya ayarlandığında özel bildirimler tanımlamak için bildiricilerin pratikte nasıl kullanılabileceğine dair bir örneğe göz atalım. Yorumları buradan takip edebilirsiniz:

// 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);
Bildirim konsolu

Burada, veri özelliklerinin değeri değiştiğinde ("güncelleme") raporlama yapılır. Nesnenin uygulamasının raporlamayı seçtiği diğer tüm veriler (notifier.notifyChange()).

Web platformunda yıllar süren deneyimlerimiz, senkronize yaklaşımın en kolay anlaşıldığı için ilk denemeniz olması gerektiğini bize öğretti. Sorun, temel düzeyde tehlikeli bir işleme modeli oluşturması. Kod yazıyor ve bir nesnenin özelliğini güncelleyin derseniz, söz konusu nesnenin özelliğinin güncellenmesi durumunda, istediğiniz her şeyi yapmaya rastgele bir kod davet edilmiş olabilir. Bir işlevin ortasındayken varsayımlarınızın geçersiz kılınması ideal değildir.

Gözlemciyseniz ideal koşullarda birisi bir şeyin ortasında olduğunda aranmak istemezsiniz. Dünyanın tutarsız bir durumunda çalışma yapmanız istenmesini istemezsiniz. Yaptığımız her şeyde hata kontrolü çok daha fazla oluyor. Çok daha fazla kötü duruma dayanmaya çalışıyoruz ve genellikle bu modelle çalışmak zor bir model. Asenkron işleyişle uğraşmak daha zordur ancak sonuçta daha iyi bir modeldir.

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

Sentetik değişiklik kayıtları

Temel olarak, erişim sağlayıcılara veya hesaplanmış özelliklere sahip olmak istiyorsanız bu değerlerin değiştiğini bildirmek sizin sorumluluğunuzdadır. Bu, biraz ekstra çalışma gerektirse de bu mekanizmanın birinci sınıf özelliği olarak tasarlanmıştır. Bu bildirimler, temel veri nesnelerinden gelen diğer bildirimlerle birlikte iletilir. Veri özelliklerinden.

Yapay değişiklik kayıtları

Erişimcileri ve hesaplanan özellikleri gözlemlemek, O.o() işlevinin başka bir bölümü olan notifier.notify ile çözülebilir. Çoğu gözlem sistemi, türetilen değerlerin bir şekilde gözlemlenmesini ister. Bunu yapmanın birçok yolu vardır. O.o "doğru" yol konusunda hiçbir yargıda bulunmaz. Hesaplanmış özellikler, dahili (özel) durum değiştiğinde bildiren erişim sağlayıcılar olmalıdır.

Yine de web geliştiricileri, kitaplıkların, hesaplanan mülklere yönelik bildirimleri ve çeşitli yaklaşımları kolaylaştırmaya (ve standart metinleri azaltmaya) yardımcı olmasını beklemelidir.

Bir sonraki örneği, yani çevre sınıfını oluşturalım. Buradaki fikir, bu çemberin bulunduğu ve yarıçap özelliğinin olduğudur. Bu durumda yarıçap bir erişim aracısıdır ve değeri değiştiğinde aslında değerin değiştiğini kendisi bildirir. Bu öğe, bu nesnede veya başka bir nesnede yapılan diğer tüm değişikliklerle birlikte yayınlanır. Özetle, bir nesneyi uyguluyorsanız sentetik veya hesaplanmış özelliklerin olmasını istersiniz ya da bunun nasıl çalışacağına yönelik bir strateji seçmeniz gerekir. Bunu yaptığınızda bu, sisteminizin tamamına uygun olur.

Bu işlemin DevTools'da nasıl ç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şim özellikleri

Erişim özelliklerine dair kısa bir not. Veri mülklerinde yalnızca değer değişikliklerinin gözlemlenebilir olduğundan daha önce bahsetmiştik. Hesaplanan mülkler veya erişimciler için uygun değildir. Bunun nedeni, JavaScript'te erişim sağlayıcıların değerindeki değişiklikler kavramının olmamasıdır. Erişimci, işlevlerin oluşturduğu bir koleksiyondur.

Bir erişim aracısına atarsanız JavaScript yalnızca işlevi orada çağırır ve kendi açısından hiçbir şey değişmez. Sadece bazı kodlara çalıştırılma fırsatı verdi.

Sorun, anlamsal olarak yukarıdaki değer için - 5 atamaya bakabiliriz. Burada ne olduğunu bilmeliyiz. Bu aslında çözülemeyen bir sorun. Örnekte bunun nedeni gösterilmektedir. Bu, rastgele bir kod olabileceğinden hiçbir sistemin bunun ne anlama geldiğini bilmesi mümkün değildir. Bu durumda istediğini yapabilir. Değere her erişildiğinde değer güncellendiğinden, değerin değişip değişmediğini sormak pek anlamlı değildir.

Tek bir geri çağırmayla birden çok nesneyi gözlemleme

O.o() işlevinde kullanılabilecek başka bir kalıp da tek bir geri çağırma gözlemleyicisi kavramıdır. Bu sayede, tek bir geri çağırma işlevi birçok farklı nesne için "gözlemci" olarak kullanılabilir. Geri çağırma işlevine, "mikro görevin sonunda" gözlemlediği tüm nesnelerdeki değişikliklerin tamamı gönderilir (Mutasyon Gözlemcileriyle olan benzerliğe dikkat edin).

Tek bir geri çağırmayla birden çok nesneyi gözlemleme

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

Belki de gerçekten büyük bir uygulama üzerinde çalışıyor ve büyük çaplı değişikliklerle düzenli olarak çalışmak zorundasınız. Nesneler, çok sayıda mülkü etkileyecek daha büyük anlamsal değişiklikleri daha kompakt bir şekilde açıklamak isteyebilir (tonlarca mülk değişikliği yayınlamak yerine).

O.o(), daha önce kullanıma sunduğumuz notifier.performChange() ve notifier.notify() olmak üzere iki belirli yardımcı program sağlayarak bu konuda yardımcı olur.

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

Bu konuyu, bazı matematik yardımcı programları (çarpma, artırma, artırmaVeÇarpma) içeren bir Thingy nesnesi tanımladığımızda büyük ölçekli değişikliklerin nasıl açıklanabileceğine dair bir örnekle inceleyelim. Bir yardımcı program her kullanıldığında, sisteme bir çalışma koleksiyonunun belirli bir değişiklik türünden oluştuğunu 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
    });
  }
}

Daha sonra, nesnemiz için iki gözlemci tanımlarız: biri değişiklikleri her şeyi kapsayan, diğeri ise yalnızca tanımladığımız belirli kabul türlerini (Thingy.INCREMENT, Thingy.MULTIPLY, Thingy.INCREMENT_AND_MULTIPLY) ilgili olarak raporlar.

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 bazı değişiklikler yapın. Vay canına, ç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

"Yürütme işlevi" içindeki her şey "büyük değişim"in işi olarak kabul edilir. "Büyük değişimi" kabul eden gözlemciler sadece "büyük değişiklik" kaydını alırlar. "İşlev gerçekleştirme" işlevinden kaynaklanan temel değişiklikleri almayacak gözlemciler.

Dizileri gözlemleme

Bir süredir nesnelerdeki değişiklikleri gözlemlemekten bahsettik. Peki ya dizilerde? Harika bir soru. Birisi bana "Mükemmel bir soru" dediğinde Yanıtlarını hiç duymuyorum çünkü bu kadar güzel bir soru sorduğum için kendimi tebrik etmekle meşgulüm ama diyebilirim. Dizilerle çalışmak için yeni yöntemlerimiz de var.

Array.observe(), kendisinde yapılan büyük çaplı değişiklikleri (ör. ekleme, kaydırma veya uzunluğunu dolaylı olarak değiştiren herhangi bir işlem) "splice" değişiklik kaydı olarak işleyen bir yöntemdir. Dahili olarak notifier.performChange("splice",...) kullanır.

Bir model "dizisi" gözlemlediğimiz ve temel verilerde herhangi bir değişiklik olduğunda benzer şekilde değişikliklerin bir listesini aldığımız bir örneği aşağıda bulabilirsiniz:

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 bilgi işlem performansı üzerindeki etkisi, bir okuma önbelleği gibi düşünülebilir. Genel olarak, önbellek aşağıdaki durumlarda (önem sırasına göre) mükemmel bir seçimdir:

  1. Okuma sıklığı, yazma sıklığına hakimdir.
  2. Okumalar sırasında algoritmik olarak daha iyi performans elde etmek için yazmalar sırasında gereken sabit miktarda işi takas eden bir önbellek oluşturabilirsiniz.
  3. Yazma işlemlerinin sürekli olarak yavaşlaması kabul edilebilir.

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

Kirli veri kontrolü, gözlemlediğiniz tüm verilerin bir kopyasının tutulmasını gerektirir. Bu durum, O.o() ile elde edemeyeceğiniz kirlilik kontrolü için yapısal bir bellek maliyetine sahip olmanız anlamına gelir. Kirli denetim, iyi bir geçici çözüm olmakla birlikte, uygulamalar için gereksiz karmaşıklık yaratabilecek temelde sızıntıya neden olan bir soyutlamadır.

Neden? kirli kontrolün, veriler değişebileceği her zaman olması gerekir. Bunu yapmanın çok sağlam bir yolu yoktur ve buna yönelik herhangi bir yaklaşımın önemli dezavantajları vardır (ör.yoklama aralığının kontrol edilmesi, görsel yapıların ve kod endişeleri arasındaki yarış koşullarının kontrol edilmesi gibi). Kirli kontrol, gözlemcilerin küresel bir sicil kaydını da gerektirir. Bu da bellek sızıntısı tehlikeleri ve O.o()'nun önlediği yıkım maliyetleri oluşturur.

Bazı sayılara göz atalım.

Aşağıdaki karşılaştırma testleri (GitHub'da mevcuttur) kirli kontrol ile O.o() işlevini karşılaştırmamıza olanak tanır. Bu testler, Gözlemlenen-Nesne-Kümesi-Boyutu-Değişim Sayısı grafikleri olarak yapılandırılmıştır. Genel sonuç, kirli kontrol performansının algoritmik olarak gözlemlenen nesnelerin sayısına, O.o() performansının ise yapılan mutasyonların sayısına orantılı olmasıdır.

Kirli kontrol

Kötü kontrol performansı

Object.observe() etkinleştirilmiş Chrome

Performansı gözlemleme

Object.observe() Çoklu Doldurma

O.o() işlevi Chrome 36'da kullanılabiliyor. Peki diğer tarayıcılarda kullanılabilir mi? Sizin için kısa bir hatırlatma hazırladık. Polymer'in Observe-JS, O.o() için bir çoklu dolgudur. Bu polifiller, varsa yerel uygulamayı kullanır, ancak aksi takdirde, çoklu dolgu yapar ve üzerine bazı faydalı şekerlemeler ekler. Dünyanın toplu bir görünümünü sunarak değişiklikleri özetler ve nelerin değiştiğini raporlar. Bu API'nin sunduğu iki güçlü özellik şunlardır:

  1. Yolları gözlemleyebilirsiniz. Bu, "foo.bar.baz"ı belirli bir nesneden gözlemlemek istediğimi ve bu yoldaki değer değiştiğinde size bilgi vereceği anlamına gelir. Yola ulaşılamazsa değer tanımsız olarak kabul edilir.

Belirli bir nesnenin yolundaki bir 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 işlemleri hakkında bilgi edinebilirsiniz. Dizi birleştirme işlemleri, temel olarak bir dizinin eski sürümünü yeni sürümüne dönüştürmek için bir diziyle yapmanız gereken minimum birleştirme işlemleri grubudur. Bu, bir tür dönüştürme işlemi veya dizinin farklı bir görünümüdür. Bu, eski durumdan yeni duruma geçmek için yapmanız gereken minimum çalışma miktarıdır.

Dizide yapılan raporlama değişikliklerini minimum birleştirme grubu olarak gösteren örnek:

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, özelliği destekleyen tarayıcılardaki veri bağlamalarının performansını iyileştirmek için büyük bir fırsat sunar.

Ember'den Yehuda Katz ve Erik Bryn, O.o() işlevine destek eklemenin Ember'in yakın vadeli yol haritasında olduğunu doğruladı. Angular'ın Misko Hervy, Angular 2.0'ın iyileştirilmiş değişiklik algılama özelliğiyle ilgili bir tasarım dokümanı yazdı. Uzun vadeli yaklaşımları, Chrome'un kararlı sürümüne eklendiğinde Object.observe() işlevinden yararlanmak olacak. O zamana kadar kendi değişiklik algılama yaklaşımları olan Watchtower.js'i tercih edecekler. Çok heyecan verici.

Sonuçlar

O.o(), hemen kullanmaya başlayabileceğiniz web platformunun güçlü bir katkısıdır.

Zamanla bu özelliğin daha fazla tarayıcıda kullanıma sunulmasını ve JavaScript çerçevelerinin yerel nesne gözlemleme özelliklerine erişerek performans artışı elde etmesini umuyoruz. Chrome'u hedefleyenler Chrome 36'da (ve sonraki sürümlerde) O.o() işlevini kullanabilmelidir. Ayrıca bu özellik daha sonraki bir Opera sürümünde de mevcut olmalıdır.

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

Kaynaklar

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