angularjs双向绑定后,发生了什么事情?是什么可以让view层和controller层进行绑定的?

angularjs双向绑定后,发生了什么事情?是什么可以让view层和controller层进行绑定的?

1.背景介绍

1.1什么是双向绑定

       双向数据绑定是 AngularJS 的核心机制之一。当 view 中有任何数据变化时,会更新到 model ,当 model 中数据有变化时,view 也会同步更新

  1.2双向数据绑定的原理?

Angular 在 scope 模型上设置了一个 监听队列,用来监听数据变化并更新 view 。 每次绑定一个东西到 view 上时 AngularJS 就会往 watch队列里插入一条 watch队列里插入一条watch,用来检测它监视的

model 里是否有变化的东西。

 当你写下表达式如{{ val }}时,AngularJS在幕后会为你在scope模型上设置一个watcher(表达式将被 Angular 编译成一个监视函数),它用来在数据发生变化的时候更新view,

这里的watcher和你会在AngularJS中设置的watcher是一样的:

将数据附加到 Scope 上,数据自身不会对性能产生影响,如果没有监视器来监视这个属性,那个这个属性在不在 Scope 上是无关重要的;Angular 并不会遍历 Scope 上的属性,它将遍历所有的观察器。

•每个监视函数是在每次 $digest 过程中被调用的。因此,我们要注意观察器的数量以及每个监视函数或者监视表达式的性能。

2.知识剖析

angularjs双向绑定后,发生了什么事情? 什么让view层和Controller层进行绑定的?

Angular双向绑定通过watch,digest和apply实现的。

watch序列

watch监控model中是否有变化,会记录last值,也就是改变后的值,每一个model都会增加一个watch到watch队列中。

传递给$watch()的第二个参数称为监听器函数,当aModel的值发生变化时,它就被调用。我们很容易理解,当aModel的值发生改变,这个监听器就会被调用来更新HTML中的表达式。但是,还有一个很重要的问题!Angular是怎么判断什么时候调用这个监听器函数的呢?换句话说,AngularJS是如何知道aModel值是何时发生改变的,从而它可以调用相应的监听器函数呢?它是否定期运行一个函数来检查

scope 模型的值是否已经改变了?好,这就是 $digest 循环的步骤。

<p>在 $digest 周期中,watcher 会被触发。当一个 watcher 被触发时,AngularJS将评估 scope 模型,如果它发生了变化,则调用相应的监听器函数。那么,我们的下一个问题是,这个

$digest 循环是何时开始的。

$digest循环是在什么时候以各种方式开始的?

当浏览器接收到可以被 angular context 处理的事件时,digest循环就会触发,,遍历所有的watch,最后更新 dom。假设你通过ng-click指令在处理程序函数中更改了一个scope模型。在这种情况下,AngularJS会通过调用

$digest() 自动触发一个 $digest 循环。当 $digest 循环开始的时候,它就会触发每一个 watcher。这些 watcher 会检查scope模型的当前值是否与上次计算得到的值不同。如果不同,则执行相应的监听器函数。因此,如果在视图中有任何表达式,它们将被更新。除了ng-click之外,还有其他一些内置的指令/服务可以让你更改模型(例如ng-model、$timeout

等),并自动触发一个 $digest 循环。

举个例子

click 时会产生一次更新的操作(至少触发两次 $digest 循环)

 •按下按钮 •浏览器接收到一个事件,进入到 angular context

•digest循环开始执行,查询每个watch 是否变化

 •由于监视 scope.val的 scope.val的watch 报告了变化,因此强制再执行一次 $digest 循环

 •新的 $digest 循环未检测到变化 •浏览器拿回控制器,更新 $scope. val.新值对应的 dom

到目前为止还不错!但是,这里有一个小问题。在上面的例子中,Angular并不直接调用 $digest()。相反,它调用 $scope.$apply(),而 $scope.$apply() 又会调用 $rootScope.$digest()。因此,一个

$digest 循环开始于 $rootScope,随后会访问所有的child scopes,并在此过程中调用child scopes中的watchers。 现在,假设你将一个ng-click指令附加到一个按钮,并将一个函数名传递给它。当单击按钮时,AngularJS将函数调用包装在

$scope.$apply() 中。因此,你的函数照常执行,更改模型(如果有的话),并开始一个 $digest 循环来确保你的更改反映在视图中。 注意:$scope.$apply() 自动调用 $rootScope.$digest()。$apply()

函数有两种形式。第一种接受一个函数作为参数,执行这个函数,并触发一个 $digest 循环。第二种则不需要任何参数,在调用时只触发一个 $digest 循环

$apply

