Revolusi data binding dengan Object.observe()

Pengantar

Revolusi akan segera hadir. Ada tambahan baru pada JavaScript yang akan mengubah semuanya yang Anda pikir Anda ketahui tentang data-binding. Hal ini juga akan mengubah jumlah library MVC Anda yang mendekati model pengamatan untuk pengeditan dan pembaruan. Siap mendapatkan peningkatan performa yang luar biasa untuk aplikasi yang memperhatikan pengamatan properti?

Baiklah. Baiklah. Tanpa penundaan lebih lanjut, dengan senang hati kami mengumumkan bahwa Object.observe() telah diluncurkan di Chrome 36 stabil. [WOOOO. THE CROWD GOES WILD].

Object.observe(), bagian dari standar ECMAScript mendatang, adalah metode untuk mengamati perubahan pada objek JavaScript secara asinkron… tanpa memerlukan library terpisah. Hal ini memungkinkan observer menerima urutan data perubahan yang diurutkan berdasarkan waktu yang menjelaskan kumpulan perubahan yang terjadi pada kumpulan objek yang diamati.

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

});

Setiap kali perubahan dilakukan, perubahan tersebut akan dilaporkan:

Perubahan dilaporkan.

Dengan Object.observe() (saya suka menyebutnya O.o() atau Oooooooo), Anda dapat menerapkan binding data dua arah tanpa memerlukan framework.

Namun, bukan berarti Anda tidak boleh menggunakannya. Untuk project besar dengan logika bisnis yang rumit, framework yang memiliki pendapat sangat berharga dan Anda harus terus menggunakannya. Framework ini menyederhanakan orientasi developer baru, memerlukan lebih sedikit pemeliharaan kode, dan menerapkan pola tentang cara mencapai tugas umum. Jika tidak memerlukannya, Anda dapat menggunakan library yang lebih kecil dan lebih terfokus seperti Polymer (yang sudah memanfaatkan O.o()).

Meskipun Anda sering menggunakan framework atau library MV*, O.o() berpotensi memberikan beberapa peningkatan performa yang baik, dengan implementasi yang lebih cepat dan lebih sederhana, sekaligus mempertahankan API yang sama. Misalnya, tahun lalu Angular menemukan bahwa dalam benchmark saat perubahan dilakukan pada model, pemeriksaan kotor memerlukan waktu 40 md per pembaruan dan O.o() memerlukan waktu 1-2 md per pembaruan (peningkatan 20-40x lebih cepat).

Data-binding tanpa memerlukan banyak kode rumit juga berarti Anda tidak perlu lagi melakukan polling untuk perubahan, sehingga masa pakai baterai lebih lama.

Jika Anda sudah yakin dengan O.o(), lanjutkan ke pengantar fitur, atau baca lebih lanjut untuk mengetahui masalah yang dipecahkan.

Apa yang ingin kita amati?

Saat berbicara tentang pengamatan data, kita biasanya merujuk pada memantau beberapa jenis perubahan tertentu:

  • Perubahan pada objek JavaScript mentah
  • Saat properti ditambahkan, diubah, dihapus
  • Saat array memiliki elemen yang digabungkan ke dalam dan ke luar
  • Perubahan pada prototipe objek

Pentingnya data-binding

Data-binding mulai menjadi penting saat Anda memperhatikan pemisahan kontrol model-tampilan. HTML adalah mekanisme deklaratif yang bagus, tetapi sepenuhnya statis. Idealnya, Anda hanya ingin mendeklarasikan hubungan antara data dan DOM serta terus memperbarui DOM. Hal ini menciptakan leverage dan menghemat banyak waktu Anda untuk menulis kode yang sangat berulang yang hanya mengirim data ke dan dari DOM antara status internal aplikasi atau server.

Data binding sangat berguna jika Anda memiliki antarmuka pengguna yang kompleks, tempat Anda perlu menghubungkan hubungan antara beberapa properti dalam model data dengan beberapa elemen dalam tampilan. Hal ini cukup umum di aplikasi web satu halaman yang kita buat saat ini.

Dengan menyediakan cara untuk mengamati data secara native di browser, kami memberi framework JavaScript (dan library utilitas kecil yang Anda tulis) cara untuk mengamati perubahan pada data model tanpa mengandalkan beberapa hack lambat yang digunakan dunia saat ini.

