Революция дата-байндинга с Object.Observe()

в 16:02, , рубрики: ECMAScript, html5rocks, javascript, object.observe, Веб-разработка, Программирование

Введение

Грядет революция. Появилось новое дополнение к JavaScript, которое изменит всё, что вы когда-либо знали о дата-байндинге. Помимо этого, изменится и подход ваших MVC библиотек к наблюдениям за редактированием и обновлением моделей. Вы готовы?

Хорошо, хорошо. Не будем тянуть. Я рад вам представить Object.observe(), который появился в бета версии Chrome 36. [ТОЛПА ЛИКУЕТ]

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()
С помощью Object.observe()(мне нравится называть его O.o() или Oooooooo), вы можете реализовать двухсторонний дата-байндинг без необходимости использовать фреймоврк.

Это совсем не говорит о том, что вам не стоит его использовать. Для больших проектов со сложной бизнес-логикой, фреймворки необходимы, и вам я не собираюсь отговоривать вас от их использования. Они нацелены на упрощение работы для новых разработчиков, требует меньше кода для поддержки и вводят определенные шаблоны для работы над общими задачами. Когда вам не требуется подобный функционал, вы можете использовать более легковесные решения, такие, как polymer(который, кстати, уже использует O.o()).

Даже если вы во всю используете фреймворки или MV* библиотеки, O.o() позволит им получить неплохой прирост в производительности, который достигается за счёт быстрой, упрощенной реализации, и в то же время продолжает использовать тот же интерфейс API. Например, последний год команда разработчиков Angular провела сравнительный анализ и установила, что dirty-checking занимает приблизительно 40мс, в то время как О.о() занимает где-то 1-2мс(получается, быстрее в 20-40 раз).
Дата-байндинг без необходимости использовать тонны сложного кода! А ведь это так же означает, что вам больше не придется опрашивать модель на получение изменений!

Если вы уже поняли, что делает О.о(), можете сразу листать к описанию нового функционала, или же можете прочитать, какие проблема решает данный подход.

За чем мы собираемся наблюдать?

Когда мы говорим о наблюдении за данными, мы обычно имеем ввиду отслеживание нескольких типов изменений:

  • Изменение нативных JavaScript объектов
  • Добавление, изменение или удаление свойств
  • Когда удаляются или добавляются данные в массив
  • Изменения в прототипе объекта

О важности дата-байндинга

Дата-байндинг начинает становится важной частью вашего приложения, когда вы начинаете затрагивать взаимодействие модели и представления. HTML является великолепным декларативным механизмом, но он полностью статичен. В идеале, вы просто хотите связать ваши данные с DOM, и держать его в постоянно актуальном состоянии. Решение с O.o() позволяет вам сэкономить большое количество времени, за счёт отсутствия необходимости писать большие куски повторяющегося кода, который будет просто посылать новые данные в DOM, и наобарот.

Дата-байндинг действительно удобен, когда вы создаете комплексный пользовательский интерфейс, где вам необходимо наладить большое количество связей между различными свойствами ваших моделей и UI элементами, отражающими их. Это одна из самых распространенных задач во время создания SPA(Single Page Application) приложений.

Первоначально, у нас небыло никакого механизма для наблюдения за данными, и мы перекладывали ответственность за это на различные JavaScript фреймворки(или же писали небольшие библиотеки), полагаясь на различные медленные хаки, которые мир использует и по сей день.

Что мир представляет из себя сегодня?

Dirty-checking

Где вы видели дата-байндинг до этого момента? Ну, если вы используете современные MV*-библиотеки для создания ваших веб приложений(Angular, Knockout), то вы, возможно, уже использовали привязку данных модели к вашему DOM. Чтобы освежить это в памяти, вот вам пример приложения «Телефонная книга», где мы биндим номер каждого телефона из массива номеров к элементу списка, и таким образом поддерживаем постоянную синхронизацию между ними:

