Революція дата-Біндінга з 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 () (мені подобається називати його Oo () або Oooooooo), ви можете реалізувати двосторонній дата-Біндінг без необхідності використовувати фреймоврк.
 
Це зовсім не говорить про те, що вам не варто його використовувати. Для великих проектів зі складною бізнес-логікою, фреймворки необхідні, і вам я не збираюся отговорівать вас від їх використання. Вони націлені на спрощення роботи для нових розробників, вимагає менше коду для підтримки і вводять певні шаблони для роботи над спільними завданнями. Коли вам не потрібно подібний функціонал, ви можете використовувати більш легковагі рішення, такі, як polymer (який, до речі, вже використовує Oo ()).
 
Навіть якщо ви у всю використовуєте фреймворки або MV * бібліотеки, Oo () дозволить їм отримати непоганий приріст в продуктивності, який досягається за рахунок швидкої, спрощеної реалізації, і в той же час продовжує використовувати той же інтерфейс API. Наприклад, останній рік команда розробників Angular провела порівняльний аналіз і встановила, що dirty-checking займає приблизно 40мс, в той час як О.о () займає десь 1-2мс (виходить, швидше в 20-40 разів).
Дата-Біндінг без необхідності використовувати тонни складного коду! А адже це так само означає, що вам більше не доведеться опитувати модель на отримання змін!
 
Якщо ви вже зрозуміли, що робить О.о (), можете відразу гортати до опису нового функціоналу, або ж можете прочитати, які проблема вирішує даний підхід.
 
 

За чим ми збираємося спостерігати?

Коли ми говоримо про спостереження за даними, ми зазвичай маємо на увазі відстеження декількох типів змін:
 
     
  • Зміна нативних JavaScript об'єктів
  •  
  • Додавання, зміна або видалення властивостей
  •  
  • Коли видаляються або додаються дані в масив
  •  
  • Зміни в прототипі об'єкта
  •  
 

Про важливість дата-Біндінга

Дата-Біндінг починає стає важливою частиною вашої програми, коли ви починаєте зачіпати взаємодія моделі і уявлення. HTML є чудовим декларативним механізмом, але він повністю статичний. В ідеалі, ви просто хочете пов'язати ваші дані з DOM, і тримати його в постійно актуальному стані. Рішення з Oo () дозволяє вам заощадити велику кількість часу, за рахунок відсутності необхідності писати великі шматки повторюваного коду, який буде просто посилати нові дані в 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.
 
 
 
Основоположна ідея dirty-checking'а полягає в тому, що в будь-який момент часу дані можуть бути змінені, і бібліотеці необхідно перевірити, яким чином вони змінилися. У разі ангуляр, це відбувається через реєстрацію станів всіх даних моделі, які необхідно відстежувати. Він знає про попередні значеннях моделі і якщо вони змінюються, виникає відповідна подія. Для розробника, основною профіт зааключается в тому, що ми працюємо з нативними JS об'єктами, які легко підтримувати і об'єднувати. З іншого боку, в міру того, що алгоритм вельми ненажерливий, це може виявитися дуже дорогим рішенням.
 
 
 
Витрати на такі операції пропорційні загальній кількості спостережуваних об'єктів. Можливо, мені потрібно робити дуже багато подібних перевірок. Так само можливо, що мені буде потрібно спосіб викликати події 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.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
  });
}

Замітка: Коли у спостерігача викликається яка повертається функція, спостережувані об'єкти можуть бути змінені кілька разів, тому для кожного зміни, нове значення і поточне значення не обов'язково одне і те ж.
 
Ми можемо спостерігати за такими змінами, використовуючи Oo (), передаючи в якості першого аргументу спостережуваний об'єкт і в якості другого возвращаемую функцію:
 
 
Object.observe(todoModel, observer);

Давайте спробуємо щось зробити з нашою моделлю:
 
 
todoModel.label = 'Buy some more milk';

Подивіться в консоль, ми отримали ряд корисної інформації! Ми знаємо, яке властивість змінилося, як воно було змінено і яке нове значення йому було присвоєно.
 
Вау! Прощай, dirty-checking! Напис на твоєму надгробку буде викарбувано Comic Sans'ом. Давайте змінимо інша властивість. Цього разу completeBy:
 
 
todoModel.completeBy = '01/01/2014';

Як ми бачимо, ми знову успішно отримали звіт про зміну:
 
