Object.observe() によるデータ バインディングの変革

はじめに

革命が起ころうとしています。JavaScript に新たに追加された機能により、データ バインディングに関するこれまでの考えがすべて変わる可能性があります。また、多くの MVC ライブラリが、編集と更新のためにモデルを監視する方法も変更されます。プロパティの観測を重視するアプリのパフォーマンスを大幅に向上させる準備はできていますか?

では、早速 Object.observe()Chrome 36 安定版にリリースされたことをお知らせします。[WOOOO. THE CROWD GOES WILD] を追加します。

Object.observe() は、今後の ECMAScript 標準の一部であり、JavaScript オブジェクトの変更を非同期で監視するメソッドです。これにより、別のライブラリを必要とせずに、これにより、オブザーバーは、監視対象のオブジェクトセットに対して行われた一連の変更を記述する、変更レコードの時系列シーケンスを受信できます。

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

});

変更が加えられた場合は、次のように報告されます。

変更が報告されました。

Object.observe()(O.o() または Oooooooo と呼んでいます)を使用すると、フレームワークを使用せずに双方向データ バインディングを実装できます。

ただし、使用しないほうがよいというわけではありません。複雑なビジネス ロジックを持つ大規模なプロジェクトでは、独自のフレームワークは非常に貴重であり、引き続き使用する必要があります。新しいデベロッパーのオリエンテーションを簡素化し、コードのメンテナンスの負担を軽減し、一般的なタスクを実行する方法にパターンを適用します。ライブラリが不要な場合は、Polymer などの小規模でより限定的なライブラリを使用できます(すでに O.o() を利用しています)。

フレームワークや MV* ライブラリを頻繁に使用している場合でも、O.o() を使用すると、同じ API を維持しながら、より高速でシンプルな実装でパフォーマンスを大幅に改善できます。たとえば、昨年 Angular で確認されたベンチマークでは、モデルに変更を加えた場合に、ダーティチェックに 1 回の更新あたり 40 ミリ秒、O.o() に 1 ~ 2 ミリ秒かかりました(20 ~ 40 倍の高速化)。

複雑なコードを大量に記述しなくてもデータ バインディングが可能なため、変更をポーリングする必要がなくなり、バッテリー駆動時間が長くなります。

O.o() のメリットをすでにご存じの場合は、機能の概要に進んでください。また、O.o() が解決する問題について詳しくは、以下をご覧ください。

観察する内容

データのモニタリングとは、通常、特定の種類の変更に注意を払うことを指します。

  • 未加工の JavaScript オブジェクトの変更
  • プロパティの追加、変更、削除が行われたとき
  • 配列に要素が挿入または削除されている場合
  • オブジェクトのプロトタイプの変更

データ バインディングの重要性

モデルビュー コントロールの分離を重視する場合、データ バインディングが重要になります。HTML は優れた宣言型メカニズムですが、完全に静的です。理想的には、データと DOM の関係を宣言し、DOM を最新の状態に保つだけです。これにより、アプリケーションの内部状態またはサーバー間で DOM との間でデータを送受信するだけの繰り返しコードを記述する時間を大幅に節約できます。

データ バインディングは、データモデル内の複数のプロパティとビュー内の複数の要素の関係をワイヤーアップする必要がある複雑なユーザー インターフェースで特に便利です。これは、現在構築しているシングルページ アプリケーションでは非常に一般的です。

ブラウザでデータをネイティブに監視する方法を実装することで、JavaScript フレームワーク(および作成する小さなユーティリティ ライブラリ)に、現在使用されている遅いハックに依存することなく、モデルデータの変更を監視する方法を提供します。

世界が今どのような状況か

ダーティチェック

データ バインディングは以前にどこで見たことがありますか?ウェブアプリの作成に最新の MV* ライブラリ(Angular、Knockout など)を使用している場合は、モデルデータを DOM にバインドすることに慣れているでしょう。復習として、データと UI が常に同期されるように、phones 配列(JavaScript で定義)内の各スマートフォンの値をリストアイテムにバインドするスマートフォン リスト アプリの例を次に示します。

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

コントローラの 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.'}
  ];
});

