Angular中的$apply()以及$digest()

本文深入探讨AngularJS中的双向数据绑定机制,解释了digest循环的工作原理及其如何确保视图和模型之间的同步更新。此外,还详细介绍了何时及如何手动调用apply方法,并通过实例演示了不同的实现方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

双向数据绑定(Two-way Data Binding),这个特性大大简化了我们的代码编写方式。数据绑定意味着当View中有任何数据发生了变化,那么这个变化也会自动地反馈到scope的数据上,也即意味着scope模型会自动地更新。类似地,当scope模型发生变化时,view中的数据也会更新到最新的值。

当你写下表达式如{{ aModel }}时,AngularJS在幕后会为你在scope模型上设置一个watcher,它用来在数据发生变化的时候更新view。
这里的watcher和你会在AngularJS中设置的watcher是一样的:

$scope.$watch('aModel', function(newValue, oldValue) {
  //update the DOM with newValue
});

传入到$watch()中的第二个参数是一个回调函数,该函数在aModel的值发生变化的时候会被调用来更新view。

digest循环:它会周期性的运行一个函数来检查scope模型中的数据是否发生了变化.在digest循环中,watchers会被触发。当一个watcher被触发时,AngularJS会检测scope模型,如何它发生了变化那么关联到该watcher的回调函数就会被调用。

在调用了scope.digest()后,$digest循环就开始了。假设你在一个ng-click指令对应的handler函数中更改了scope中的一条数据,
此时AngularJS会自动地通过调用digest()来触发一轮digest循环。当digest循环开始后,它会触发每个watcher。
这些watchers会检查scope中的当前model值是否和上一次计算得到的model值不同。
如果不同,那么对应的回调函数会被执行。调用该函数的结果,就是view中的表达式内容(译注:诸如{{ aModel }})会被更新。

除了ng-click指令,还有一些其它的built-in指令以及服务来让你更改models(比如ng-model,timeout等)和自动触发一次$digest循环。

AngularJS并不直接调用digest(),而是调用scope.apply(),后者会调用rootScope.digest()。因此,一轮digest循环在$rootScope开始,随后会访问到所有的children scope中的watchers。

scope.apply()会自动地调用rootScope.digest()。apply()方法有两种形式。
第一种会接受一个function作为参数,执行该function并且触发一轮digest循环。(推荐)
第二种会不接受任何参数,只是触发一轮digest循环。
需要记住的是你总是应该使用接受一个function作为参数的apply()方法。这是因为当你传入一个function到apply()中的时候,
这个function会被包装到一个try…catch块中,所以一旦有异常发生,该异常会被$exceptionHandler service处理。

什么时候手动调用apply()方法?

实际上,AngularJS对此有着非常明确的要求,就是它只负责对发生于AngularJS上下文环境中的变更会做出自动地响应(即,在apply()方法中发生的对于models的更改)。AngularJS的built-in指令就是这样做的,所以任何的model变更都会被反映到view中。
但是,如果你在AngularJS上下文之外的任何地方修改了model,那么你就需要通过手动调用$apply()来通知AngularJS。这就像告诉AngularJS,你修改了一些models,希望AngularJS帮你触发watchers来做出正确的响应。

比如,如果你使用了JavaScript中的setTimeout()来更新一个scope model,那么AngularJS就没有办法知道你更改了什么。这种情况下,调用apply()就是你的责任了,通过调用它来触发一轮digest循环。类似地,如果你有一个指令用来设置一个DOM事件listener并且在该listener中修改了一些models,那么你也需要通过手动调用apply()来确保变更会被正确的反映到view中。

digest循环会运行多少次?

当一个digest循环运行时,watchers会被执行来检查scope中的models是否发生了变化。如果发生了变化,那么相应的listener函数就会被执行。这涉及到一个重要的问题。如果listener函数本身会修改一个scope model呢?AngularJS会怎么处理这种情况?

答案是digest循环不会只运行一次。在当前的一次循环结束后,它会再执行一次循环用来检查是否有models发生了变化。这就是脏检查(Dirty Checking),它用来处理在listener函数被执行时可能引起的model变化。因此,digest循环会持续运行直到model不再发生变化,或者digest循环的次数达到了10次。因此,尽可能地不要在listener函数中修改model。

