听风是风

学或不学,知识都在那里,只增不减。

导航

统计

深入了解angularjs中的digestapply方法,从区别聊到使用优化

 壹 ❀ 引

如果有人问,在angularjs中修改模型数据为何视图会同步更新呢,我想大多数人一定会回答脏检查(Dirty Checking)相关概念。没错,在angularjs中作用域(scope)作为链接控制器(controller)与视图(view)之间的桥梁,除了绑定数据监听事件外,一旦有数据发生改变,scope还兼顾了脏检测更新视图的职责,这是我们宏观的理解。

这就引发了一系列的问题,以点击事件为例,为什么在angularjs中用原生click事件达不到更新视图的效果?ng-click与原生click有何区别?ng-click触发后angularjs又是怎么让视图更新的呢?digestapply这两个眼熟的方法究竟有何作用,两者有什么区别?如果你对于这些问题感兴趣,不妨静下心来读一读本文,那么本文开始。

 贰 ❀ angularjs的数据绑定

现在有个需求,当我们点击按钮时需要更新视图中的文本信息,当然不通过angularjs我们使用原生js做法也能轻易实现,像这样:

<div>我的名字是:<span class="name"></span></div>
<button class="btn">click me</button>
let btn = document.querySelector('.btn');
let name = document.querySelector('.name');
btn.onclick = function () {
    name.innerHTML = '听风是风';
};

但这样做就有两个问题,第一我们不得不操作DOM,第二不便于复用,如果我们希望点击后将name字段更新到DOM不同层级的各种地方,此时获取DOM就尤为复杂了。

而angularjs便提供了一种有效的解决方法---数据绑定,它将我们需要更新的name字段抽离成了一份数据,在使用时你不用关心这份数据与DOM结构的内在联系,你要考虑的仅仅是在何处放置这份数据而已。

同样还是上面的需求,我们使用angularjs就可以这么做:

<body ng-controller="myCtrl as vm">
    <div>我的名字是:<span>{{vm.name}}</span></div>
    <button class="btn" ng-click="vm.sayName()">click me</button>
</body>
angular.module('myApp', [])
    .controller('myCtrl', function () {
        let vm = this;
        vm.name = '';
        vm.sayName = function () {
            vm.name = '听风是风';
        };
    });

我们仔细对比这两种实现,js是click事件作为媒介找到对应的DOM并操作DOM,而angularjs通过click事件操作的却只是数据,前者操作DOM后者操作数据。突然想起了事件驱动与数据驱动的概念,也有那么点意思了。

这就比较神奇了,当我们点击按钮,name的值发生了改变,同时视图上也同步进行了刷新,angularjs是如何感知变化,又是怎么通过到视图的呢?这就得介绍$digest循环了。

 叁 ❀ 神器的$digest

angularjs的事件循环又称为$digest循环,循环过程中包含了数据的脏检测,准确来说angularjs的脏检测功能是由scope上的$digest()方法实现,这里我们先理解$digest与脏检测的关系。

angularjs中的$digest循环主要包含了watchevalAsync列表两个部分,evalAsyncwatch列表大家是不是有点想法了呢?

没错,这里的watchwatch监听的数组,在scope中以$$watchers字段表示。watchangularjs便watch监听了我们需要交互的每份数据,只要发生改变,底层将通知视图进行更新。

说到这大家就纳闷了,我编程中明明没加$watch,哪来的呢?其实在angularjs使用中,无论是表达式{{}}还是ng-bind,凡是与视图上与数据交互的地方angualrjs都会帮你去注册watch监听,我们来看个简单的例子:

<body ng-app="myApp">
    <div ng-controller="myCtrl as vm">
        纯路人,{{vm.name}}非常{{vm.describe}}
        <div ng-bind="vm.age"></div>
    </div>
</body>
angular.module('myApp', [])
    .controller('myCtrl', function () {
        var vm = this;
        vm.name = "听风是风";
        vm.describe = '帅';
        vm.age = 26;
    });

在这个例子中,我们定义了三个属性,并在视图上与之绑定,查看当前控制器scope属性下的$$watchers属性(这个需要谷歌插件才能查看,插件名 ng-inspect for AngularJS),可以看到类型为Array,数组中包含的三个监听器分别对应我们前面定义的数据。一旦数据发生变化,$watch回调负责更新视图。

一个新的问题就是,angularjs的$watch又是如何感知哪些值发生了变化呢,这就像ng-click能执行代码是依赖了点击行为,毕竟总不能用定时器一直监听吧。

关于这一点就又回到了我们前面提到的$digest循环上了,angularjs在每次调用scope.digest()方法都会发起$digest循环,在循环中angularjs会触发$$watchers中的每一个watchwatch要做的就是新旧值对比,以及发生变化后的相应操作了。

OK,到这里我们明白了调用socpe.digest()会触发digestwatch进行数据对比,也就是我们说的脏检查,以及在数据变更后对视图进行更新。

那么applydigest又有什么联系呢?我们接着说。

 肆 ❀ applydigest

在angularjs开发中,大家一定有过这样的经历,如果一段断码明明修改了数据但视图没变化,用scope.apply方法包裹代码就能解决该问题。比如我们在前文中click与ng-click的例子,这是为什么?

不卖关子,click之所以无法触发视图更新,这是因为click绑定函数中的函数作用域已经脱离了angularjs的上下文,angularjs的$digest循环无法感知脱离angularjs作用域的数据变化(你变了我不知道)。

使用applyangularjsapply也触发了digestscope.applyrootscope.$digest,所以只要使用了applyangularjswarchers。

相比之下像angularjs中内置的事件比如ng-click都内置了applydigest循环,如果我们依旧使用applyangularjsapply,一个简单的例子:

<button class="btn" ng-click="vm.sayName()">click me</button>
复制代码
angular.module('myApp', [])
    .controller('myCtrl', function ($scope) {
        let vm = this;
        vm.name = '';
        vm.sayName = function () {
            $scope.$apply(function () {
                vm.name = '听风是风';
            });
        };
    });
复制代码

OK,打到这里我们知道了applyrootscope.digestdigest循环,对所有作用域中的数据进行脏检查。

 伍 ❀ 何时使用$apply

在angularjs controller中的任何地方都属于angularjs的上下文,在这个上下文中直接修改变量都不需要applyangularjsapply来通知angularjs进行额外的脏检查。举几个例子:

1.普通事件绑定的函数内修改数据需要使用$apply,文章开头已有举例。

2.普通定时器回调中修改数据需要使用apply使angularjstimeout:

//angularjs定时器
$timeout(() => vm.name = '听风是风', 1000);
//普通定时器
setTimeout(() => {
    $scope.$apply(() => vm.name = '时间跳跃');
}, 2000);

在上文中我们已经说了,如果调用了applyrootscope.$digest,这样性能其实是不太好的,特别是存在多个scope的情况下,我们往往更喜欢只检测当前作用域的数据变化。

更加优化的做法是在当前作用域调用$digest,像这样:

setTimeout(() => {
    $scope.$apply(() => vm.name = '时间跳跃');
}, 1000);

setTimeout(function () {
    vm.name = '听风是风';
    $scope.$digest();
}, 2000);

 陆 ❀ 总

那么到这里,我们详细介绍了digestapply方法的区别,在介绍digestdigest是由applyngclickclickapply后,我们简单提及了使用applyangualrjs使digest可代替。那么到这里,本文结束。

 参考

理解Angular中的apply()digest()

angularjs权威指南

posted on   听风是风  阅读(1425)  评论(2编辑  收藏  举报

编辑推荐:
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
点击右上角即可分享
微信分享提示