进行数据变化检查的实际上是$digest函数,但是我们往往不是直接使用$digest,而是使用$apply,$apply接收表达式或者函数作为参数后调用$digest来更新绑定部门以及监控器。实际上,Angular几乎在所有提供的代码中添加了$apply,如ng-click,初始controller,$http的回调操作,在这,你并不需要亲自调用

$apply,而且重复的调用会引起错误。因此,当你运行了一个新阶段,并且这部分并不属于Angular库的情况下才需要使用$apply。这有一段关于setTimeout的代码,在经过了2000毫秒的延迟之后,代码进入执行了一个新的阶段,但是Angular并不知道数据有更新,因此更新并不会被显示。我们应该用angular

JS提供的timeout方法,这样它就会被自动用 timeout方法,这样它就会被自动用apply方法包起来了

•什么时候用$apply()

那我们到底什么时候需要去调用apply()方法呢?情况非常少,实际上几乎我们所有的代码都包在scope.apply()里面,像ng−click,controller的初始化,http的回调函数等。在这些情况下,我们不需要自己调用,实际上我们也不能自己调用,否则在apply()方法里面再调用

apply()方法会抛出错误。如果我们需要在一个新的执行序列中运行代码时才真正需要用到它,而且当且仅当这个新的执行序列不是被angular JS的库的方法创建的,这个时候我们需要将代码用 scope.apply()包起来。</p>

$digest 循环会运行多少次?

$digest 循环的上限是 10 次(超过 10次后抛出一个异常,防止无限循环)。 $digest 循环不会只运行一次。在当前的一次循环结束后,它会再执行一次循环用来检查是否有 models 发生了变化。

这就是脏检查(Dirty Checking),它用来处理在 listener 函数被执行时可能引起的 model 变化。因此 digest循环会持续运行直到model不再发生变化,或者 digest循环会持续运行直到model不再发生变化,或者digest

循环的次数达到了 10 次(超过 10 次后抛出一个异常,防止无限循环)。 当 $digest 循环结束时,DOM 相应地变化。

脏检查如何被触发

angular 会在可能触发 UI 变更的时候进行脏检查:这句话并不准确。实际上脏检查是digest执行的,另一个更常用的用于触发脏检查的函数apply——其实就是 $digest 的一个简单封装(还做了一些抓异常的工作)。

通常写代码时我们无需主动调用 apply或 apply或digest 是因为 angular 在外部对我们的回调函数做了包装。例如常用的 ng-click,这是一个指令(Directive),$digest过程的逻辑就是检查watcher列表中的每一项,看当前值与上次的值是否相同,如果不同则调用listener回调函数。这就是dirty-checking的核心逻辑,

apply()和 apply()和digest() 的区别?

apply是 apply是scope(或者是 direcvie 里的 link 函数中的 scope)的一个函数,调用它会强制一次 digest循环(除非当前正在执行循环,这种情况下会抛出一个异常,这是我们不需要在那里执行

digest循环(除非当前正在执行循环,这种情况下会抛出一个异常,这是我们不需要在那里执行apply 的标志)。

apply()和 apply()和digest() 有两个区别。

 •1) 最直接的差异是, $apply 可以带参数,( //无参 $scope.$apply() //有参 $scope.$apply(function(){ ... }))它可以接受一个函数,然后在应用数据之后,调用这个函数。所以,一般在集成非

Angular 框架(比如jQuery)的代码时,可以把代码写在这个里面调用。

 •2) 当调用 digest的时候,只触发当前作用域和它的子作用域上的监控,但是当调用 digest的时候,只触发当前作用域和它的子作用域上的监控,但是当调用apply 的时候,会触发作用域树上的所有监控。

 

3.常见问题

   在 AngularJS 中使用 $watch注意事项?

4.解决方案

 如果要监听的是一个对象,那还需要第三个参数,表示比较的是对象的值而不是引用,如果不加第三个参数 true ,在 data.name 变化时,不会触发相应操作,因为引用的是同一引用。

// vm.data.name = 'htf';

// $scope.$watch('data', function (newValue, oldValue) {

// if (newValue === oldValue) {

// return;

// }

// vm.updated++;

// }, true);

这里添加true

 5.编码实战

  html

<div class="box">

 

<input type="text" name="" id="" ng-model="vm.text"> {{vm.text}}

<br>

<br>

<button ng-click="vm.val=vm.val+1">点击+1</button>

{{vm.val}}

<br>

<br>

 

<button ng-click="vm.getMessage()">延时信息</button>

: {{vm.message}}

<br>

<br>

<button ng-click="vm.getMessage2()">延时信息2</button>

: {{vm.message2}}

<br><br>

 

<div ng-show="false">

 

<span id="span1" ng-bind="content"></span>

 