digest循环最少也会运行两次,即使在listener函数中并没有改变任何model。正如上面讨论的那样,它会多运行一次来确保models没有变化。
需要记住的最重要的是AngularJS是否能检测到你对于model的修改。如果它不能检测到,那么你就需要手动地调用apply()。

例子如下:

timeout service:
angularjs服务:会帮你调用apply(),让你不需要手动地调用它。
setTimeout()
Javascript方法,需要手动调用apply

<!DOCTYPE html>
<html ng-app="myApp">
<head>
    <meta charset="utf-8">
    <title>My AngularJS App</title>
    <script src="http://sandbox.runjs.cn/uploads/rs/376/pbcx3e1z/angular.min.js"></script>
    <script src="http://sandbox.runjs.cn/uploads/rs/376/pbcx3e1z/angular-route.min.js"></script>
</head>

<body>
<div ng-controller="MessageController1">
    Delayed Message: {{message}};
</div>
<div ng-controller="MessageController2">
    Delayed Message: {{message}};
</div>
<div ng-controller="MessageController3">
    Delayed Message: {{message}};
</div>

<script>
    angular.module('myApp', [])
    /*$timeout service:angularjs服务:会帮你调用$apply(),让你不需要手动地调用它。
     * setTimeout() :Javascript方法,需要手动调用$apply*/
        .controller('MessageController1', function ($scope) {
            $scope.getMessage = function () {
                setTimeout(function () {
                    $scope.message = ' setTimeout===Fetched after two seconds';
                    console.log('message:js1' + $scope.message);
                    $scope.$apply();
                    //this triggers a $digest第二种会不接受任何参数,只是触发一轮$digest循环。
                }, 2000);
            };
            $scope.getMessage();
        })
        .controller('MessageController2', function ($scope) {
            $scope.getMessage = function () {
                setTimeout(function () {
                    $scope.$apply(function () {
                        //第一种会接受一个function作为参数,执行该function并且触发一轮$digest循环。(推荐)
                        //wrapped this within $apply
                        $scope.message = ' setTimeout==Fetched after 2 seconds';
                        console.log('message:js2' + $scope.message);
                    });
                }, 2000);
            };
            $scope.getMessage();
        })
        .controller('MessageController3', function ($scope, $timeout) {
            $timeout(function () {/*内置服务自动调用$apply*/
                $scope.message = ' $timeout===Fetched after two seconds';
                console.log('message:ng' + $scope.message);
            }, 2000);
        });

</script>
</body>
</html>

以上三种写法一样的效果:

Markdown

参考:

理解Angular中的apply()以及digest()

AngularJS中,$digest是一个循环机制,用于检查和更新作用域中的所有绑定。它会遍历作用域树,并检查每个作用域中的所有watcher。当检测到一个watcher的值发生变化时,$digest会触发这些watcher的回调函数,从而更新相应的视图。 $digest循环由AngularJS自动触发,不需要手动调用。当AngularJS检测到可能发生变化的事件(例如用户输入、HTTP请求的响应等)时,它会自动启动$digest循环。在这个过程中,AngularJS会检查每个作用域中的watcher,并更新视图以反映最新的模型状态。 然而,有时候我们需要在AngularJS上下文之外修改模型,并希望通知AngularJS进行更新。这时就可以使用$apply方法。$apply方法会触发$digest循环,强制AngularJS检查和更新所有作用域中的watcher。 一种常见的使用场景是在使用第三方库或原生JS代码时,需要手动调用$apply方法来通知AngularJS进行更新。在这种情况下,我们可以将修改模型的代码包裹在$apply函数中,以确保AngularJS能够正确地检测到变化并更新相应的视图。 总结起来,$digestAngularJS自动触发的循环机制,用于检查和更新作用域中的所有绑定。而$apply是手动调用的方法,用于通知AngularJS在AngularJS上下文之外的代码中发生了模型的变化,需要进行更新。 :https://www.jb51.net/article/120399.htm :https://stackoverflow.com/questions/41285152/when-to-call-scope-apply-in-angularjs
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值