Tampilan dunia saat ini

Pemeriksaan kotor

Di mana Anda pernah melihat data-binding sebelumnya? Nah, jika menggunakan library MV* modern untuk mem-build webapp (misalnya Angular, Knockout), Anda mungkin terbiasa mengikat data model ke DOM. Sebagai pengingat, berikut adalah contoh aplikasi Daftar telepon tempat kita mengikat nilai setiap ponsel dalam array phones (ditentukan dalam JavaScript) ke item daftar sehingga data dan UI kita selalu sinkron:

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

dan JavaScript untuk pengontrol:

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.'}
  ];
});

Setiap kali data model yang mendasarinya berubah, daftar kita di DOM akan diperbarui. Bagaimana cara Angular mencapai hal ini? Nah, di balik layar, kode ini melakukan sesuatu yang disebut pemeriksaan kotor.

Pemeriksaan kotor

Ide dasar dari pemeriksaan kotor adalah setiap kali data dapat berubah, library harus memeriksa apakah data tersebut berubah melalui ringkasan atau siklus perubahan. Dalam kasus Angular, siklus ringkasan mengidentifikasi semua ekspresi yang terdaftar untuk dipantau guna melihat apakah ada perubahan. Model ini mengetahui nilai model sebelumnya dan jika nilai tersebut telah berubah, peristiwa perubahan akan diaktifkan. Bagi developer, manfaat utamanya adalah Anda dapat menggunakan data objek JavaScript mentah yang mudah digunakan dan disusun dengan cukup baik. Kelemahannya adalah memiliki perilaku algoritme yang buruk dan berpotensi sangat mahal.

Pemeriksaan kotor.

Biaya operasi ini sebanding dengan jumlah total objek yang diamati. Saya mungkin perlu melakukan banyak pemeriksaan kotor. Mungkin juga memerlukan cara untuk memicu pemeriksaan kotor saat data mungkin telah berubah. Ada banyak trik cerdas yang digunakan framework untuk hal ini. Tidak jelas apakah hal ini akan sempurna.

Ekosistem web harus memiliki lebih banyak kemampuan untuk berinovasi dan mengembangkan mekanisme deklaratifnya sendiri, misalnya

  • Sistem model berbasis batasan
  • Sistem persistensi otomatis (misalnya mempertahankan perubahan ke IndexedDB atau localStorage)
  • Objek penampung (Ember, Backbone)

Objek Penampung adalah tempat framework membuat objek yang di dalamnya menyimpan data. Atribut ini memiliki pengakses ke data dan dapat merekam apa yang Anda tetapkan atau dapatkan dan disiarkan secara internal. Cara ini berhasil. Model ini relatif berperforma baik dan memiliki perilaku algoritme yang baik. Contoh objek penampung yang menggunakan Ember dapat ditemukan di bawah ini:

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

Biaya untuk menemukan apa yang berubah di sini sebanding dengan jumlah hal yang berubah. Masalah lainnya adalah sekarang Anda menggunakan jenis objek yang berbeda ini. Secara umum, Anda harus mengonversi dari data yang Anda dapatkan dari server ke objek ini agar dapat diamati.

Hal ini tidak disusun dengan baik dengan kode JS yang ada karena sebagian besar kode mengasumsikan bahwa kode tersebut dapat beroperasi pada data mentah. Tidak untuk jenis objek khusus ini.

Introducing Object.observe()

Idealnya, yang kita inginkan adalah yang terbaik dari kedua dunia - cara untuk mengamati data dengan dukungan untuk objek data mentah (objek JavaScript reguler) jika kita memilih untuk DAN tanpa perlu memeriksa semuanya setiap saat. Sesuatu dengan perilaku algoritma yang baik. Sesuatu yang disusun dengan baik dan terintegrasi dengan platform. Inilah keindahan yang ditawarkan Object.observe().

Dengan begitu, kita dapat mengamati objek, mengubah properti, dan melihat laporan perubahan dari apa yang telah berubah. Namun, cukup teorinya, mari kita lihat beberapa kode.

Object.observe()

Object.observe() dan Object.unobserve()

Mari kita bayangkan bahwa kita memiliki objek JavaScript vanilla sederhana yang mewakili model:

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

Kemudian, kita dapat menentukan callback setiap kali mutasi (perubahan) dilakukan pada objek:

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