<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">
        {{phone.name}}
        <p>{{phone.snippet}}</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 достигает этого? Что ж, за сценой он выполняет то, что мы называется dirty-checking.

Революция дата байндинга с Object.Observe()

Основополагающая идея dirty-checking´а заключается в том, что в любой момент времени данные могут быть изменены, и библиотеке необходимо проверить, каким образом они изменились. В случае ангуляра, это происходит через регистрацию состояний всех данных модели, которые необходимо отслеживать. Он знает о предыдущих значениях модели и если они изменяются, возникает соответствующее событие. Для разработчика, основной профит зааключается в том, что мы работаем с нативными JS объектами, которые легко поддерживать и объединять. С другой стороны, в меру того, что алгоритм весьма прожорлив, это может оказаться очень дорогим решением.

Революция дата байндинга с Object.Observe()

Расходы на такие операции пропорциональны общему количеству наблюдаемых объектов. Возможно, мне потребуется делать очень много подобных проверок. Так же возможно, что мне потребуется способ вызвать события dirty-checking´а, когда модель «возможно» изменилась. Существует довольно много хитрых трюков, к которым прибегает фреймворк чтобы реализовать подобные решения. Пока еще непонятно, доведут ли данное решение до ума.

Экосистема веба должна иметь больше возможностей для совершенствования и развития своих декларативных механизмов, например

  • Системы моделей, основанных на контейнерах с внешним доступом к аттрибутам через сеттеры/геттеры
  • Системы с автосохранением(сохраняющие изменения в IndexedDB или localStorage)
  • Объекты-контейнеры (например, Backbone, Ember)

Объекты-контейнеры — это механизм хранения данных, при котором фреймворк создает объекты, которые хранят внутри себя данные, доступ к которым предоставляется с помощью акцессоров(геттеры/сеттеры), а так же реализует возможность подписки на любые свои изменения. Это работает весьма неплохо: алгоритм обеспечивает довольно быструю работу. Пример использования таких объектов в Ember можно найти ниже:

