В последнее время я искал рамки 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()
на объекте человека, который будет зависеть от двух других свойств. Для этого требуется небольшая дополнительная разметка для объекта, чтобы разработчики могли указать зависимость.
Помимо этого, существуют ли какие-либо недостатки этого подхода?
Недостатком было бы то, что вы должны это написать. Зачем вам это делать, когда есть библиотеки, которые делают это за вас? –
@AshBurlaczenko: Назовите это учебным упражнением. – mpen
На стороне примечания [KnockoutJS] (http://knockoutjs.com/) работает аналогично, только он не использует функции JS 185 AFAIK. – Jeroen