深入了解angularjs中的$digest与$apply方法,从区别聊到使用优化
壹 ❀ 引
如果有人问,在angularjs中修改模型数据为何视图会同步更新呢,我想大多数人一定会回答脏检查(Dirty Checking)相关概念。没错,在angularjs中作用域(scope)作为链接控制器(controller)与视图(view)之间的桥梁,除了绑定数据监听事件外,一旦有数据发生改变,scope还兼顾了脏检测更新视图的职责,这是我们宏观的理解。
这就引发了一系列的问题,以点击事件为例,为什么在angularjs中用原生click事件达不到更新视图的效果?ng-click与原生click有何区别?ng-click触发后angularjs又是怎么让视图更新的呢?$digest和$apply这两个眼熟的方法究竟有何作用,两者有什么区别?如果你对于这些问题感兴趣,不妨静下心来读一读本文,那么本文开始。
贰 ❀ 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循环主要包含了$watch列表与$evalAsync列表两个部分,$evalAsync列表先不分析,看到$watch列表大家是不是有点想法了呢?
没错,这里的$watch列表就是一个包含了多个$watch监听的数组,在scope中以$$watchers字段表示。$watch大家都不会陌生,监听一份数据,如果发生改变则执行对应回调,而angularjs便是利用$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中的每一个$watch(脏检测),有了触发源$watch要做的就是新旧值对比,以及发生变化后的相应操作了。
OK,到这里我们明白了调用$socpe.$digest()会触发$digest循环,在循环中又会触发所有$watch进行数据对比,也就是我们说的脏检查,以及在数据变更后对视图进行更新。
那么$apply与$digest又有什么联系呢?我们接着说。
肆 ❀ $apply与$digest
在angularjs开发中,大家一定有过这样的经历,如果一段断码明明修改了数据但视图没变化,用$scope.$apply方法包裹代码就能解决该问题。比如我们在前文中click与ng-click的例子,这是为什么?
不卖关子,click之所以无法触发视图更新,这是因为click绑定函数中的函数作用域已经脱离了angularjs的上下文,angularjs的$digest循环无法感知脱离angularjs作用域的数据变化(你变了我不知道)。
使用$apply就能让angularjs执行脏检查的本质其实就是$apply也触发了$digest循环,准确来说,执行$scope.$apply后会调用$rootscope.$digest,所以只要使用了$apply方法,angularjs都 会从根作用域开始遍历每个作用域中的每个$warchers。
相比之下像angularjs中内置的事件比如ng-click都内置了$apply用于触发$digest循环,如果我们依旧使用$apply,angularjs反而会报错告诉你已经启动了$apply,一个简单的例子:
<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,打到这里我们知道了$apply方法执行会调用$rootscope.$digest方法,从而启动全新的$digest循环,对所有作用域中的数据进行脏检查。

