Revolusi data binding dengan Object.observe()

Addy Osmani
Addy Osmani

Pengantar

Revolusi akan segera terjadi. 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 LIAR].

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 proyek besar dengan logika bisnis yang rumit, kerangka kerja yang tidak dapat berubah sangat berharga dan Anda harus terus menggunakannya. Mereka menyederhanakan orientasi developer baru, membutuhkan 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 memberi mereka beberapa peningkatan performa yang sehat, dengan penerapan yang lebih cepat dan sederhana sambil tetap 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).

Pengikatan data tanpa perlu banyak kode yang rumit juga berarti Anda tidak perlu lagi memeriksa perubahan, jadi daya tahan baterai yang lebih lama!

Jika Anda sudah dijual di O.o(), lewati ke bagian perkenalan fitur, atau baca lebih lanjut tentang masalah yang dipecahkan.

Apa yang ingin kita amati?

Ketika kita berbicara tentang pengamatan data, kita biasanya mengacu pada pengamatan terhadap 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 jaga agar DOM tetap terbaru. 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 menyertakan cara untuk mengamati data secara native di browser, kami memberikan framework JavaScript (dan library utilitas kecil yang Anda tulis) cara untuk mengamati perubahan pada data model tanpa mengandalkan beberapa peretasan lambat yang digunakan dunia saat ini.

Seperti apa dunia hari ini

Pemeriksaan kotor

Di mana Anda pernah melihat data binding sebelumnya? Nah, jika Anda menggunakan library MV* modern untuk mem-build aplikasi web (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 pokok berubah, daftar kita di DOM akan diperbarui. Bagaimana cara Angular mencapai hal ini? Nah, di balik layar, ia 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. Fungsi ini mengetahui nilai sebelumnya pada model dan jika nilai tersebut telah berubah, peristiwa perubahan akan diaktifkan. Bagi pengembang, manfaat utamanya adalah Anda bisa menggunakan data objek JavaScript mentah yang menyenangkan untuk digunakan dan disusun dengan cukup baik. Kekurangannya adalah memiliki perilaku algoritma 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 lebih mampu untuk berinovasi dan mengembangkan mekanisme deklaratifnya sendiri, misalnya

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

Objek Penampung adalah tempat framework membuat objek yang menyimpan data di dalamnya. Mereka memiliki pengakses ke data dan dapat menangkap apa yang Anda tetapkan atau dapatkan dan siarkan secara internal. Ini akan berhasil. Algoritme ini relatif berperforma baik dan memiliki perilaku algoritme yang baik. Contoh objek container 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 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 AND tanpa perlu melakukan pemeriksaan kotor setiap saat. Sesuatu dengan perilaku algoritma yang baik. Sesuatu yang tersusun dengan baik dan dimasukkan ke dalam platform. Inilah keindahan dari apa yang ditawarkan Object.observe().

Fungsi ini memungkinkan kita untuk mengamati objek, mengubah properti, dan melihat laporan perubahan hal-hal 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
};

Selanjutnya kita bisa menentukan callback setiap kali mutasi (perubahan) dibuat 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 nisanmu harus diukir dengan Comic Sans. Mari ubah properti lain. Kali ini completeBy:

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

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

Ubah laporan.

Bagus. Bagaimana jika kita sekarang memutuskan untuk menghapus properti 'completed' 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 dapat mengetahui kapan properti ditambahkan. Setelah dihapus. Pada dasarnya, set properti pada sebuah objek ("new", "dihapus", "reconfiguration") 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, setiap mutasi yang dibuat pada objek setelah ini dijalankan tidak lagi menghasilkan daftar kumpulan data perubahan yang dikembalikan.

Mutasi

Menentukan perubahan minat

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

Object.observe(obj, callback, optAcceptList)

Mari kita lihat contoh bagaimana hal ini dapat digunakan:

// 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 sekarang kami menghapus label, perhatikan bahwa jenis perubahan ini dilaporkan:

delete todoModel.label;

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

Notifikasi

O.o() juga dilengkapi dengan notifikasi. Notifikasi ini tidak seperti notifikasi yang mengganggu di ponsel, tetapi sangat berguna. Notifikasi mirip dengan Pengamat Mutasi. Mereka 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 pemberi notifikasi terlihat sedikit 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()).

Bertahun-tahun pengalaman di platform web telah mengajari kami bahwa pendekatan sinkron adalah hal pertama yang Anda coba karena cara ini paling mudah untuk dipahami. Masalahnya adalah model pemrosesan yang dibuat secara fundamental berbahaya. Jika Anda menulis kode dan mengatakan, memperbarui properti objek, Anda tidak benar-benar ingin situasi setelah 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 pergi melakukan pekerjaan dengan cara 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.

Catatan perubahan sintetis

Pada dasarnya, jika ingin memiliki pengakses atau properti terkomputasi, Anda bertanggung jawab untuk memberi tahu kapan 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.

Catatan perubahan sintetis

Aksesor yang mengamati 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 melakukan ini. O.o tidak menilai 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 ketika nilainya berubah, radius benar-benar akan memberi tahu dirinya sendiri bahwa nilai tersebut berubah. Ini akan dikirimkan bersama 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, ini akan masuk ke dalam sistem Anda secara keseluruhan.

