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

Обновлен jsfiddle

Не имеет смысла иметь 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.

Другие вопросы по тегам