Revolusi data binding dengan Object.observe()

Addy Osmani
Addy Osmani

Pengantar

Revolusi akan segera terjadi. Ada tambahan baru untuk JavaScript yang akan mengubah semua yang menurut Anda sudah Anda ketahui tentang data binding. Ini juga akan mengubah jumlah library MVC Anda yang mendekati model pengamatan untuk pengeditan dan pembaruan. Siap untuk meningkatkan performa yang bagus untuk aplikasi yang peduli dengan pengamatan properti?

Oke. Oke. Tanpa penundaan lebih lanjut, dengan senang hati saya umumkan Object.observe() telah tersedia di Chrome 36 stabil. [WOOOO. PENGUNGKAPAN SECARA LIAR].

Object.observe(), bagian dari standar ECMAScript mendatang, adalah metode untuk mengamati perubahan secara asinkron pada objek JavaScript... tanpa memerlukan library terpisah. Hal ini memungkinkan pengamat untuk menerima catatan perubahan urutan waktu yang menjelaskan rangkaian perubahan yang terjadi pada sekumpulan 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 dibuat, hal ini akan dilaporkan:

Perubahan dilaporkan.

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

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

Bahkan jika Anda sering menggunakan framework atau library MV*, O.o() berpotensi memberi mereka beberapa peningkatan performa yang bagus, dengan implementasi yang lebih cepat dan sederhana sambil mempertahankan API yang sama. Misalnya, tahun lalu Angular menemukan bahwa dalam benchmark saat perubahan dilakukan pada model, pemeriksaan kotor membutuhkan waktu 40 md per update dan O.o() membutuhkan 1-2 md per update (peningkatan 20-40x lebih cepat).

Pengikatan data tanpa memerlukan banyak kode rumit juga berarti Anda tidak perlu lagi meminta perubahan, sehingga masa pakai baterai lebih lama!

Jika Anda sudah menjual di O.o(), lewati pengantar fitur, atau baca lebih lanjut tentang masalah yang akan dipecahkan.

Apa yang ingin kita amati?

Ketika kita berbicara tentang pengamatan data, kita biasanya mengacu pada pengamatan untuk beberapa jenis perubahan tertentu:

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

Pentingnya data binding

Data binding mulai menjadi penting jika Anda ingin memisahkan kontrol tampilan model. HTML adalah mekanisme deklaratif yang hebat, tetapi sepenuhnya statis. Idealnya, Anda hanya ingin mendeklarasikan hubungan antara data dan DOM serta menjaga agar DOM tetap terbaru. Hal ini menciptakan pengaruh dan menghemat banyak waktu Anda dalam menulis kode yang benar-benar berulang yang hanya mengirim data ke dan dari DOM antara status internal aplikasi atau server.

Data binding sangat berguna ketika Anda memiliki antarmuka pengguna yang kompleks di mana Anda perlu menghubungkan beberapa properti dalam model data dengan beberapa elemen dalam tampilan Anda. Hal ini cukup umum dalam aplikasi satu halaman yang kita bangun saat ini.

Dengan membuat cara untuk mengamati data secara native di browser, kami memberikan framework JavaScript (dan library utilitas kecil yang Anda tulis) cara untuk mengamati perubahan data model tanpa bergantung pada beberapa peretasan lambat yang digunakan saat ini.

Tampilan dunia saat ini

Pemeriksaan kotor

Di mana Anda pernah melihat data binding sebelumnya? Nah, jika Anda menggunakan library MV* modern untuk membangun aplikasi web (misalnya Angular, Knockout), Anda mungkin terbiasa mengikat data model ke DOM. Sebagai penyegaran, 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 dalam DOM akan diperbarui. Bagaimana cara Angular mencapainya? Nah, di balik layar, terjadi sesuatu yang disebut pemeriksaan kotor.

Pemeriksaan kotor

Ide dasar dari pengecekan kotor adalah bahwa setiap kali data dapat berubah, {i>library<i} harus pergi dan memeriksa apakah data berubah melalui {i>digest <i}atau siklus perubahan. Dalam kasus Angular, siklus ringkasan mengidentifikasi semua ekspresi yang terdaftar untuk dipantau guna melihat apakah ada perubahan. Fungsi ini mengetahui nilai model sebelumnya dan jika nilai tersebut telah berubah, peristiwa perubahan akan diaktifkan. Bagi developer, manfaat utama di sini adalah Anda bisa menggunakan data objek JavaScript mentah yang menyenangkan untuk digunakan dan tersusun dengan cukup baik. Kelemahannya adalah bahwa ia memiliki perilaku algoritma yang buruk dan berpotensi sangat mahal.

Pemeriksaan yang kotor.