基盤となるモデルデータが変更されるたびに、DOM 内のリストが更新されます。Angular ではどのように実現されているのでしょうか。実は、この処理はバックグラウンドでダーティチェックと呼ばれる処理を行っています。

ダーティ チェック

ダーティチェックの基本的な考え方は、データが変更された可能性がある場合はいつでも、ライブラリがダイジェストまたは変更サイクルを介して変更されたかどうかを確認する必要があるということです。Angular の場合、ダイジェスト サイクルは、監視対象として登録されたすべての式を特定して、変更がないか確認します。モデルの以前の値を認識し、値が変更された場合は変更イベントがトリガーされます。デベロッパーにとっての主なメリットは、使用しやすく、コンポーズも比較的容易な未加工の JavaScript オブジェクト データを使用できることです。欠点は、アルゴリズムの動作が不安定で、非常に費用がかかる可能性があることです。

ダーティ チェック。

このオペレーションの費用は、検出されたオブジェクトの総数に比例します。ダーティ チェックを大量に行う必要がある場合がある。また、データが変更された可能性がある場合にダーティチェックをトリガーする方法が必要になる場合があります。フレームワークには、この目的で使用できる賢いトリックが数多くあります。これが完全に解決されるかどうかは不明です。

ウェブ エコシステムは、独自の宣言型メカニズム(

  • 制約ベースのモデルシステム
  • 自動永続化システム(IndexedDB や localStorage への変更の永続化など)
  • コンテナ オブジェクト(Ember、Backbone)

コンテナ オブジェクトは、内部でデータを保持するオブジェクトをフレームワークが作成する場所です。データへのアクセス方法が用意されており、設定または取得した内容をキャプチャして内部ブロードキャストできます。これはうまく機能します。比較的パフォーマンスが高く、アルゴリズム動作も良好です。Ember を使用するコンテナ オブジェクトの例を以下に示します。

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

変更内容を特定する費用は、変更された項目の数に比例します。もう 1 つの問題は、この異なる種類のオブジェクトを使用している点です。一般に、サーバーから取得したデータをこれらのオブジェクトに変換して、オブザーバブルにする必要があります。

ほとんどのコードは、元のデータで操作できることを前提としているため、これは既存の JS コードと特にうまく組み合わせることができません。これらの特殊な種類のオブジェクトには対応していません。

Introducing Object.observe()

理想的には、両方の長所を兼ね備えた方法が必要です。つまり、必要に応じて元のデータ オブジェクト(通常の JavaScript オブジェクト)をサポートし、常にすべてをダーティチェックする必要がない方法です。アルゴリズムの動作が良好なもの。適切にコンポーズされ、プラットフォームに組み込まれているもの。これが Object.observe() の優れた点です。

これにより、オブジェクトを監視し、プロパティを変更し、変更内容の変更レポートを確認できます。理論はここまでにして、コードを見てみましょう。

Object.observe()

Object.observe() と Object.unobserve()

モデルを表す単純な JavaScript オブジェクトがあるとします。

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

次に、オブジェクトにミューテーション(変更)が加えられたときに呼び出されるコールバックを指定します。

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

これらの変更は、O.o() を使用して確認できます。オブジェクトを最初の引数として、コールバックを 2 番目の引数として渡します。

Object.observe(todoModel, observer);

では、Todo モデル オブジェクトに変更を加えましょう。

todoModel.label = 'Buy some more milk';

コンソールを見ると、役立つ情報が返されます。変更されたプロパティ、変更方法、新しい値を確認できます。

コンソールのレポート

おめでとうございます!ダーティチェックにさようなら!墓石には Comic Sans で刻まれます。別のプロパティを変更しましょう。この場合の completeBy は次のようになります。

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

変更レポートが正常に取得されたことがわかります。

レポートを変更します。

ありがとうございます。オブジェクトから「completed」プロパティを削除するとどうなりますか。

delete todoModel.completed;
完了

返された変更レポートには、削除に関する情報が含まれています。想定どおり、プロパティの新しい値は未定義になりました。プロパティがいつ追加されたかを確認できることがわかりました。削除された場合。基本的に、オブジェクトのプロパティのセット(「new」、「deleted」、「reconfigured」)とプロトタイプの変更(proto)です。

他のオブザーバビリティ システムと同様に、変更のリッスンを停止するメソッドも用意されています。この場合、Object.unobserve() です。O.o() と同じシグネチャですが、次のように呼び出すことができます。

Object.unobserve(todoModel, observer);

以下に示すように、この処理の実行後にオブジェクトに対して行われたミューテーションでは、変更レコードのリストが返されなくなります。

ミューテーション

関心のある変更を指定する

ここまで、監視対象オブジェクトの変更リストを取得する方法の基本について説明しました。オブジェクトに加えられた変更のすべてではなく、そのサブセットのみに注目したい場合はどうすればよいでしょうか。誰もが迷惑メール フィルタが必要です。オブザーバーは、受信リストで通知を受け取る変更の種類のみを指定できます。これは、次のように O.o() の 3 番目の引数を使用して指定できます。

Object.observe(obj, callback, optAcceptList)

使用例をご紹介します。

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

ただし、このラベルを削除すると、次のような変更は報告されます。

delete todoModel.label;

O.o() に受け入れるタイプのリストを指定しないと、デフォルトで「固有の」オブジェクト変更タイプ(addupdatedeletereconfigurepreventExtensions(オブジェクトの拡張不可が検出できない場合))になります。

通知

O.o() には通知の概念も用意されています。スマートフォンに表示される煩わしい広告とは異なり、有用なものです。通知はミューテーション オブザーバーに似ています。マイクロタスクの終了時に行われます。ブラウザのコンテキストでは、ほとんどの場合、現在のイベント ハンドラの末尾になります。

通常、1 つの作業単位が完了し、オブザーバーが作業を開始できるタイミングです。これは優れたターンベースの処理モデルです。

通知を使用するワークフローは次のようになります。

通知

オブジェクトのプロパティが取得または設定されたときのカスタム通知を定義するために、通知の実用的な使用例を見てみましょう。コメントは以下の場所で確認できます。

// 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);
通知コンソール

ここでは、データ プロパティの値が変更されたとき(「更新」)にレポートします。オブジェクトの実装で報告されるその他のもの(notifier.notifyChange())。

ウェブ プラットフォームでの長年の経験から、同期アプローチは理解しやすいため、最初に試すアプローチであると考えています。問題は、根本的に危険な処理モデルが作成されることです。コードを記述してオブジェクトのプロパティを更新する場合、そのオブジェクトのプロパティを更新する際に、任意のコードが任意の処理を行う可能性がある状況は避けたいものです。関数の途中で前提条件が破棄されるのは望ましくありません。

オブザーバーは、誰かが何かをしている最中に呼び出されないようにするのが理想的です。世界が不整合な状態になっている状態で作業を開始するのは避けたいものです。エラーチェックが大幅に増える。多くの悪い状況を許容しようとするため、一般的に扱いにくいモデルです。非同期は扱いにくいですが、最終的には優れたモデルです。

この問題の解決策は、合成変更レコードです。

合成変更レコード

基本的に、アクセサラまたは計算プロパティを使用する場合は、これらの値が変更されたときに通知する責任があります。少し手間がかかりますが、このメカニズムのファーストクラスの機能として設計されており、これらの通知は基盤となるデータ オブジェクトからの他の通知とともに配信されます。データのプロパティから。

合成変更レコード

アクセサラと計算プロパティの監視は、O.o() の別の部分である notifier.notify で解決できます。ほとんどの監視システムでは、派生値の監視が何らかの形で必要になります。方法はたくさんあります。O.o は「正しい」方法について判断しません。計算プロパティは、内部(非公開)状態が変更されたときに通知するアクセサタである必要があります。

繰り返しになりますが、ウェブデベロッパーは、ライブラリによって計算プロパティへの通知やさまざまなアプローチが容易になり(ボイラープレートが削減される)、

次に、円クラスの例を設定しましょう。ここでは、この円と半径プロパティがあります。この場合、半径はアクセサラで、値が変更されると、値が変更されたことを通知します。この変更は、このオブジェクトまたは他のオブジェクトに対する他のすべての変更とともに配信されます。基本的に、合成プロパティまたは計算プロパティを持つオブジェクトを実装する場合は、このプロパティをどのように機能させるか戦略を選択する必要があります。設定が完了すると、システム全体に適合します。

コードをスキップして、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);
  })
}
合成変更レコード コンソール