Відмінно, а що якщо тепер ми вирішимо видалити з нашого об'єкта властивість «completed»:
 
 
delete todoModel.completed;

 
Тепер, як ми можемо бачити, звіт про зміни включає в себе інформацію про видалення. Як і очікувалося, нове значення властивості тепер undefined. Так, тепер ми знаємо, що можемо відстежити, коли нові властивості були додані або видалені. Простіше кажучи, ряд властивостей об'єкта («new», «deleted», «reconfigured») і його ланцюжка прототипів.
 
Як і в будь-якій системі спостереження, повинен існувати метод для припинення спостереження за змінами об'єкта. У нашому випадку, це Object.unobserve (), який має таку ж сигнатуру, як і Oo (), але може бути викликаний сл. чином:
 
 
Object.unobserve(todoModel, observer);

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

фокусуємося на властивостях

Отже, коли ми познайомилися з основами, давайте повернемося до списку змін спостережуваного об'єкта.
 
 
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;

Якщо ви не вказуєте список застосовних типів до Oo (), то за замовчуванням передається «внутрішній» об'єкт, що регламентує зміни «add», «update», «delete», «reconfigure», «preventExtensions» (якщо об'єкт не можна змінити, він не піддається спостереженню).
 
 

Нотифікації

У Oo () так само є поняття нотифікацій (повідомлень). Вони не мають нічого спільного з тими надокучливими штуками, які спливають у вашому телефоні, вони набагато корисніше. Нотифікації схожі з 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);

 
 
Тут ми виводимо оповіщення при зміні даних («update»), та й усього, чого завгодно, власне кажучи, якщо це зазначено у виклику методу notifier.notifyChange ().
 
Роки досвіду у веб-розробці навчили нас, що синхронні операції — це перше, чого ми вчимося, тому що найпростіше управляти саме такими операціями. Проблема полягає в тому, що це створює потенційно небезпечну процессинговую модель (модел обробки даних пр. пров. ). Якщо ви пишіть код і, скажімо, пишіть «обнови властивість об'єкта», ви навряд чи хочете, щоб це призвело до спрацювання якогось довільного коду в середині функції, який зможе робити все, що захоче.
 
Навіть з боку спостерігача, ви не хочете, щоб якась стороння функція була викликана з середини поточної функції і т.п. Неузгодженість і роз'єднаність місць викликів функцій дратує, вірно? І додайте до цього ще перевірки на помилки, та інші ситуації, які можуть ускладнити життя при даному підході. І в результаті ми бачимо, що з подібною моделлю реально важко працювати. Асинхронний підхід складніше в розумінні, але все ж, на сьогоднішній день, нічого краще нього немає.
 
Рішенням цієї проблеми може стати синтетичне зміні записів.
 
 

Синтетичне зміна записів

Простіше кажучи, якщо вам буде потрібно отримати доступ до акцессору або обчислюваному властивості (про вич. Властивості можна прочитати тут ), на вас лягає відповідальність за створення оповіщення при зміні цієї властивості. Це трохи додає роботи, але скоріше є фичей, ніж незручністю. Ваші оповіщення будуть доставлені разом з оповіщенні про зміну інших властивостей і матимуть сл. вид:
 
 
 
Спостереження за акцессорамі та змінами обчислюваних властивостей може бути виконано за допомогою notifier.notify (це теж включено в специфікацію Object.observe). Багато системи для спостереження за змінами повинні надавати інформацію про змінені значеннях, і, чесно кажучи, у нас є досить багато способів, як це зробити. Object.observe не нав'язує нам «правильний» шлях.
 
Я думаю, що веб-розробники можуть очікувати від бібліотек якісь готові рішення для допомоги з оповіщенням зміни обчислюваних властивостей.
 
Тепер давайте перейдемо до сл. Наприклад, який ілюструє нам створення класу «Круг». Суть в тому, що у нас є коло і його властивість «радіус». У нашому прикладі радіус буде аксессор, і коли його значення зміниться, виникне подія, що оповіщає про це. Воно буде доставлено разом з усіма іншими оповіщеннями про зміну об'єкта.
 