伍 ❀ 何时使用$apply
在angularjs controller中的任何地方都属于angularjs的上下文,在这个上下文中直接修改变量都不需要$apply,但如果你在普通函数以及非angularjs提供的回调函数中修改变量,此时都需要结合$apply来通知angularjs进行额外的脏检查。举几个例子:
1.普通事件绑定的函数内修改数据需要使用$apply,文章开头已有举例。
2.普通定时器回调中修改数据需要使用$apply,一般推荐使用angularjs封装的定时器,比如$timeout:
//angularjs定时器
$timeout(() => vm.name = '听风是风', 1000);
//普通定时器
setTimeout(() => {
$scope.$apply(() => vm.name = '时间跳跃');
}, 2000);
在上文中我们已经说了,如果调用了$apply 等同于$rootscope.$digest,这样性能其实是不太好的,特别是存在多个scope的情况下,我们往往更喜欢只检测当前作用域的数据变化。
更加优化的做法是在当前作用域调用$digest,像这样:
setTimeout(() => {
$scope.$apply(() => vm.name = '时间跳跃');
}, 1000);
setTimeout(function () {
vm.name = '听风是风';
$scope.$digest();
}, 2000);
陆 ❀ 总
那么到这里,我们详细介绍了$digest与$apply方法的区别,在介绍$digest循环后了解到$digest是由$apply触发,从而也解释了ng-click与普通click的区别。在介绍了$apply后,我们简单提及了使用$apply的场景,我们知道它会让angualrjs从根作用域开始脏检测,代价较大,因此推荐使用$digest可代替。那么到这里,本文结束。
参考
理解Angular中的$apply()以及$digest()
angularjs权威指南
深入了解angularjs中的$digest与$apply方法,从区别聊到使用优化的更多相关文章
- angularjs 中的$digest和$apply区别
$digest和$apply 在Angular中,有$apply和$digest两个函数,我们刚才是通过$digest来让这个数据应用到界面上.但这个时候,也可以不用$digest,而是使用$appl ...
- AngularJS中的digest循环$apply
欢迎大家指导与讨论 : ) 前言 Angular会拓展这个标准的浏览器流程,创建一个Angular上下文.这个Angular上下文指的是运行在Angular事件循环内的特定代码,该Angular事件循 ...
- js中的call和apply方法的区别
一.call和apply的说明 1.call,apply都属于Function.prototype的一个方法,它是JavaScript引擎内在实现的,因为属于Function.prototype,所以 ...
- 转发: JS中的call()和apply()方法和区别 --小白变色记
一.方法定义: apply:调用一个对象的一个方法,用另一个对象替换当前对象.例如:B.apply(A, arguments);即A对象应用B对象的方法. call:调用一个对象的一个方法,用另一个对 ...
- angularjs中provider,factory,service的区别和用法
angularjs中provider,factory,service的区别和用法 都能提供service,但是又有差别 service 第一次被注入时实例化,只实例化一次,整个应用的生命周期中是个单例 ...
- JS中的call()和apply()方法(转)
转自:http://uule.iteye.com/blog/1158829 JS中的call()和apply()方法 博客分类: JS 1.方法定义 call方法: 语法:call([thisOb ...
- 我总结的call()与apply()方法的区别
[call()与apply()的区别]在ECMAScript中每一个函数都是function类型(是javascript的基本引用类型)的实例,具有一定的属性和方法.call()和apply()则是这 ...
- 正则表达式中的exec和match方法的区别
正则表达式中的exec和match方法的区别 字符串的正则方法有:match().replace().search().split() 正则对象的方法有:exec().test() 1.match m ...
- IL角度理解C#中字段,属性与方法的区别
IL角度理解C#中字段,属性与方法的区别 1.字段,属性与方法的区别 字段的本质是变量,直接在类或者结构体中声明.类或者结构体中会有实例字段,静态字段等(静态字段可实现内存共享功能,比如数学上的pi就 ...
随机推荐
- 使用 cAdvisor 主机上的容器
目录 前言 安装测试 安装 docker 安装docker-ce 启动 cAdvisor 容器 访问测试 prometheus 服务端配置 使用 promtool 检查配置文件 重新加载配置文件 前言 ...
- Leetcode_01【两数之和】
文章目录: 题目 脚本一及注释 脚本逻辑 脚本二及注释 脚本逻辑 题目: 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标. ...
- 使用Python将xmind脑图转成excel用例(一)
最近接到一个领导需求,将xmind脑图直接转成可以导入的excel用例,并且转换成gui可执行的exe文件,方便他人使用. 因为对Python比较熟悉,所以就想使用Python来实现这个功能,先理一下 ...
- Spring面试题集锦(精选)
以下来自网络收集,找不到原文出处.此次主要为了面试收集,希望对大家有所帮助~~~~ 1.什么是Spring? Spring是一个开源的Java EE开发框架.Spring框架的核心功能可以应用在任何J ...
- fsockopen与HTTP 1.1/HTTP 1.0
在前面的例子中,HTTP请求信息头有些指定了 HTTP 1.1,有些指定了 HTTP/1.0,有些又没有指定,那么他们之间有什么区别呢? 关于HTTP 1.1与HTTP 1.0的一些基本情况,可以参考 ...
- adb adb monkey命令及介绍
1.adb的组成部分 守护进程,客户端,服务器端` 2.Monkey程序是Google公司提供的一个压力和稳定性测试的工具 3.命令 命令 参数 功能 adb version 查看当前a ...
- RAC环境查询JOB正在运行的信息
需求: 客户环境12.2.0.1,三节点RAC需要,将一个正在运行的Job session kill掉, 但是通过DBA_JOBS_RUNNING发现,无法发现其它实例运行的JOB,因此需要登陆多台实 ...
- 云享专家倪升武:微服务架构盛行的时代,你需要了解点 Spring Boot
[摘要] 微服务架构的本质在于分布式.去中心化. 随着互联网的高速发展,庞大的用户群体和快速的需求变化已经成为了传统架构的痛点. 在这种情况下,如何从系统架构的角度出发,构建出灵活.易扩展的系统来快速 ...
- MVC效验器
步骤一:导入依赖 <!--数据效验--> <dependency> <groupId>org.hibernate</groupId> <artif ...
- 什么是RESTful?RESTfule风格
导读 理解什么是REST之前,先去脑补以下什么是HTTP,参考[Http协议] 什么是REST? REST(英文:Representational State Transfer,简称REST,意思:表 ...