アクセサのプロパティ

アクセサラ プロパティについて簡単に説明します。データ プロパティでは値の変更のみがオブザーバブルであると前述しました。計算プロパティやアクセサには使用できません。これは、JavaScript にはアクセサへの値の変更という概念がないためです。アクセサは、関数の集合にすぎません。

アクセサラに代入すると、JavaScript はそこで関数を呼び出すだけであり、JavaScript の観点からは何も変更されません。一部のコードが実行される機会が与えられただけです。

問題は、上記の値 -5 への代入を意味的に見ると、何が起こったのかを把握できるはずです。これは実際には解決できない問題です。理由は次の例で説明します。これは任意のコードである可能性があるため、システムがその意味を把握することはできません。この場合、任意の処理を行うことができます。値はアクセスされるたびに更新されるため、変更されたかどうかを尋ねるのは意味がありません。

1 つのコールバックで複数のオブジェクトをオブザーバする

O.o() で可能なもう 1 つのパターンは、単一のコールバック オブザーバーの概念です。これにより、1 つのコールバックをさまざまなオブジェクトの「オブザーバー」として使用できます。コールバックには、「マイクロタスクの終了」時に、コールバックが監視するすべてのオブジェクトに対する変更の完全なセットが配信されます(ミューテーション オブザーバーとの類似点に注意してください)。