Kemudian, kita dapat mengamati perubahan ini menggunakan O.o(), dengan meneruskan objek sebagai argumen pertama dan callback sebagai argumen kedua:

Object.observe(todoModel, observer);

Mari kita mulai membuat beberapa perubahan pada objek model Todos:

todoModel.label = 'Buy some more milk';

Melihat konsol, kita mendapatkan beberapa informasi yang berguna. Kita tahu properti apa yang berubah, bagaimana properti tersebut diubah, dan nilai barunya.

Laporan konsol

Hore! Selamat tinggal, pemeriksaan kotor! Batu nisan Anda harus diukir dengan Comic Sans. Mari kita ubah properti lain. Kali ini completeBy:

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

Seperti yang dapat kita lihat, kita sekali lagi berhasil mendapatkan laporan perubahan:

Mengubah laporan.

Bagus. Bagaimana jika sekarang kita memutuskan untuk menghapus properti 'selesai' dari objek kita:

delete todoModel.completed;
Selesai

Seperti yang dapat kita lihat, laporan perubahan yang ditampilkan menyertakan informasi tentang penghapusan. Seperti yang diharapkan, nilai baru properti kini tidak ditentukan. Jadi, sekarang kita tahu bahwa Anda dapat mengetahui kapan properti telah ditambahkan. Setelah dihapus. Pada dasarnya, kumpulan properti pada objek ("baru", "dihapus", "dikonfigurasi ulang") dan prototipenya berubah (proto).

Seperti pada sistem pengamatan lainnya, metode juga ada untuk berhenti memproses perubahan. Dalam hal ini, Object.unobserve(), yang memiliki tanda tangan yang sama dengan O.o(), tetapi dapat dipanggil sebagai berikut:

Object.unobserve(todoModel, observer);

Seperti yang dapat kita lihat di bawah, mutasi apa pun yang dilakukan pada objek setelah ini dijalankan tidak lagi menghasilkan daftar data perubahan yang ditampilkan.

Mutasi

Menentukan perubahan minat

Jadi, kita telah melihat dasar-dasar di balik cara mendapatkan kembali daftar perubahan pada objek yang diamati. Bagaimana jika Anda hanya tertarik pada sebagian perubahan yang dilakukan pada objek, bukan semuanya? Semua orang memerlukan filter spam. Nah, pengamat hanya dapat menentukan jenis perubahan yang ingin mereka dengar melalui daftar yang diterima. Hal ini dapat ditentukan menggunakan argumen ketiga ke O.o() sebagai berikut:

Object.observe(obj, callback, optAcceptList)

Mari kita pelajari contoh cara penggunaannya:

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

Namun, jika kita sekarang menghapus label, perhatikan bahwa jenis perubahan ini akan dilaporkan:

delete todoModel.label;

Jika Anda tidak menentukan daftar jenis penerimaan ke O.o(), jenis perubahan objek "intrinsik" akan ditetapkan secara default (add, update, delete, reconfigure, preventExtensions (untuk saat objek menjadi tidak dapat diperluas tidak dapat diamati)).

Notifikasi

O.o() juga dilengkapi dengan notifikasi. Notifikasi ini tidak seperti notifikasi yang mengganggu di ponsel, tetapi sangat berguna. Notifikasi mirip dengan Mutation Observer. Peristiwa ini terjadi di akhir tugas mikro. Dalam konteks browser, ini hampir selalu berada di akhir pengendali peristiwa saat ini.

Waktunya tepat karena umumnya satu unit pekerjaan telah selesai dan sekarang observer dapat melakukan pekerjaannya. Ini adalah model pemrosesan berbasis giliran yang bagus.

Alur kerja untuk menggunakan notifikasi terlihat seperti ini:

Notifikasi

Mari kita lihat contoh cara notifikasi dapat digunakan dalam praktik untuk menentukan notifikasi kustom saat properti pada objek diambil atau ditetapkan. Perhatikan komentar di sini:

// 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);
Konsol notifikasi

Di sini, kita melaporkan saat nilai properti data berubah ("update"). Hal lain yang dipilih implementasi objek untuk dilaporkan (notifier.notifyChange()).

