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

Addy Osmani
Addy Osmani

はじめに

革命が起ころうとしています。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 の調査によると、モデルに変更が加えられているベンチマークでは、ダーティ チェックは更新あたり 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

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

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

Introducing Object.observe()

理想的には、求めているのは両方の長所を活かしたもので、元データ オブジェクト(通常の JavaScript オブジェクト)をサポートしてデータを監視する方法です。ただし、常にすべてのダーティチェックを行う必要がなく、かつ(AND)ことを選択した場合です。アルゴリズムの動作が優れているもの。適切に構成され、プラットフォームに組み込まれているもの。これが 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);

まず、Todos モデル オブジェクトに変更を加えます。

todoModel.label = 'Buy some more milk';

コンソールに有用な情報が表示されています。どのプロパティが変更されたか、どのように変更されたか、新しい値がどのようなものかがわかっています。

コンソールのレポート

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

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

再び正常に変更レポートを取得しているのが確認できます。

変更レポート。

これでオブジェクトから「completed」プロパティを削除するとどうなりますか。

delete todoModel.completed;
完了

ご覧のように、返された変更のレポートには削除に関する情報が含まれています。予想どおり、プロパティの新しい値は未定義になります。これで、宿泊施設が追加されたタイミングを確認できるようになりました。アイテムが削除されたとき。基本的には、オブジェクトのプロパティ セット(「new」、「deleted」、「reconfiguration」)と、そのプロトタイプの変更(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 は「正しい」方法に関して何も判断しません。計算プロパティは、内部(非公開)状態が変更されたときに通知するアクセサタである必要があります。

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

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

コードをスキップして、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 はそこで関数を呼び出すだけで、その視点からは何も変わりません。コードを実行する機会が得られるだけです。

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

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

O.o() で可能なもう 1 つのパターンは、単一のコールバック オブザーバーの概念です。これにより、1 つのコールバックを多くの異なるオブジェクトの「オブザーバー」として使用できます。このコールバックは、「マイクロタスクの最後」に、観察したすべてのオブジェクトに対するすべての変更セットを渡します(Mutation Observer と同様です)。

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() を比較できます。これらは、Observed-Object-Set-Size と Number-Of-Mutations のグラフとして構成されています。一般的な結果として、ダーティ チェックのパフォーマンスはアルゴリズム的に観測対象オブジェクトの数に比例し、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.
  });
});

Framework と Object.observe()

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

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

まとめ

O.o() は、すぐに使用できるウェブ プラットフォームへの強力な付加です。

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

そこで、Object.observe() について JavaScript フレームワークの作成者に話を聞いて、どのようにアプリのデータ バインディングのパフォーマンスを改善しようとしているのかを相談してみましょう。今後も、楽しみなイベントが盛りだくさんです。

リソース

Rafael Weinstein、Jake Archibald、Eric Bidelman、Paul Kinlan、Vivian Cromwell の皆様からいただいたご意見とレビューに感謝いたします。