1 つのコールバックで複数のオブジェクトをオブザーバする

大規模な変更

非常に大きなアプリを開発していて、大規模な変更を定期的に行う必要がある場合などです。オブジェクトは、大量のプロパティ変更をブロードキャストするのではなく、多くのプロパティに影響する大きなセマンティック変更をよりコンパクトな方法で記述できます。

O.o() は、すでに紹介した notifier.performChange()notifier.notify() という 2 つのユーティリティの形で、この処理をサポートします。

大規模な変更

大規模な変更を記述する方法の例として、いくつかの計算ユーティリティ(multiply、increment、incrementAndMultiply)を使用して Thingy オブジェクトを定義する方法を見てみましょう。ユーティリティを使用すると、作業のコレクションが特定のタイプの変更で構成されていることをシステムに通知します。

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

次に、オブジェクトに 2 つのオブザーバーを定義します。1 つは変更のオールキャッチで、もう 1 つは定義した特定の受け入れタイプ(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);
}

これで、このコードを試すことができます。新しい Thingy を定義しましょう。

var thingy = new Thingy(2, 4);

確認してから変更を加えます。とても楽しかったです。たくさんのものがある

// 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 }
大規模な変更

「perform function」内のすべては「big-change」の作業と見なされます。「big-change」を受け入れたオブザーバーは、「big-change」レコードのみを受信します。そうでないオブザーバーは、「関数を実行」した処理によって生じた基盤となる変更を受け取ります。

配列の観察

ここまではオブジェクトの変更の監視について説明してきましたが、配列の場合はどうでしょうか。いい質問ですね。誰かが「いい質問ですね」と言ったとき。素晴らしい質問をした自分を褒め称えるために、相手が何と答えたかは聞きませんが、話がそれました。配列を操作するための新しいメソッドも追加されています。

Array.observe() は、自身に対する大規模な変更(スプライス、unshift、長さを暗黙的に変更する変更など)を「スプライス」変更レコードとして扱うメソッドです。内部では notifier.performChange("splice",...) を使用します。

モデルの「配列」を監視し、基盤となるデータに変更があった場合に変更リストを取得する例を次に示します。

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';
配列の観察

パフォーマンス

O.o() の計算パフォーマンスへの影響を理解するには、読み取りキャッシュと考えるとわかりやすいでしょう。一般的に、キャッシュは次のような場合に最適です(重要度順)。

  1. 読み取りの頻度が書き込みの頻度よりもはるかに高い。
  2. 書き込みに必要な一定量の作業を、読み取り時のアルゴリズムによるパフォーマンスの向上と交換するキャッシュを作成できます。
  3. 書き込みの一定時間の遅延は許容できます。