// Container objects
MyApp.president = Ember.Object.create({
  name: "Barak 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 кодом, т.к. большая часть кода предполагает, что он может взаимодействовать с обычными данными(не обёрнутыми в объект-контейнер пр. пер.), но не с этими специализированными объектами.

Введение в Object.observe()

Было бы по-настоящему здорово, если бы мы могли получить лучшее от этих двух вселенных: возьмем возможность наблюдать за изменением данных с поддержкой для обычных объектов(нативные объекты JavaScript) и уберем dirty-checking, а вместо него добавим какой-нибудь алгоритм с хорошим характеристиками. Какой-нибудь такой, который будет объединять все эти положительные качества и будет встроен в платформу. Так вот, встречайте — Object.observe(), уже готовый к использованию!

Он позволяет нам наблюдать за объектом, изменять свойства и наблюдать за изменением через отчеты об изменениях. Но хватит теории, давайте посмотрим на код!
Революция дата байндинга с Object.Observe()

Object.observe() и Object.unobserve()

Давайте представим, что мы имеем нативный JS объект, представляющий модель:

// 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(), передавая в качестве первого аргумента наблюдаемый объект и в качестве второго возвращаемую функцию:

Object.observe(todoModel, observer);

Давайте попробуем что-нибудь сделать с нашей моделью:

todoModel.label = 'Buy some more milk';

Посмотрите в консоль, мы получили ряд полезной информации! Мы знаем, какое свойство изменилось, как оно было изменено и какое новое значение ему было присвоено.
Революция дата байндинга с Object.Observe()
Вау! Прощай, dirty-checking! Надпись на твоём надгробии будет высечена Comic Sans'ом. Давайте изменим другое свойство. В этот раз completeBy:

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

Как мы видим, мы опять успешно получили отчёт об изменении:
Революция дата байндинга с Object.Observe()
Отлично, а что если теперь мы решим удалить из нашего объекта свойство «completed»:

delete todoModel.completed;

Революция дата байндинга с Object.Observe()
Теперь, как мы можем видеть, отчет об изменениях включает в себя информацию об удалении. Как и ожидалось, новое значение свойства теперь undefined. Так, теперь мы знаем, что можем отследить, когда новые свойства были добавлены или удалены. Проще говоря, ряд свойств объекта(«new», «deleted», «reconfigured») и его цепочки прототипов.

Как и в любой системе наблюдения, должен существовать метод для прекращения наблюдения за изменениями объекта. В нашем случае, это Object.unobserve(), который имеет такую же сигнатуру, как и O.o(), но может быть вызван сл. образом:

Object.unobserve(todoModel, observer);

Как мы можем видеть ниже, любые изменения, которые были сделаны после вызова последнего метода, больше не выведут в консоль отчет об изменениях.

Революция дата байндинга с Object.Observe()

Фокусируемся на свойствах

Итак, когда мы познакомились с основами, давайте вернемся к списку изменений наблюдаемого объекта.

Object.observe(obj, callback, opt_acceptList)

Давайте сразу перейдем к примеру, чтобы посмотреть, как это можно использовать:

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

Теперь, если мы удалим свойство «label», нотификация об изменении не возникнет:

delete todoModel.label;

Если вы не указываете список применимых типов к O.o(), то по умолчанию передается «внутренний» объект, регламентирующий изменения «add», «update», «delete», «reconfigure», «preventExtensions»(если объект нельзя изменить, он не поддается наблюдению).

Нотификации

В O.o() так же есть понятие нотификаций(оповещений). Они не имеют ничего общего с теми надоедливыми штуками, которые всплывают в вашем телефоне, они намного полезнее. Нотификации схожи с Mutation Observers(и замечательная статья на хабре от zag2art). Они возникают во время окончания выполнения микрозадач. В контексте браузера, они почти всегда возникают в конце текущего обработчика события.

Такое время крайне удобно из-за того, что основная работа уже была выполнена, и наблюдатели могут начать делать свою работу, не мешая основной логике. Это отличная модель пошаговой обработки событий

Рабочий процесс, построенный с использованием оповещений выглядит как-то так:

image

А теперь давайте посмотрим на пример того, как нотификации могут быть использованы для оповещения об изменении состояния объекта. Обратите внимание на комментарии:

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

Революция дата байндинга с Object.Observe()

Здесь мы выводим оповещение при изменении данных(«update»), да и всего, чего угодно, собственно говоря, если это указано в вызове метода notifier.notifyChange().

Годы опыта в веб-разработке научили нас, что синхронные операции — это первое, чему мы учимся, т.к. проще всего управлять именно такими операциями. Проблема заключается в том, что это создает потенциально опасную процессинговую модель(модел обработки данных пр. пер.). Если вы пишите код и, скажем, пишите «обнови свойство объекта», вы вряд ли хотите, чтобы это привело к срабатыванию какого-то произвольного кода в середине функции, который сможет делать всё, что захочет.

Даже со стороны наблюдателя, вы не хотите, чтобы какая-то сторонняя функция была вызвана из середины текущей функции и т.п. Несогласованность и разобщенность мест вызовов функций раздражает, верно? И прибавьте к этому ещё проверки на ошибки, и прочие ситуации, которые могут усложнить жизнь при данном подходе. И в результате мы видим, что с подобной моделью реально трудно работать. Асинхронный подход сложнее в понимании, но всё же, на сегодняшний день, ничего лучше него нет.

Решением этой проблемы может стать синтетическое изменении записей.

Синтетическое изменение записей

Проще говоря, если вам потребуется получить доступ к акцессору или вычисляемому свойству(про выч. свойства можно прочитать тут), на вас ложится ответственность за создание оповещения при изменении этого свойства. Это немного добавляет работы, но скорее является фичей, чем неудобством. Ваши оповещения будут доставлены вместе с оповещении об изменении других свойств и будут иметь сл. вид:

Революция дата байндинга с Object.Observe()

Наблюдение за акцессорами и изменениями вычисляемых свойств может быть выполнено с помощью notifier.notify(это тоже включено в спецификацию Object.observe). Многие системы для наблюдения за изменениями должны предоставлять информацию об измененных значениях, и, честно говоря, у нас есть довольно много способов, как это сделать. Object.observe не навязывает нам «правильный» путь.

Я думаю, что веб-разработчики могут ожидать от библиотек какие-то готовые решения для помощи с оповещением изменения вычисляемых свойств.

Теперь давайте перейдем к сл. примеру, который иллюстрирует нам создание класса «Круг». Суть в том, что у нас есть круг и его свойство «радиус». В нашем примере радиус будет аксессором, и когда его значение поменяется, возникнет событие, оповещающее об этом. Оно будет доставлено вместе со всеми остальными оповещениями об изменении объекта.

Давайте посмотрим, как наш код будет работать в 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);
  })
}