Biaya operasi ini sebanding dengan jumlah total objek yang diamati. Aku mungkin perlu melakukan banyak pemeriksaan kotor. Mungkin juga memerlukan cara untuk memicu pemeriksaan kotor saat data mungkin telah berubah. Ada banyak kerangka trik cerdas yang digunakan untuk ini. Tidak jelas apakah 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 pada IndexedDB atau localStorage)
  • Objek container (Ember, Backbone)

Objek Container adalah tempat framework membuat objek yang di dalamnya menyimpan data. Mereka memiliki pengakses ke data dan mereka dapat menangkap apa yang Anda tetapkan atau dapatkan dan siarkan secara internal. Ini berhasil. Performanya relatif bagus dan memiliki perilaku algoritma 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 saat ini 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 kurang tersusun dengan baik pada kode JS yang ada karena sebagian besar kode mengasumsikannya dapat beroperasi pada data mentah. Bukan untuk objek khusus semacam ini.

Introducing Object.observe()

Idealnya, yang kita inginkan adalah yang terbaik dari kedua dunia - cara mengamati data dengan dukungan untuk objek data mentah (objek JavaScript biasa) jika kita memilih AND tanpa perlu memeriksa semuanya setiap saat. Sesuatu dengan perilaku algoritma yang baik. Sesuatu yang tersusun dengan baik dan dimasukkan ke dalam platform. Inilah keunggulan Object.observe() yang ditawarkan.

Hal ini memungkinkan kita untuk mengamati objek, mengubah properti, dan melihat laporan perubahan dari hal yang berubah. Namun cukup dengan teori, mari kita lihat beberapa kode.

Object.observe()

Object.observe() dan Object.unobserve()

Bayangkan kita memiliki objek JavaScript sederhana yang mewakili sebuah model:

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

Kemudian, kita dapat menetapkan 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';

Saat melihat konsol, kita mendapatkan kembali beberapa informasi berguna. Kita tahu properti apa yang berubah, bagaimana properti diubah, dan apa nilai barunya.

Laporan konsol

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

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

Seperti yang dapat dilihat, 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 kami tahu bahwa Anda dapat mengetahui kapan properti telah ditambahkan. Saat dihapus. Pada dasarnya, set properti pada suatu objek ("new", "deleted", "reconfiguration") dan perubahan prototipenya (proto).

Seperti dalam sistem pengamatan apa pun, ada metode untuk berhenti memproses perubahan. Dalam hal ini, Object.unobserve() adalah 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 dilakukan pada objek setelah ini dijalankan tidak lagi akan menghasilkan daftar catatan perubahan yang ditampilkan.

Mutasi

Menentukan perubahan minat

Kita telah mempelajari dasar-dasar cara mendapatkan kembali daftar perubahan pada objek yang diamati. Bagaimana jika Anda hanya tertarik pada sebagian perubahan yang dilakukan pada suatu objek, bukan semuanya?. Semua orang membutuhkan filter spam. Nah, pengamat dapat menentukan hanya jenis perubahan yang ingin mereka dengar melalui daftar penerimaan. Ini dapat ditentukan menggunakan argumen ketiga untuk 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 memang dilaporkan:

delete todoModel.label;

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

Notifikasi

O.o() juga dilengkapi dengan gagasan notifikasi. Hal-hal itu tidak seperti hal-hal menjengkelkan yang Anda dapatkan di ponsel, tetapi lebih bermanfaat. Notifikasi mirip dengan Mutation Observer. Hal itu terjadi di akhir tugas mikro. Dalam konteks browser, error ini hampir selalu berada di akhir pengendali peristiwa saat ini.

Pengaturan waktu yang tepat karena umumnya satu unit pekerjaan telah selesai dan sekarang pengamat bisa melakukan pekerjaan mereka. Inilah model pemrosesan berbasis giliran yang bagus.

Alur kerja untuk menggunakan {i>notifier<i} tampak sedikit seperti ini:

Notifikasi

Mari kita lihat contoh bagaimana pemberi notifikasi dapat digunakan dalam praktik untuk mendefinisikan notifikasi kustom ketika properti pada objek didapatkan atau disetel. 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 kami melaporkan saat nilai properti data berubah ("update"). Apa pun yang dipilih oleh implementasi objek untuk dilaporkan (notifier.notifyChange()).

Pengalaman bertahun-tahun di platform web telah mengajari kami bahwa pendekatan sinkron adalah hal pertama yang Anda coba karena merupakan yang paling mudah untuk dipahami. Masalahnya adalah menciptakan model pemrosesan yang pada dasarnya berbahaya. Jika Anda menulis kode dan mengatakan, mengupdate properti objek, Anda tidak benar-benar ingin situasi setelah mengupdate properti objek tersebut dapat mengundang beberapa kode arbitrer untuk melakukan apa pun yang diinginkannya. Ini tidak ideal untuk membuat asumsi Anda menjadi tidak valid saat Anda menjalankan di tengah-tengah suatu fungsi.