Давайте подивимося, як наш код буде працювати в DevTools:
 
 
function Circle® {
  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® {
      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 насправді не має можливості відслідковувати зміни значення для акцессоров, адже за фактом вони є лише колекцією функцій.
 
Якщо ви вже працювали з акцессорамі, то можете собі уявити, як вони працюють: вони просто надає набір функцій, які дають доступ до властивостей, і нічого більше.
 
 

Спостереження за кількома об'єктами з одного повертається функцією

Іншим можливим патерном при роботі з Oo () є нотація використання спостереження за об'єктом з єдиною повертається функцією. Це дозволяє використовувати дану функцію як функції-спостерігача для будь-якої кількості різних об'єктів. Возвращаемая функція буде надавати кожен раз повний набір змін для всіх об'єктів, які вона відстежує (це відбуватиметься по закінченню всіх мікрозадач, см. Mutation Observers).
 
 
 

Масштабні зміни

Можливо, ви працюєте над реально огрооооомним проектом і регулярно змушені стикатися з масштабними змінами.
Oo () допомагає в цьому за допомогою двох специфічний функцій: notifier.performChange () і notifier.notify (), про який ми вже говорили.
 
 
 
Давайте подивимося на приклад того, як масштабні зміни можуть б описані за допомогою 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® {
    console.log®;
    observer.records = r;
    observer.callbackCount++;
};

observer2.callback = function®{
	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 }

 
 
 
Все, що знаходиться всередині «виконуваної функції» будемо вважати роботою з «великою кількістю змін». Спостерігачі, які приймають «великі зміни» отримуватимуть тільки їх. Решта спостерігачі будуть отримувати інші зміни.
 
 

Спостереження за масивами

Ми поговорили про спостереження змін у об'єктів, але що щодо масивів? Відмінний питання! До речі кажучи, кожного разу, коли мені кажуть «Відмінний питання», я ніколи не чую відповідь, тому що занадто сконцентрований на привітанні себе з таким вдалим питанням, але ми відволіклися :) У нас є нові методи і для роботи з масивами!
 
 
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';

 
 
 

Продуктивність

Про обчислювальної швидкості Oo () можна думати як про швидкість читання кеша. Взагалі кажучи, кеш є відмінним вибором, коли (у порядку важливості):
  • Частота читання вище, ніж частота запису
  • Коли у вас є можливість створити кеш, який буде жертвувати часом записи в сторону фіксованого часу на операції читання
  • Постійне час затримки для операцій запису типово
Oo () спроектований для use-кейсів начебто перший.

Dirty-checking вимагає тримати копії всіх даних, за якими ви спостерігаєте. Це означає, що ви отримуєте просідання по пам'яті, яку ви ніколи не отримаєте з Oo (). Dirty-checking є свого роду затичкою, яка так само створює свого роду непотрібну абстракцію, яка в результаті створює зайві складності в додатках.

Чому? Тому, що dirty-checking запускається кожного разу, коли дані «могли» бути змінені. Це не є надійним способом подібної перевірки і має суттєві недоліки, наприклад, гонку між кодом рендеринга (і т.п.) і обчислювальним кодом (адже всі знають, що в JS використовується один потік і для інтерфейсу і для обчислень?). Для dirty-checking так само потрібне підключення глобального реєстру спостерігачів, створюючи тим самим ризики витоку пам'яті і т.п., чого дозволяє уникнути Oo ().

Давайте поглянемо на деякі цифри.

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

Головні результати — це те, що продуктивність dirty-checking'а пропорційна кількості спостережуваних об'єктів, в той час як продуктивність Oo () пропорційна кол-ву мутацій, які ми зробили.

Dirty-checking


Object.observe ()


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

Круто, Oo () може бути сіпользован в 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 ()

Як ми вже говорили, Oo () дає фреймворками і бібліотекам величезні можливості поліпшити продуктивність їх механізму дата-Біндінга в браузерах, що підтримують це нововведення.

Ієгуда Кац і Ерік Брін з Ember затвердили підтримку Oo () в найближчих roadmap'ах Ember'а. Мисько Херві (з Angular) так само написав у проекті своєї документації до Angular 2.0 про поліпшення детектування змін.
Yehuda Katz and Erik Bryn from Ember confirmed that adding support for Oo () 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.

Підсумки

Oo () — потужне нововведення для web платформи, яке ви можете використовувати вже сьогодні.

Ми сподіваємося, що це функціональність незабаром з'явиться і в інших браузерах, дозволяючи JavaScript фреймворк отримати якийсь виграш в продуктивності з новими нативними можливостями об'єктів і спостереженням за ними. Крім Chrome 36, ця функціональність так само буде доступно в найближчому релізі Opera.

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

Використані ресурси:


Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.