Революция дата байндинга с Object.Observe()

Свойства акцессоров

Небольшое примечение о свойствах акцессора. Ранее мы говорили только о наблюдении на изменениями значений свойств, но совсем не упоминали подобное поведение для акцессоров или вычисляемых свойств. Причиной тому является то обстоятельство, что JavaScript на самом деле не имеет возможности отслеживать изменения значения для акцессоров, ведь по факту они являются лишь коллекцией функций.

Если вы уже работали с акцессорами, то можете себе представить, как они работают: они просто предоставляет набор функций, которые дают доступ к свойствам, и ничего больше.

Наблюдение за несколькими объектами с одной возвращаемой функцией

Другим возможным паттерном при работе с O.o() является нотация использования наблюдения за объектом с единственной возвращаемой функцией. Это позволяет использовать данную функцию в качестве функции-наблюдателя для любого количества различных объектов. Возвращаемая функция будет предоставлять каждый раз полный набор изменений для всех объектов, которые она отслеживает(это будет происходить по окончанию всех микрозадач, см. Mutation Observers).

Революция дата байндинга с Object.Observe()

Масштабные изменения

Возможно, вы работаете над реально огрооооомным проектом и регулярно вынуждены сталкиваться с масштабными изменениями.
O.o() помогает в этом с помощью двух специфический функций: notifier.performChange() и notifier.notify(), о котором мы уже говорили.

Революция дата байндинга с Object.Observe()

Давайте посмотрим на пример того, как масштабные изменения могут бы описаны с помощью Thingy object с помощью некоторых математических функций(multiply, increment, incrementAndMultiply). Каждый раз, когда мы используем функцию, она говорит системе, что коллекция работ включает в себя определенный тип изменений.

Например: 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
    });
  }
}

Мы объявляем два наблюдателя для нашего объекта: один для наблюдения за всеми изменениями и другой для отчетов о специфических изменениях, которые мы описали выше(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 }

Революция дата байндинга с Object.Observe()

Всё, что находится внутри «выполняемой функции» будем считать работой с «большим количеством изменений». Наблюдатели, которые принимают «большие изменения» будут получать только их. Оставшиеся наблюдатели будут получать остальные изменения.

Наблюдение за массивами

Мы поговорили о наблюдении изменений у объектов, но что насчет массивов? Отличный вопрос! Кстати говоря, каждый раз, когда мне говорят «Отличный вопрос», я никогда не слышу ответ, т.к. слишком сконцентрирован на поздравлении себя с таким удачным вопросом, но мы отвлеклись :) У нас есть новые методы и для работы с массивами!

Array.observe() — это метод, который работает с большим количеством изменений самого объекта, например splice, unshift или что-либо другое, что изменяет его длинну, как, например, splice.
Для этого он использует 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';

Революция дата байндинга с Object.Observe()

Производительность

О вычислительной скорости O.o() можно думать как о скорости чтения кеша. Вообще говоря, кеш является отличным выбором, когда(в порядке важности):

  • Частота чтения выше, чем частота записи
  • Когда у вас есть возможность создать кеш, который будет жертвовать временем записи в сторону фиксированного времени на операции чтения
  • Постоянное время задержки для операций записи приемлимо