</div>

 

<span id="span2" ng-bind="content"></span>

 

<button ng-click="">TEST</button>

</div>

js

angular.module("myApp")

.controller("articleController", function ($timeout, ArticleManagementService, date, ask, $state, $scope, $http, typeOption, statusOption, onOff, $rootScope) {

var vm = this;

vm.text = "111"

vm.text2 = "text2"

// $scope.$apply(function(){//这里主动调用依次apply的话会报错,提示digest已在进程中,这就是上面讲的通常写代码时我们无需主动调用 apply或 apply或digest 是因为 angular 在外部对我们的回调函数做了包装

// console.log(vm.text)

// })

 

watch

 

$scope.$watch('vm.text', function (newValue, oldValue) {

console.log("新值" + newValue, "旧值" + oldValue)

console.log(vm.text)

});

vm.getMessage = function () {

setTimeout(function () {

$scope.$apply(function () {//这里是原生的定时器函数,不是angular库的,点击后2秒后view不会实时更新,添加apply就会更新了

vm.message = '2秒后显示';

console.log('message:' + vm.message);

})

}, 2000);

}

但是一般推荐使用下面这个angular封装的timeout服务

vm.getMessage2 = function () {

$timeout(function () {

vm.message2 = '2秒后显示';

console.log('message2:' + vm.message2);

}, 2000, true);

}

// vm.data.name = 'htf';

 

 

 

})

6.扩展思考

   脏检查慢吗?

说实话脏检查效率是不高,但是也谈不上有多慢。简单的数字或字符串比较能有多慢呢?十几个表达式的脏检查可以直接忽略不计;上百个也可以接受;成百上千个就有很大问题了。绑定大量表达式时请注意所绑定的表达式效率。建议注意一下几点: •表达式(以及表达式所调用的函数)中少写太过复杂的逻辑

•不要连接太长的 filter(往往 filter 里都会遍历并且生成新数组) •不要访问 DOM 元素。

1、使用单次绑定减少绑定表达式数量 单次绑定(One-time binding 是 Angular 1.3 就引入的一种特殊的表达式,它以 :: 开头,当脏检查发现这种表达式的值不为 undefined 时就认为此表达式已经稳定,并取消对此表达式的监视。这是一种行之有效的减少绑定表达式数量的方法,与

ng-repeat 连用效果更佳,但过度使用也容易引发 bug。

2、善用 ng-if 减少绑定表达式的数量

7.参考文献

   https://www.cnblogs.com/zhoulujun/p/8881414.html

8.更多讨论

1,问:脏检查的范围

答:

angular 会对所有绑定到 UI 上的表达式做脏检查。其实,在 angular 实现内部,所有绑定表达式都被转换为 scope. scope.watch()。每个 watch记录了上一次表达式的值。有ng−bind="a"即有 watch记录了上一次表达式的值。有ng−bind="a"即有scope.watch(

′ a ′ ,callback),而 watch(′a′,callback),而scope.$watch 可不会管被 watch 的表达式是否跟触发脏检查的事件有关。

举个例子

<div ng-show="false">

<span id="span1" ng-bind="content"></span>

</div>

<span id="span2" ng-bind="content"></span>

<button ng-click="">TEST</button>

问:点击 TEST 这个按钮时会触发脏检查吗?触发几次?

首先:ng-click="" 什么都没有做。angular 会因为这个事件回调函数什么都没做就不进行脏检查吗?不会。

 然后:#span1 被隐藏掉了,会检查绑定在它上面的表达式吗?尽管用户看不到,但是 scope. scope.watch('content', callback) 还在。就算你直接把这个 span 元素干掉,只要

watch 表达式还在,要检查的还会检查。

 

2,问:重复的表达式会重复检查吗

答:

会。

最后:别忘了 ng-show="false"。可能是因为 angular 的开发人员认为这种绑定常量的情况并不多见,所以 $watch 并没有识别所监视的表达式是否是常量。常量依旧会重复检查。

 所以:触发三次。一次 false,一次 content,一次 content

3,问:apply()和 apply()和digest() 的区别?

答:apply()和 apply()和digest() 有两个区别。

 •1) 最直接的差异是, $apply 可以带参数,( //无参 $scope.$apply() //有参 $scope.$apply(function(){ ... }))它可以接受一个函数,然后在应用数据之后,调用这个函数。所以,一般在集成非

Angular 框架(比如jQuery)的代码时,可以把代码写在这个里面调用。

 •2) 当调用 digest的时候,只触发当前作用域和它的子作用域上的监控,但是当调用 digest的时候,只触发当前作用域和它的子作用域上的监控,但是当调用apply 的时候,会触发作用域树上的所有监控。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值