Pengalaman bertahun-tahun di platform web telah mengajarkan kepada kita bahwa pendekatan sinkron adalah hal pertama yang Anda coba karena paling mudah dipahami. Masalahnya adalah model pemrosesan yang dibuat secara fundamental berbahaya. Jika Anda menulis kode dan misalnya, memperbarui properti objek, Anda tidak benar-benar menginginkan situasi yang memperbarui properti objek tersebut dapat mengundang beberapa kode arbitrer untuk melakukan apa pun yang diinginkan. Asumsi Anda tidak akan valid saat Anda menjalankan fungsi di tengah-tengah.

Jika Anda adalah pengamat, sebaiknya Anda tidak ingin dipanggil jika seseorang sedang melakukan sesuatu. Anda tidak ingin diminta untuk melakukan pekerjaan pada status dunia yang tidak konsisten. Akhirnya melakukan lebih banyak pemeriksaan error. Mencoba menoleransi lebih banyak situasi buruk dan umumnya, model ini sulit digunakan. Async lebih sulit ditangani, tetapi pada akhirnya merupakan model yang lebih baik.

Solusi untuk masalah ini adalah data perubahan sintetis.

Data perubahan sintetis

Pada dasarnya, jika Anda ingin memiliki pengakses atau properti yang dihitung, Anda bertanggung jawab untuk memberi tahu saat nilai ini berubah. Ini adalah pekerjaan tambahan kecil, tetapi dirancang sebagai semacam fitur kelas satu dari mekanisme ini dan notifikasi ini akan dikirimkan bersama notifikasi lainnya dari objek data pokok. Dari properti data.

Data perubahan sintetis

Mengamati pengakses dan properti yang dihitung dapat diselesaikan dengan notifier.notify - bagian lain dari O.o(). Sebagian besar sistem pengamatan menginginkan beberapa bentuk pengamatan nilai turunan. Ada banyak cara untuk melakukannya. O.o tidak memberikan penilaian tentang cara yang "benar". Properti yang dihitung harus berupa pengakses yang memberi tahu saat status internal (pribadi) berubah.

Sekali lagi, developer web harus mengharapkan library untuk membantu mempermudah pemberitahuan dan berbagai pendekatan ke properti yang dihitung (dan mengurangi boilerplate).

Mari kita siapkan contoh berikutnya, yaitu class lingkaran. Idenya di sini adalah kita memiliki lingkaran ini dan ada properti radius. Dalam hal ini, radius adalah pengakses dan saat nilainya berubah, radius akan memberi tahu sendiri bahwa nilainya telah berubah. Perubahan ini akan dikirimkan dengan semua perubahan lain pada objek ini atau objek lainnya. Pada dasarnya, jika Anda menerapkan objek, Anda ingin memiliki properti sintetis atau komputasi atau Anda harus memilih strategi untuk cara kerjanya. Setelah Anda melakukannya, tindakan ini akan sesuai dengan sistem Anda secara keseluruhan.

Lewati kode untuk melihat cara kerjanya di DevTools.

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);
  })
}
Konsol data perubahan sintetis

Properti pengakses

Catatan singkat tentang properti pengakses. Sebelumnya, kami telah menyebutkan bahwa hanya perubahan nilai yang dapat diamati untuk properti data. Tidak untuk properti atau pengakses yang dikomputasi. Alasannya adalah JavaScript tidak benar-benar memiliki konsep perubahan nilai pada pengakses. Pengakses hanyalah kumpulan fungsi.

Jika Anda menetapkan ke pengakses, JavaScript hanya akan memanggil fungsi di sana dan dari sudut pandangnya, tidak ada yang berubah. Tindakan ini hanya memberi beberapa kode kesempatan untuk dijalankan.

Masalahnya adalah secara semantik, kita dapat melihat penetapan di atas ke nilai -5. Kita seharusnya bisa mengetahui apa yang terjadi di sini. Ini sebenarnya adalah masalah yang tidak dapat dipecahkan. Contoh ini menunjukkan alasannya. Tidak ada cara bagi sistem apa pun untuk mengetahui maksudnya karena ini bisa berupa kode arbitrer. Dalam hal ini, callback dapat melakukan apa pun yang diinginkannya. Fungsi ini memperbarui nilai setiap kali diakses, sehingga menanyakan apakah nilai tersebut berubah tidak masuk akal.

Mengamati beberapa objek dengan satu callback