Jika Anda seorang pengamat, idealnya Anda tidak ingin dipanggil jika seseorang sedang berada di tengah-tengah sesuatu. Anda tidak ingin diminta untuk melakukan pekerjaan di kondisi dunia yang tidak konsisten. Akhirnya melakukan lebih banyak pemeriksaan {i>error<i}. Mencoba menoleransi lebih banyak situasi buruk dan secara umum, ini adalah model yang sulit untuk dilakukan. Asinkron lebih sulit ditangani tetapi merupakan model yang lebih baik pada akhirnya.

Solusi untuk masalah ini adalah catatan perubahan sintetik.

Catatan perubahan sintetis

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

Catatan perubahan sintetis

Mengamati pengakses dan properti terkomputasi dapat diselesaikan dengan notifier.notify - bagian lain dari O.o(). Sebagian besar sistem pengamatan menginginkan suatu bentuk pengamatan nilai turunan. Ada banyak cara untuk melakukan ini. O.o tidak membuat penilaian tentang cara yang "benar". Properti yang dihitung harus merupakan pengakses yang notify saat status internal (pribadi) berubah.

Sekali lagi, webdev mengharapkan library membantu mempermudah pemberitahuan dan berbagai pendekatan terhadap properti komputasi (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 tersebut sebenarnya akan memberi tahu dirinya sendiri bahwa nilainya berubah. Fungsi ini akan dikirimkan dengan semua perubahan lain pada objek ini atau objek lainnya. Pada dasarnya, jika Anda mengimplementasikan objek, Anda ingin memiliki properti sintetis atau terkomputasi atau Anda harus memilih strategi terkait cara kerjanya. Setelah Anda melakukannya, ini akan sesuai dengan 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. Kita telah sebutkan sebelumnya bahwa hanya perubahan nilai yang dapat diobservasi untuk properti data. Tidak untuk properti terkomputasi atau pengakses. Alasannya adalah JavaScript tidak benar-benar memiliki gagasan perubahan nilai pada pengakses. Aksesor hanyalah kumpulan fungsi.

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

Masalahnya adalah secara semantik kita dapat melihat penetapan kita di atas untuk nilai - 5 untuknya. Kita harus bisa mengetahui apa yang terjadi di sini. Sebenarnya ini adalah masalah yang tidak dapat dipecahkan. Contoh tersebut menunjukkan alasannya. Sebenarnya tidak ada cara bagi sistem untuk mengetahui apa yang dimaksud dengan ini karena ini bisa berupa kode arbitrer. Dalam kasus ini, aplikasi dapat melakukan apa pun yang diinginkan. Hal ini memperbarui nilai setiap kali diakses, sehingga menanyakan apakah nilai tersebut berubah tidaklah masuk akal.

Mengamati beberapa objek dengan satu callback

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

Mengamati beberapa objek dengan satu callback

Perubahan berskala besar

Mungkin Anda sedang mengerjakan aplikasi yang sangat besar dan rutin harus bekerja dengan perubahan berskala besar. 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 melakukan hal ini dalam bentuk dua utilitas spesifik: notifier.performChange() dan notifier.notify(), yang telah kita perkenalkan.

Perubahan berskala besar

Mari kita lihat ini dalam contoh bagaimana perubahan skala besar dapat dijelaskan di mana kita menentukan objek Thingy dengan beberapa utilitas matematika (multiply, inflate, progressiveAndMultiply). Setiap kali utilitas digunakan, alat ini memberi tahu sistem bahwa sekumpulan 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, tentukan dua observer untuk objek: satu observer yang merupakan generik untuk perubahan dan satu lagi yang hanya akan melaporkan kembali jenis penerimaan spesifik yang telah ditentukan (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 bisa mulai bermain dengan kode ini. Mari kita definisikan Thingy baru:

var thingy = new Thingy(2, 4);

Amati dan kemudian buat beberapa perubahan. Asyik, menyenangkan 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 berskala besar

Segala sesuatu di dalam "fungsi performa" dianggap sebagai pekerjaan "perubahan besar". Pengamat yang menerima "perubahan besar" hanya akan menerima catatan "perubahan besar". Pengamat yang tidak akan menerima perubahan mendasar yang dihasilkan dari pekerjaan yang dilakukan “melakukan fungsi”.

Mengamati array

Kita telah membahas beberapa waktu tentang mengamati perubahan pada objek, tetapi bagaimana dengan array?! Pertanyaan bagus. Ketika seseorang berkata, "Pertanyaan bagus." Aku tidak pernah mendengar jawaban mereka karena sibuk memberi selamat kepada diri sendiri karena mengajukan pertanyaan yang bagus, tetapi aku menyimpang. Kita 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 record perubahan "gabungan". Secara internal, kode ini menggunakan notifier.performChange("splice",...).

Berikut adalah contoh saat kita mengamati "array" model dan juga mendapatkan kembali daftar perubahan saat 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 membayangkan dampak kinerja komputasi O.o() adalah dengan menganggapnya seperti cache baca. Secara umum, cache adalah pilihan yang tepat jika (sesuai urutan kepentingannya):

  1. Frekuensi pembacaan mendominasi frekuensi penulisan.
  2. Anda dapat membuat cache yang menukar jumlah konstan pekerjaan yang terlibat selama penulisan untuk performa yang lebih baik secara algoritmis selama pembacaan.
  3. Perlambatan waktu operasi tulis yang konstan dapat diterima.

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

Pengecekan kotor mengharuskan Anda menyimpan salinan semua data yang Anda amati. Ini berarti Anda mengeluarkan biaya memori struktural untuk pemeriksaan kotor yang tidak Anda dapatkan dengan O.o(). Pengecekan kotor, meskipun solusi stop-gap yang memadai, juga merupakan abstraksi yang bocor secara pada dasarnya yang dapat menciptakan kompleksitas yang tidak perlu untuk aplikasi.

Mengapa? Pemeriksaan kotor harus dijalankan setiap kali data mungkin telah berubah. Tidak ada cara yang sangat efektif untuk melakukan hal ini dan pendekatan apa pun terhadapnya memiliki kelemahan yang signifikan (misalnya, memeriksa interval polling berisiko menimbulkan artefak visual dan kondisi race di antara masalah kode). Pemeriksaan kotor juga memerlukan registry global observer, yang dapat menimbulkan bahaya kebocoran memori dan biaya pembongkaran yang dapat dihindari O.o().

Mari kita lihat beberapa angkanya.

Pengujian tolok ukur di bawah ini (tersedia di GitHub) memungkinkan kita membandingkan pemeriksaan kotor vs O.o(). Pengujian ini disusun sebagai grafik observable-Object-Set-Size vs Number-Of-Mutations. Hasil umumnya adalah bahwa kinerja pemeriksaan kotor sebanding secara algoritmis dengan jumlah objek yang diamati, sedangkan kinerja O.o() sebanding dengan jumlah mutasi yang dibuat.

Pemeriksaan kotor

Performa pemeriksaan yang kotor

Chrome dengan Object.observe() diaktifkan

Mengamati performa

Membuat Polyfilling Object.observe()

Bagus - jadi O.o() dapat digunakan di Chrome 36, tetapi bagaimana dengan menggunakannya di browser lain? Kami siap membantu Anda. Observer-JS Polymer adalah polyfill untuk O.o() yang akan menggunakan implementasi native jika ada, tetapi jika tidak ada, mem-polyfill-nya dan menyertakan beberapa sugaring berguna di atasnya. Laporan ini menawarkan tampilan agregat tentang dunia yang merangkum perubahan dan memberikan laporan tentang hal-hal yang telah berubah. Dua hal sangat ampuh yang diekspos adalah:

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

Contoh pengamatan nilai pada 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. Kode ini akan memberi tahu Anda tentang spesialisasi array. Penyambungan array pada dasarnya adalah rangkaian operasi gabungan minimal yang harus Anda lakukan pada array untuk mengubah versi lama array ke versi baru array. Ini adalah jenis transformasi atau tampilan array yang berbeda. Ini adalah jumlah pekerjaan minimum yang perlu Anda lakukan untuk berpindah dari status lama ke status baru.

Contoh perubahan pelaporan pada array sebagai kumpulan splice 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 kesempatan besar untuk framework dan library untuk meningkatkan performa data binding di browser yang mendukung fitur ini.

Yehuda Katz dan Erik Bryn dari Ember mengonfirmasi bahwa menambahkan dukungan untuk O.o() termasuk dalam roadmap jangka pendek Ember. Misko Hervy dari Angular menulis dokumen desain tentang peningkatan deteksi perubahan Angular 2.0. Pendekatan jangka panjang mereka adalah memanfaatkan Object.observe() saat tersedia di Chrome stabil, memilih Watchtower.js, pendekatan deteksi perubahan mereka sendiri hingga saat itu. Suuuper seru.

Kesimpulan

O.o() adalah tambahan yang kuat untuk platform web yang dapat Anda keluarkan dan gunakan saat ini.

Kami berharap fitur ini akan tersedia di lebih banyak browser seiring waktu, sehingga framework JavaScript dapat memperoleh peningkatan performa dari akses ke kemampuan pengamatan objek asli. Orang 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 diskusikan dengan penulis framework JavaScript tentang Object.observe() dan bagaimana mereka berencana menggunakannya untuk meningkatkan performa data binding di aplikasi Anda. Pasti ada saat-saat yang menarik!

Referensi

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