Object.Observe Synchronous Callback
Я экспериментировал с Object.observe в Chrome v36. Моим первоначальным намерением было использовать это для бизнес-логики в моих моделях, но асинхронное поведение делает это невозможным. Я свел это к следующему примеру:
function Person(name) {
this.name = name;
this.someOtherProperty = null;
this.watch = function()
{
var self = this;
Object.observe(this, function(changes){
for(var i = 0; i < changes.length; i++)
{
if(changes[i].name == "name")
{
self.someOtherProperty = changes[i].newValue;
console.log("Property Changed");
}
}
});
}
}
$(function () {
var p = new Person("Alice");
p.watch();
p.name = "Bob";
$("#output").text("Output: "+ p.someOtherProperty);
console.log("Output");
});
Ссылка JSFiddle, с помощью jQuery.
Моя проблема заключается в том, что "Выход" вызывается до "Свойство изменено". Есть ли способ сделать Object.Observe синхронным, или я должен сделать это лучше? (Я использую AngularJS, кстати.)
Проблема здесь не в том, чтобы добавлять текст в DOM или выводить на консоль. Моя бизнес-логика требует от меня немедленного обновления someOtherPropety
когда name
изменения, и я бы предпочел инкапсулировать эту логику в моей модели.
Очевидно, что это только пример, но у меня есть бизнес-правила, которые основаны на немедленном исполнении.
3 ответа
Object.observe
"К сожалению" (читать далее), не выполняет синхронную задачу. Он отправляет уведомления об изменениях, как только "микро-задача" заканчивается.
Это объясняется здесь.
Многолетний опыт работы с веб-платформой научил нас, что синхронный подход - это первое, что вы пробуете, потому что его легче всего обернуть. Проблема в том, что он создает принципиально опасную модель обработки. Если вы пишете код и говорите, обновите свойство объекта, вы на самом деле не хотите, чтобы ситуация, когда обновление свойства этого объекта могло бы привести к тому, что какой-то произвольный код мог пойти и сделать то, что ему нужно. Это не идеальный вариант, когда ваши предположения становятся недействительными, когда вы проходите через середину функции.
Итак, ваша "микро-задача" заканчивается после console.log("Output")
был вызван, то Object.observe
уведомляет об изменениях на объекте.
Классический метод для синхронных событий - использовать вместо этого методы получения и установки:
Person.prototype.setName = function(name) {
this.name = name;
console.log("Name Changed");
};
p.setName("Bob");
Конечно, это заставит вас создавать методы получения и установки для каждого свойства, которое вы хотите просмотреть, и забывать о событиях при удалении и добавлении новых свойств.
Как вы говорите, наблюдать не синхронно. Но вы могли бы сделать так, чтобы watch принимал обратный вызов и обновлял там "someOtherProperty". Как это
$(function () {
var p = new Person("Alice");
p.watch(function(){
$("#output").text("Output: "+ p.someOtherProperty);
});
p.name = "Bob";
console.log("Output");
});
Не имеет смысла иметь Object.observe
вести себя синхронно. Пришлось бы заблокировать поток и ждать, пока что-то изменится.
Вы должны передать обратный вызов вашему watch
функция, которая будет выполняться всякий раз, когда что-то меняется:
this.watch = function (callback) {
var self = this;
Object.observe(this, function (changes) {
changes.forEach(function (change) {
if (change.name === 'name') {
self.someOtherProperty = change.newValue;
console.log("Property Changed");
callback();
}
});
});
}
$(function () {
var p = new Person("Alice");
p.watch(function () {
// !!!!! OF COURCE YOU SHOULD NEVER DO IT LIKE THIS IN ANGULAR !!!! //
$("#output").text("Output: " + p.someOtherProperty);
console.log("Output");
});
p.name = "Bob";
});
Кстати, если вы используете Angular (что по вашему коду и скрипте совсем не очевидно), вам не нужно заботиться о выполнении какого-либо кода, когда произойдет заметное изменение. Пока вы завернули код в $scope.$apply()
Angular позаботится об обновлении вида и т. Д.
Например:
<div ng-controller="someCtrl">
Output: {{p.someOtherProperty}}
</div>
.controller('someCtrl', function ($scope, Person) {
$scope.p = new Person('Alice');
$scope.p.watch();
$scope.p.name = 'Bob';
});
app.factory('Person', function ($rootScope) {
return function Person(name) {
var self = this;
self.name = name;
self.someOtherProperty = null;
this.watch = function () {
Object.observe(self, function (changes) {
$rootScope.$apply(function () {
changes.forEach(function (change) {
console.log(change);
if (change.name === 'name') {
self.someOtherProperty = self.name;
}
});
});
});
};
};
});
Смотрите также короткую англоязычную демонстрацию.
Еще лучше, посмотрите это более "реальное" демо.
В основном, преимущество использования O.o
вместо грязной проверки Angular в том, что вы экономите на $$watchers
и, таким образом, ваш $digest
циклы быстрее и дешевле.
Angular также будет использовать этот механизм (O.o
) в любом случае, когда выйдет ES6.