Pola lain yang mungkin dengan O.o() adalah gagasan satu observer callback. Hal ini memungkinkan satu callback digunakan sebagai "observer" untuk banyak objek yang berbeda. Callback akan menerima kumpulan lengkap perubahan pada semua objek yang diamatinya di “akhir tugas mikro” (Perhatikan kesamaannya dengan Mutation Observer).

Mengamati beberapa objek dengan satu callback

Perubahan skala besar

Mungkin Anda sedang mengerjakan aplikasi yang sangat besar dan harus menangani perubahan skala besar secara rutin. Objek mungkin ingin menjelaskan perubahan semantik yang lebih besar yang akan memengaruhi banyak properti dengan cara yang lebih ringkas (bukan menyiarkan banyak perubahan properti).

O.o() membantu hal ini dalam bentuk dua utilitas khusus: notifier.performChange() dan notifier.notify(), yang telah kami perkenalkan.

Perubahan skala besar

Mari kita lihat contoh cara perubahan skala besar dapat dijelaskan saat kita menentukan objek Thingy dengan beberapa utilitas matematika (multiply, increment, incrementAndMultiply). Setiap kali digunakan, utilitas akan memberi tahu sistem bahwa kumpulan pekerjaan terdiri dari jenis perubahan tertentu.

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

Kemudian, kita menentukan dua observer untuk objek kita: satu yang merupakan catch-all untuk perubahan dan satu lagi yang hanya akan melaporkan kembali jenis penerimaan tertentu yang telah kita tentukan (Thingy.INCREMENT, Thingy.MULTIPLY, Thingy.INCREMENT_AND_MULTIPLY).

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

Sekarang kita dapat mulai bermain dengan kode ini. Mari kita tentukan Thingy baru:

var thingy = new Thingy(2, 4);

Amati, lalu buat beberapa perubahan. Wow, sangat menyenangkan. Ada begitu banyak hal!

// 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 }
Perubahan skala besar

Semua yang ada di dalam "perform function" dianggap sebagai pekerjaan “big-change”. Pengamat yang menerima “big-change” hanya akan menerima data “big-change”. Pengamat yang tidak akan menerima perubahan yang mendasarinya yang dihasilkan dari pekerjaan yang dilakukan oleh “fungsi yang dijalankan”.

Mengamati array

Kita telah membahas cara mengamati perubahan pada objek, tetapi bagaimana dengan array? Pertanyaan bagus. Saat seseorang memberi tahu saya, "Pertanyaan bagus". Saya tidak pernah mendengar jawabannya karena saya sibuk memuji diri sendiri karena telah mengajukan pertanyaan yang bagus, tetapi saya menyimpang. Kami juga memiliki metode baru untuk menggunakan array.

Array.observe() adalah metode yang memperlakukan perubahan berskala besar pada dirinya sendiri - misalnya - splice, unshift, atau apa pun yang secara implisit mengubah panjangnya - sebagai catatan perubahan "splice". Secara internal, status ini menggunakan notifier.performChange("splice",...).

Berikut adalah contoh saat kita mengamati "array" model dan juga mendapatkan kembali daftar perubahan jika ada perubahan pada data pokok:

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';
Mengamati array

Performa

Cara untuk memikirkan dampak performa komputasi O.o() adalah dengan menganggapnya seperti cache baca. Secara umum, cache adalah pilihan yang tepat jika (dalam urutan kepentingan):

  1. Frekuensi operasi baca mendominasi frekuensi operasi tulis.
  2. Anda dapat membuat cache yang menukar jumlah pekerjaan konstan yang terlibat selama operasi tulis dengan performa yang lebih baik secara algoritma selama operasi baca.
  3. Penurunan waktu konstan untuk operasi tulis dapat diterima.

O.o() dirancang untuk kasus penggunaan seperti 1).

Pemeriksaan kotor memerlukan penyimpanan salinan semua data yang Anda amati. Artinya, Anda akan dikenai biaya memori struktural untuk pemeriksaan kotor yang tidak Anda dapatkan dengan O.o(). Pemeriksaan kotor, meskipun merupakan solusi sementara yang layak, juga merupakan abstraksi yang bocor secara mendasar yang dapat menciptakan kompleksitas yang tidak perlu untuk aplikasi.

