【问题标题】:Object.Observe Synchronous CallbackObject.Observe 同步回调
【发布时间】:2014-07-31 09:38:30
【问题描述】:

我一直在 Chrome v36 中试验 Object.observe。我最初的意图是在我的模型中将其用于业务逻辑,但异步行为似乎使这成为不可能。我将其归结为以下示例:

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 链接,带有 jQ​​uery。

我的问题是在“属性更改”之前调用了“输出”。有什么方法可以使 Object.Observe 同步,还是我应该这样做更好? (顺便说一句,我正在使用 AngularJS。)

这里的问题是没有将文本添加到 DOM 或输出到控制台。我的业务逻辑要求我在name 更改时立即更新someOtherPropety,我更愿意将此逻辑封装在我的模型中。

显然,这只是一个示例,但我有依赖于立即执行的业务规则。

【问题讨论】:

  • 你真正想要什么,我想你知道这是预期的行为。

标签: javascript angularjs object.observe


【解决方案1】:

Object.observe, "sadly" (阅读下一篇),不执行同步任务。一旦“微任务”结束,它就会发送更改通知。

这是解释here

多年的网络平台经验告诉我们,同步方法是您尝试的第一件事,因为它最容易理解。问题是它创建了一个从根本上危险的处理模型。如果您正在编写代码并说更新对象的属性,那么您真的不希望更新该对象的属性可能会邀请一些任意代码去做它想做的任何事情。在函数中间运行时让假设无效是不理想的。

因此,您的“微任务”结束console.log("Output") 已被调用,然后Object.observe 通知对象的更改。

拥有同步事件的经典方法是使用 getter 和 setter:

Person.prototype.setName = function(name) {
    this.name = name;
    console.log("Name Changed");
};

p.setName("Bob");

当然,这会迫使您为要查看的每个属性创建 getter 和 setter,而忘记删除和添加新属性的事件。

【讨论】:

  • @ExpertSystem 我认为他的意思是,有时,如果O.o 是同步的会很有用。但这是经验问题,因为同步解决方案会导致许多其他潜在问题。这也是我们得到MutationObserver 而不是MutationEvents 的原因。
  • @TroelsLarsen, MaxArt:我以为你在谈论O.o 本身的(同步/异步)性质。好像您在谈论调用 O.o 侦听器回调的方式。所以,我们现在在同一个页面上,我都同意:)
  • @ExpertSystem:好的,我想我现在理解你了。我猜 LasseChristiansen 是对的。我需要以某种方式使用 getter 和 setter。
  • @TroelsLarsen:这取决于你想用你的代码做什么。如果您只想更新视图,则不需要 getter/setter(感谢 Angular)。看,我回答中的最后一个示例/演示。
  • @ExpertSystem:回想起来,我不应该以视图为例。这仅适用于模型内的业务逻辑。因此,如果 PropertyA 发生变化,PropertyB 就会获得一个新值。
【解决方案2】:

正如你所说,观察不是同步的。但是您可以让手表进行回调并在那里更新“someOtherProperty”。像这样

$(function () {
    var p = new Person("Alice");
    p.watch(function(){
        $("#output").text("Output: "+ p.someOtherProperty);
    });
    p.name = "Bob";
    console.log("Output");
});

Updated jsfiddle

【讨论】:

    【解决方案3】:

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

    另请参阅此short Angular demo


    更好的是,请参阅此 more "real-worldy" demo
    基本上,使用 O.o 而不是 Angular 的脏检查的优点是您可以节省 $$watchers,因此您的 $digest 周期更快且成本更低。
    无论如何,当 ES6 出现时,Angular 也会使用这种机制(O.o)。

    【讨论】:

    • 我抽象出角度,因为我(目前)在我的模型中不需要 DependencyInjection。同样,我不想对角度范围或类似的东西一无所知。我不介意为 getter 和 setter 编写(生成)代码。我想我需要确定 Angular 是否可以绑定到这些。
    • Angular 可以绑定到任何东西。但是,如果在 Angular 上下文之外发生了某些事情/发生了变化(例如,在异步执行的回调中),您需要通过将代码包装在 $scope.$apply() 中来明确告诉 Angular 发生了什么事情(参见上面的示例)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-21
    • 1970-01-01
    • 1970-01-01
    • 2015-05-03
    相关资源
    最近更新 更多