Lewati kode untuk melihat ini berfungsi 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 JavaScript pengakses hanya memanggil fungsi di sana dan dari sudut pandangnya tidak ada yang berubah. Hal ini hanya memberikan beberapa kesempatan untuk dijalankan.

Masalahnya adalah secara semantik, kita dapat melihat penetapan di atas untuk nilai - 5. Kita seharusnya bisa mengetahui apa yang terjadi di sini. Ini sebenarnya masalah yang tidak terpecahkan. Contoh ini menunjukkan alasannya. Sebenarnya tidak ada cara bagi sistem apa pun untuk mengetahui apa yang dimaksud dengan ini karena ini bisa berupa kode arbitrer. Dalam kasus ini, AI dapat melakukan apa pun yang diinginkannya. Sistem ini memperbarui nilai setiap kali diakses, sehingga tidak masuk akal untuk menanyakan apakah nilai berubah.

Mengamati beberapa objek dengan satu callback

Pola lain yang mungkin dengan O.o() adalah gagasan tentang satu observer callback. Hal ini memungkinkan callback tunggal digunakan sebagai "pengamat" untuk banyak objek yang berbeda. Callback akan mengirimkan kumpulan perubahan lengkap ke semua objek yang diamatinya di "akhir tugas mikro" (Perhatikan kemiripan dengan Mutation Observer).

Mengamati beberapa objek dengan satu callback

Perubahan berskala besar

Mungkin Anda sedang mengerjakan aplikasi yang benar-benar besar dan secara teratur harus bekerja dengan perubahan skala besar. Objek mungkin ingin mendeskripsikan perubahan semantik yang lebih besar yang akan memengaruhi banyak properti dengan cara yang lebih ringkas (bukan menyiarkan banyak perubahan properti).

O.o() membantu melakukan hal ini melalui dua utilitas spesifik: notifier.performChange() dan notifier.notify(), yang sudah kami perkenalkan.

Perubahan skala besar

Mari kita lihat ini dalam contoh bagaimana perubahan berskala besar dapat dijelaskan di mana kita menentukan objek Thingy dengan beberapa utilitas matematika (kalikan, penambahan, penambahan, dan Multiply). 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 Hal baru:

var thingy = new Thingy(2, 4);

Amati, lalu buat beberapa perubahan. Astaga, asyik sekali. 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

Segala sesuatu di dalam "menjalankan fungsi" dianggap sebagai pekerjaan "perubahan besar". Pengamat yang menerima "perubahan besar" hanya akan menerima catatan "perubahan besar". Pengamat yang tidak akan menerima perubahan yang mendasarinya yang dihasilkan dari pekerjaan yang dilakukan oleh “fungsi yang dijalankan”.

Mengamati array

Kita telah berbicara cukup lama tentang mengamati perubahan pada objek, tetapi bagaimana dengan array?! Pertanyaan bagus. Saat seseorang memberi tahu saya, "Pertanyaan bagus". Saya tidak pernah mendengar jawaban mereka karena saya sibuk mengucapkan selamat pada diri sendiri karena telah mengajukan pertanyaan yang luar biasa, tetapi saya menyimpang. Kami juga memiliki metode baru untuk menggunakan array.

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

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

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 menggambarkan tentang dampak kinerja komputasi dari O.o() adalah dengan memikirkannya seperti {i>cache<i} 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 mengharuskan Anda menyimpan 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 pengamat global, sehingga menimbulkan bahaya kebocoran memori dan biaya penguraian yang dapat dihindari O.o().

Mari kita lihat beberapa angkanya.

Pengujian benchmark di bawah (tersedia di GitHub) memungkinkan kita membandingkan pemeriksaan kotor vs O.o(). Pengujian tersebut terstruktur sebagai grafik Pengamatan-Objek-Set-Ukuran 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

Amati 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 ada polyfillnya dan menyertakan beberapa sugaring yang berguna di atasnya. Bagian ini menawarkan pandangan gabungan tentang dunia yang merangkum perubahan dan memberikan laporan tentang apa yang telah berubah. Dua hal yang sangat canggih yang dieksposnya adalah:

  1. Anda dapat mengamati jalur. Ini berarti Anda dapat mengatakan, saya ingin mengamati "foo.bar.baz" dari objek tertentu dan mereka akan memberi tahu Anda ketika nilai pada jalur itu berubah. Jika jalur tidak dapat dijangkau, maka akan menganggap nilai 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. Perintah ini akan memberi tahu Anda tentang gabungan array. Pemisahan array pada dasarnya adalah serangkaian operasi pemisahan minimal yang harus Anda lakukan pada array untuk mengubah array versi lama menjadi array versi baru. 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 perubahan pelaporan pada array sebagai kumpulan slice minimal:

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 telah disebutkan, O.o() akan memberikan peluang besar bagi kerangka kerja dan pustaka untuk meningkatkan kinerja pengikatan data mereka di browser yang mendukung fitur ini.

Yehuda Katz dan Erik Bryn dari Ember mengonfirmasi bahwa menambahkan dukungan untuk O.o() merupakan rencana 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 tiba di Chrome stabil, dan memilih Watchtower.js, pendekatan deteksi perubahan mereka sendiri sampai 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 harus dapat menggunakan O.o() di Chrome 36 (dan yang lebih baru) dan fitur tersebut juga akan tersedia dalam 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 ada hal-hal menarik yang akan terjadi.

Resource

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