O.o() は、1)のようなユースケースを対象としています。

ダーティチェックでは、監視対象のすべてのデータのコピーを保持する必要があります。つまり、ダーティチェックに構造的なメモリコストが発生し、O.o() では発生しません。ダーティチェックは、適切な暫定的なソリューションですが、根本的に漏洩する抽象化であり、アプリケーションに不要な複雑さを生む可能性があります。

その理由は、データが変更された可能性がある場合は、いつでもダーティチェックを実行する必要があります。これを実現する非常に堅牢な方法はなく、どのようなアプローチにも重大な欠点があります(ポーリング間隔のチェックでは、視覚的なアーティファクトやコード懸念事項間の競合状態のリスクがあります)。ダーティチェックにはオブザーバーのグローバル レジストリも必要になり、メモリリークの危険性と O.o() が回避する破棄コストが発生します。

数字で確認してみましょう。

以下のベンチマーク テスト(GitHub で入手可能)では、ダーティチェックと O.o() を比較できます。これらのテストは、観測されたオブジェクトセットのサイズとミューテーション数のグラフとして構成されています。一般的な結果として、ダーティチェックのパフォーマンスは、観測されたオブジェクトの数にアルゴリズム的に比例しますが、O.o() のパフォーマンスは、行われたミューテーションの数に比例します。

ダーティチェック

ダーティ チェックのパフォーマンス

Object.observe() がオンの Chrome

パフォーマンスを観察する

Object.observe() のポリフィル

では、Chrome 36 では O.o() を使用できますが、他のブラウザではどうでしょうか?Google がお手伝いしますのでご安心ください。Polymer の Observe-JS は O.o() のポリフィルです。ネイティブ実装が存在する場合はネイティブ実装を使用し、存在しない場合はポリフィルし、便利な糖衣を追加します。世界全体の集計ビューを提供し、変更の概要と変更内容のレポートを提供します。公開される非常に強力な機能は次の 2 つです。

  1. パスをモニタリングできます。つまり、特定のオブジェクトの「foo.bar.baz」を監視したいというように指定すると、そのパスの値が変更されたときに通知されます。パスに到達できない場合、値は未定義と見なされます。

特定のオブジェクトのパスで値を観測する例:

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. 配列の結合について説明します。配列のスプリットは、基本的に、古いバージョンの配列を新しいバージョンの配列に変換するために配列に対して実行する必要があるスプリット オペレーションの最小セットです。これは、配列の変換または配列の別のビューの一種です。古い状態から新しい状態に移行するために必要な最小限の作業量です。

配列の変更を最小限のスプライス セットとして報告する例:

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

フレームワークと Object.observe()

前述のように、O.o() により、フレームワークとライブラリは、この機能をサポートするブラウザでデータ バインディングのパフォーマンスを大幅に改善できるようになります。

Ember の Yehuda Katz 氏と Erik Bryn 氏は、O.o() のサポートの追加が Ember の近い将来のロードマップに含まれていることを確認しました。Angular の Misko Hervy が、Angular 2.0 の変更検出の改善に関する設計ドキュメントを作成しました。長期的なアプローチとしては、Chrome 安定版に Object.observe() が導入されたら、それまでは独自の変更検出アプローチである Watchtower.js を選択して、Object.observe() を活用する予定です。とても楽しみです。

まとめ

O.o() は、ウェブ プラットフォームに追加された強力な機能で、すぐに使用できます。

今後、この機能がより多くのブラウザに導入され、JavaScript フレームワークがネイティブ オブジェクトの観察機能にアクセスしてパフォーマンスを向上させられるようになることを期待しています。Chrome をターゲットとする場合は、Chrome 36 以降で O.o() を使用できます。この機能は、今後の Opera リリースでも利用可能になる予定です。

ぜひ、JavaScript フレームワークの作成者に Object.observe() について、また、それをどのように使用してアプリ内のデータ バインディングのパフォーマンスを改善する予定かについて、話しかけてみてください。今後も、楽しみな機能が追加される予定です。

リソース

Rafael Weinstein、Jake Archibald、Eric Bidelman、Paul Kinlan、Vivian Cromwell の皆様、ご意見とレビューをありがとうございました。