O.o() спроектирован для use-кейсов вроде первого.

Dirty-checking требует держать копии всех данных, за которыми вы наблюдаете. Это значит, что вы получаете просадку по памяти, которую вы никогда не получите с O.o(). Dirty-checking является своего рода затычкой, которая так же создает своего рода ненужную абстракцию, которая в результате создает лишние сложности в приложениях.

Почему? Потому, что dirty-checking запускается каждый раз, когда данные «могли» быть изменены. Это не является надежным способом подобной проверки и имеет существенные недостатки, например, гонку между кодом рендеринга(и т.п.) и вычислительным кодом(ведь все знают, что в JS используется один поток и для интерфейса и для вычислений?). Для dirty-checking так же требуется подключение глобального реестра наблюдателей, создавая тем самым риски утечки памяти и т.п., чего позволяет избежать O.o().

Давайте взглянем на некоторые цифры.

Представленные ниже бенчмарки(доступны на GitHub) позволяют нам сравни dirty-checking и O.o(). Они представлены в виде графа с абсциссой Observed-Object-Set-Size и ординатой Number-Of-Mutations.

Главные результаты — это то, что производительность dirty-checking'а пропорциональна количеству наблюдаемых объектов, в то время как производительность O.o() пропорциональна кол-ву мутаций, которые мы сделали.

Dirty-checking

Революция дата байндинга с Object.Observe()

Object.observe()

Революция дата байндинга с Object.Observe()

Object.observe() для старых браузеров

Круто, O.o() может быть сипользован в Chrome 36 beta, но что насчет остальных браузеров? Мы вам поможем. Observe-JS — это полифил для Polymer, который будет использовать нативную реализацию, как только она появится, но он так же включает в себя несколько полезных вещей поверх этого. Он предлагает использовать обобщенный взгляд на объекты наблюдения и докладывает об общих изменениях. Две наиболее полезных вещей, которые он предлагает:

1) Вы можете наблюдать за «путями». Это означает, что вы можете сказать «эй, я хочу следить за foo.bar.baz» у выбранного объекта и он будет оповещать вас об изменении свойств, как только оно будет происходить. Если путь недоступен, он вернет undefined/

Пример наблюдения за значением по пути указанного объекта:

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

2) Он будет оповещать вас об изменении длинны массивов. Изменение длинны массива в нашем случае — это минимальное количество splice-операций, которые мы должны сделать с массивом, чтобы перевести его из старого состояния в новое(изменившееся).

Пример оповещения о таких изменениях касательно массива, как минимальный набор splice-операций:

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 утвердили поддержку O.o() в ближайших roadmap'ах Ember'а. Миско Херви(из Angular) так же написал в проекте своей документации к Angular 2.0 об улучшении детектирования изменений.
Yehuda Katz and Erik Bryn from Ember confirmed that adding support for O.o() is in Ember's near-term roadmap. Angular's Misko Hervy wrote a design doc on Angular 2.0's improved change detection. Я думаю, что наиболее вероятно ожидать движения в этом направлении, когда эта фича появится в пакете Chrome 36 stable.

Итоги

O.o() — мощное нововведение для web платформы, которое вы можете использовать уже сегодня.

Мы надеемся, что это функциональность вскоре появится и в других браузерах, позволяя JavaScript фреймворкам получить некий выигрыш в производительности с новыми нативными возможностями объектов и наблюдением за ними. Помимо Chrome 36, эта функциональность так же будет доступно в ближайшем релизе Opera.

Что ж, теперь вы можете идти рассказывать авторам JS фреймворков об Object.observe() и как они могут использовать его, чтобы улучшить их механизм data-binding'а.
Похоже, грядут действительно удивительные времена!

Использованные ресурсы:

Автор: xamd

Источник

Поделиться