Mengapa? Nah, pemeriksaan kotor harus dijalankan setiap kali data mungkin telah berubah. Tidak ada cara yang sangat andal untuk melakukannya dan setiap pendekatan untuk melakukannya memiliki kelemahan yang signifikan (misalnya, memeriksa interval polling berisiko menimbulkan artefak visual dan kondisi perlombaan antara masalah kode). Pemeriksaan kotor juga memerlukan registry global observer, yang menciptakan bahaya kebocoran memori dan biaya pembongkaran yang dihindari O.o().

Mari kita lihat beberapa angka.

Pengujian benchmark di bawah (tersedia di GitHub) memungkinkan kita membandingkan pemeriksaan kotor vs O.o(). Pengujian ini disusun sebagai grafik Ukuran-Set-Objek-Yang-Diamati vs Jumlah-Mutasi. Hasil umumnya adalah performa pemeriksaan kotor secara algoritmis sebanding dengan jumlah objek yang diamati, sedangkan performa O.o() sebanding dengan jumlah mutasi yang dibuat.

Pemeriksaan kotor

Performa pemeriksaan kotor

Chrome dengan Object.observe() diaktifkan

Mengamati performa

Mengisi ulang Object.observe()

Bagus - jadi O.o() dapat digunakan di Chrome 36, tetapi bagaimana cara menggunakannya di browser lain? Kami siap membantu Anda. Observe-JS Polymer adalah polyfill untuk O.o() yang akan menggunakan implementasi native jika ada, tetapi jika tidak, polyfill akan menyertakan beberapa sugaring yang berguna di atasnya. Fitur ini menawarkan tampilan gabungan dunia yang merangkum perubahan dan memberikan laporan tentang perubahan yang terjadi. Dua hal yang sangat canggih yang dieksposnya adalah:

  1. Anda dapat mengamati jalur. Artinya, Anda dapat mengatakan, Saya ingin mengamati "foo.bar.baz" dari objek tertentu dan keduanya akan memberi tahu Anda saat nilai di jalur tersebut berubah. Jika jalur tidak dapat dijangkau, nilainya akan dianggap tidak ditentukan.

Contoh mengamati nilai di jalur dari objek tertentu:

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. Halaman ini akan memberi tahu Anda tentang penyambungan array. Penyambungan array pada dasarnya adalah kumpulan minimal operasi penyambungan yang harus Anda lakukan pada array untuk mengubah versi lama array menjadi versi baru array. Ini adalah jenis transformasi atau tampilan array yang berbeda. Ini adalah jumlah minimum pekerjaan yang perlu Anda lakukan untuk beralih dari status lama ke status baru.

Contoh pelaporan perubahan pada array sebagai kumpulan sambungan minimum:

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

Framework dan Object.observe()

Seperti yang disebutkan, O.o() akan memberi framework dan library peluang besar untuk meningkatkan performa data-binding mereka di browser yang mendukung fitur tersebut.

Yehuda Katz dan Erik Bryn dari Ember mengonfirmasi bahwa penambahan dukungan untuk O.o() ada dalam roadmap jangka pendek Ember. Misko Hervy dari Angular menulis dokumen desain tentang deteksi perubahan yang ditingkatkan di Angular 2.0. Pendekatan jangka panjang mereka adalah memanfaatkan Object.observe() saat dirilis di Chrome stabil, dengan memilih Watchtower.js, pendekatan deteksi perubahan mereka sendiri hingga saat itu. Sangat menarik.

Kesimpulan

O.o() adalah tambahan yang efektif untuk platform web yang dapat Anda gunakan sekarang.

Kami berharap fitur ini akan tersedia di lebih banyak browser seiring waktu, sehingga framework JavaScript dapat mendapatkan peningkatan performa dari akses ke kemampuan pengamatan objek native. Pengguna yang menargetkan Chrome akan dapat menggunakan O.o() di Chrome 36 (dan yang lebih baru) dan fitur ini juga akan tersedia di rilis Opera mendatang.

Jadi, lanjutkan dan bicarakan dengan penulis framework JavaScript tentang Object.observe() dan bagaimana mereka berencana menggunakannya untuk meningkatkan performa data-binding di aplikasi Anda. Pasti akan ada banyak hal menarik di masa mendatang.

Resource

Terima kasih kepada Rafael Weinstein, Jake Archibald, Eric Bidelman, Paul Kinlan, dan Vivian Cromwell atas masukan dan ulasan mereka.