2013-02-10 2 views
8

В последнее время я искал рамки JavaScript, такие как Angular и Meteor, и мне было интересно, как они узнают, когда изменилось свойство объекта, чтобы они могли обновлять DOM.Создание объектов, которые можно наблюдать

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

Но с появлением геттеров и сеттеров в JS 1.8.5 мы можем сделать лучше, не так ли?

Как немного проверки концепции, я соединял этот скрипт:

(Edit: обновленного кода для добавления зависимого-свойства/метод поддержка)

function dependentProperty(callback, deps) { 
    callback.__dependencies__ = deps; 
    return callback; 
} 

var person = { 
    firstName: 'Ryan', 
    lastName: 'Gosling', 
    fullName: dependentProperty(function() { 
     return person.firstName + ' ' + person.lastName; 
    }, ['firstName','lastName']) 
}; 

function observable(obj) { 
    if (!obj.__properties__) Object.defineProperty(obj, '__properties__', { 
     __proto__: null, 
     configurable: false, 
     enumerable: false, 
     value: {}, 
     writable: false 
    }); 
    for (var prop in obj) { 
     if (obj.hasOwnProperty(prop)) { 
      if(!obj.__properties__[prop]) obj.__properties__[prop] = { 
       value: null, 
       dependents: {}, 
       listeners: [] 
      }; 
      if(obj[prop].__dependencies__) { 
       for(var i=0; i<obj[prop].__dependencies__.length; ++i) { 
        obj.__properties__[obj[prop].__dependencies__[i]].dependents[prop] = true; 
       } 
       delete obj[prop].__dependencies__; 
      } 
      obj.__properties__[prop].value = obj[prop]; 
      delete obj[prop]; 
      (function (prop) { 
       Object.defineProperty(obj, prop, { 
        get: function() { 
         return obj.__properties__[prop].value; 
        }, 
        set: function (newValue) { 
         var oldValue = obj.__properties__[prop].value; 
         if(oldValue !== newValue) { 
          var oldDepValues = {}; 
          for(var dep in obj.__properties__[prop].dependents) { 
           if(obj.__properties__[prop].dependents.hasOwnProperty(dep)) { 
            oldDepValues[dep] = obj.__properties__[dep].value(); 
           } 
          } 
          obj.__properties__[prop].value = newValue; 
          for(var i=0; i<obj.__properties__[prop].listeners.length; ++i) { 
           obj.__properties__[prop].listeners[i](oldValue, newValue); 
          } 
          for(dep in obj.__properties__[prop].dependents) { 
           if(obj.__properties__[prop].dependents.hasOwnProperty(dep)) { 
            var newDepValue = obj.__properties__[dep].value(); 
            for(i=0; i<obj.__properties__[dep].listeners.length; ++i) { 
             obj.__properties__[dep].listeners[i](oldDepValues[dep], newDepValue); 
            } 
           } 
          } 
         } 
        } 
       }); 
      })(prop); 
     } 
    } 
    return obj; 
} 

function listen(obj, prop, callback) { 
    if(!obj.__properties__) throw 'object is not observable'; 
    obj.__properties__[prop].listeners.push(callback); 
} 

observable(person); 

listen(person, 'fullName', function(oldValue, newValue) { 
    console.log('Name changed from "'+oldValue+'" to "'+newValue+'"'); 
}); 

person.lastName = 'Reynolds'; 

каких журналы:

имя изменено с "Райан Гослинг" в "Райан Рейнольдс"

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

Помимо этого, существуют ли какие-либо недостатки этого подхода?

JsFiddle

+0

Недостатком было бы то, что вы должны это написать. Зачем вам это делать, когда есть библиотеки, которые делают это за вас? –

+1

@AshBurlaczenko: Назовите это учебным упражнением. – mpen

+0

На стороне примечания [KnockoutJS] (http://knockoutjs.com/) работает аналогично, только он не использует функции JS 185 AFAIK. – Jeroen

ответ

1

Появление добытчиками и сеттеров в JS 1.8.5 - есть ли минусы такого подхода?

  • Вы не улавливаете никаких изменений свойств помимо наблюдаемых. Конечно, этого достаточно для моделируемых объектов сущности, и для чего-либо еще мы могли бы использовать Proxies.
  • Он ограничен браузерами, которые поддерживают геттеры/сеттеры и, возможно, даже прокси. Но эй, кто заботится об устаревших браузерах? :-) И в ограниченных средах (Node.js) это вообще не выполняется.
  • Свойства аксессуаров (с геттером и сеттером) намного медленнее, чем реальные методы get/set. Конечно, я не ожидаю, что они будут использоваться в критических разделах, и они могут сделать код очень привлекательным. Но вы должны держать это в глубине своего разума. Кроме того, причудливый код может привести к неправильным представлениям - обычно вы ожидаете, что присвоение/доступ к ресурсам будет короткой (O(1)), в то время как с геттерами/сеттерами может быть намного больше. Вам нужно будет не забывать об этом, и использование реальных методов могло бы помочь.

Так что если мы знаем, что мы делаем, да, мы, , можем сделать лучше.

Тем не менее, есть одна огромная точка, которую нам нужно запомнить: синхронность/асинхронность (также посмотрите на this excellent answer). Грязная проверка углового позволяет вам сразу менять набор свойств, до событие срабатывает в следующем повороте цикла события. Это помогает избежать (распространения) семантически недействительных состояний.

Тем не менее, я вижу синхронные геттеры/сеттеры как шанс. Они позволяют нам объявить зависимости между свойствами и определить правильные состояния. Он автоматически обеспечит правильность модели, в то время как нам нужно только изменить одно свойство за раз (вместо того, чтобы менять firstName и fullName все время, firstName достаточно). Тем не менее, во время урегулирования зависимостей это может быть недействительным, поэтому нам нужно позаботиться об этом.

Таким образом, слушатели, не связанные с управлением зависимостями, должны запускаться асинхронно. Просто setImmediate их петля.

+0

(1) «Грязная проверка углового позволяет вам сменить кучу свойств сразу, прежде событие срабатывает в следующем повороте цикла события »- я обратился к нему уже пару раз. Не нужно немедленно запускать обработчики событий, мы можем просто пометить свойство как грязное и запустить все обработчики в конце, то же самое, что и Angular, но без необходимости проверять каждое свойство. (2) По большей части мы должны точно знать, какие свойства необходимо соблюдать, потому что мы должны разбирать шаблон, чтобы настроить часы на этих переменных в любом случае. Может быть, мы пропустили пару из-за ... – mpen

+0

... нестандартное использование, но это более или менее то же самое, что и угловое. В этих случаях вам нужно вручную добавить часы/прослушиватель. (3) Почему сеттеры не были «O (1)»? Предполагая, что мы откладываем выполнение обработчиков до конца события, нам просто нужно надавить свойство на набор «грязных» свойств, а затем обработать его позже, что добавит лишь небольшие накладные расходы постоянным временем, которые должны быть меньше более высокая стоимость проверки * всего * после каждого события. – mpen

+1

On (2): Я не знаком с Angular и не знаю его точной работы - я просто подумал, что с опросом вы можете обнаружить и другие свойства. Моя точка зрения не была посвящена разработке шаблонов, но более общая - она ​​неприменима везде, конечно. (3) Да, если вы задерживаете выполнение сеттеров и просто устанавливаете грязные флаги, вы * можете * сделать это постоянными накладными расходами. Однако моя точка зрения была скорее связана с тем, что программист, который использует простое назначение (ожидая O (1)), точно не знает, что происходит за кулисами (или даже может вообще не знать об этом). – Bergi

Смежные вопросы