如何使用$ scope。$ watch和$ scope。$在AngularJS中应用?
我不明白如何使用$scope.$watch
和$scope.$apply
。 官方文档没有帮助。
我不明白的是:
- 他们连接到DOM?
- 我如何更新模型的DOM更改?
- 他们之间的连接点是什么?
我试过这个教程 ,但需要理解$watch
和$apply
是理所当然的。
$apply
和$watch
做什么的,我该如何恰当地使用它们?
您需要了解AngularJS如何工作才能理解它。
摘要周期和$范围
首先,AngularJS定义了一个所谓摘要循环的概念。 这个循环可以被认为是一个循环,在这个循环期间,AngularJS会检查所有$scope
监视的variables是否有变化。 所以如果你的控制器中定义了$scope.myVar
并且这个variables被标记为被监视 ,那么你隐式地告诉AngularJS在循环的每次迭代中监视myVar
的变化。
一个自然的后续问题将是:是否所有附加到$scope
被监视? 幸运的是,没有。 如果您要监视$scope
每个对象的更改,那么快速摘要循环需要很长时间才能评估,并且很快就会遇到性能问题。 这就是为什么AngularJS团队给了我们两种声明一些$scope
variables的方法(看下面)。
$ watch有助于监听$ scope的变化
有两种方法可以将$scope
variables声明为被监视。
- 通过expression式
<span>{{myVar}}</span>
在模板中使用它 - 通过
$watch
服务手动添加它
广告1)这是最常见的情况,我相信你以前看过,但你不知道这是在后台创build了一个手表。 是的,它已经! 使用AngularJS指令(如ng-repeat
)也可以创build隐式手表。
广告2)这是你如何创build自己的手表 。 $watch
服务可以帮助您在附加到$scope
值发生更改时运行一些代码。 它很less使用,但有时是有帮助的。 例如,如果你想在每次'myVar'改变的时候运行一些代码,你可以这样做:
function MyController($scope) { $scope.myVar = 1; $scope.$watch('myVar', function() { alert('hey, myVar has changed!'); }); $scope.buttonClicked = function() { $scope.myVar = 2; // This will trigger $watch expression to kick in }; }
$ apply允许将更改与摘要循环集成
您可以像集成机制那样考虑$apply
函数 。 你会发现,每当你直接改变连接到$scope
对象的观察variables时 ,AngularJS就会知道这个变化已经发生了。 这是因为AngularJS已经知道监视这些变化。 所以如果发生在由框架pipe理的代码中,摘要循环将继续。
但是,有时候你想要改变AngularJS世界以外的某些值,并看到这些变化正常传播。 考虑一下 – 你有一个$scope.myVar
值,它将在jQuery的$.ajax()
处理程序中被修改。 这将在未来某个时候发生。 AngularJS不能等待这个事情发生,因为它没有被指示等待jQuery。
为了解决这个问题,已经引入了$apply
。 它可以让你明确地开始消化循环。 但是,你应该只使用它来将一些数据迁移到AngularJS(与其他框架集成),但从来没有使用这个方法结合常规的AngularJS代码,因为AngularJS会抛出一个错误。
这与DOM有什么关系?
那么,你应该再次遵循教程,现在你知道这一切。 摘要循环将确保UI和JavaScript代码保持同步,只要没有任何变化,通过评估附加到所有$scope
的每个观察者。 如果在摘要循环中没有更多的变化,那么认为它已经完成。
您可以将对象附加到$scope
对象,或者直接在视图中以{{expression}}
forms声明它们。
我希望这有助于澄清一些关于这一切的基本知识。
进一步阅读:
- 做你自己的AngularJS,第1部分:范围和文摘
在AngularJS中,我们更新模型,我们的视图/模板会自动更新DOM(通过内置或自定义指令)。
$ apply和$ watch,都是Scope方法,都与DOM没有关系。
Concepts页面(“Runtime”部分)对$ digest循环,$ apply,$ evalAsync队列和$ watch列表有相当好的解释。 这是伴随文本的图片:
无论代码如何访问范围 – 通常控制器和指令(它们的链接函数和/或其控制器)都可以设置一个“ watchExpression ”,以便AngularJS根据这个范围进行评估。 只要AngularJS进入$ digest循环(特别是“$ watch list”循环),就会进行评估。 您可以观察个别范围属性,您可以定义一个函数来一起观察两个属性,您可以观察数组的长度等。
当事情发生在“AngularJS内部”时 – 例如,你input一个启用了AngularJS双向数据绑定的文本框(例如,使用ng-model),$ httpcallback激发等 – $ apply已经被调用,所以我们上图中的“AngularJS”矩形内。 所有watchExpressions将被评估(可能不止一次 – 直到没有进一步的变化被检测到)。
当事情发生在“AngularJS之外” – 例如,你在一个指令中使用了bind(),然后这个事件被触发,导致你的callback被调用,或者一些jQuery注册的callback触发 – 我们仍然在“Native”矩形中。 如果callback代码修改任何$ watch正在监视的内容,则调用$ apply来进入AngularJS矩形,导致$ digest循环运行,因此AngularJS会注意到这个变化并发挥它的魔力。
这个博客已经涵盖了所有的例子和可以理解的解释。
AngularJS $scope
函数$watch(), $digest()
和$apply()
是AngularJS中的一些中心函数。 理解$watch()
, $digest()
和$apply()
对于理解AngularJS是必不可less的。
当你从视图的某个地方创build一个数据绑定到$ scope对象上的一个variables时,AngularJS在内部创build一个“watch”。 手表意味着AngularJS监视$scope object
上variables的变化。 该框架正在“观察”variables。 手表是使用$scope.$watch()
函数创build的,我将在本文稍后介绍。
在您的应用程序的关键点AngularJS调用$scope.$digest()
函数。 这个函数遍历所有的手表,并检查是否有任何观察的variables已经改变。 如果观察variables已经改变,则调用相应的监听器函数。 监听器函数可以完成它需要做的任何工作,比如改变一个HTML文本来反映监视variables的新值。 因此, $digest()
函数是触发数据绑定更新的东西。
大部分时间AngularJS都会调用$ scope。$ watch()和$scope.$digest()
函数,但在某些情况下,您可能需要自己调用它们。 因此知道他们如何工作真的很好。
$scope.$apply()
函数用来执行一些代码,然后调用$scope.$digest()
,这样所有的手表都会被检查,并调用相应的监听器函数。 在将AngularJS与其他代码集成时, $apply()
函数非常有用。
我将在本文的其余部分详细介绍$watch(), $digest()
和$apply()
函数。
$表()
$scope.watch()
函数创build一些variables的监视。 当您注册手表时,您将两个函数作为parameter passing给$watch()
函数:
- 一个值函数
- 一个监听器函数
这里是一个例子:
$scope.$watch(function() {}, function() {} );
第一个函数是值函数,第二个函数是侦听器函数。
值函数应该返回正在观察的值。 然后AngularJS可以检查返回的值与上一次返回的watch函数的值。 这样AngularJS可以确定值是否已经改变。 这里是一个例子:
$scope.$watch(function(scope) { return scope.data.myVar }, function() {} );
这个示例的valuate函数返回$scope
variablesscope.data.myVar
。 如果这个variables的值改变,将返回一个不同的值,AngularJS将调用监听器函数。
请注意值函数如何将范围作为参数(名称中不含$)。 通过这个参数,值函数可以访问$scope
和它的variables。 值函数也可以看全局variables,如果你需要的话,但是大多数情况下你会看$scope
variables。
如果值已经改变,监听器函数应该做任何事情。 也许你需要改变另一个variables的内容,或者设置一个HTML元素的内容。 这里是一个例子:
$scope.$watch(function(scope) { return scope.data.myVar }, function(newValue, oldValue) { document.getElementById("").innerHTML = "" + newValue + ""; } );
此示例将HTML元素的内部HTML设置为variables的新值,embedded在使元素为粗体的b元素中。 当然,你可以使用代码{{ data.myVar }
来完成这个工作,但这只是你在监听器函数内部可以做的一个例子。
$摘要()
$scope.$digest()
函数迭代$scope object
中的所有监视,以及它的子$ scope对象(如果有的话)。 当$digest()
遍历手表时,它将调用每个手表的值函数。 如果value函数返回的值不同于上一次调用该函数返回的值,则会调用该表的侦听器函数。
每当AngularJS认为有必要的时候调用$digest()
函数。 例如,在执行button点击处理程序之后,或者在AJAX
调用返回后(执行done()/ fail()callback函数之后)。
你可能会遇到一些AngularJS没有为你调用$digest()
函数的情况。 您通常会通过注意到数据绑定不会更新显示的值来检测到这一点。 在这种情况下,调用$scope.$digest()
,它应该工作。 或者,也可以使用$scope.$apply()
来代替,我将在下一节中解释。
$适用()
$scope.$apply()
函数将一个函数作为参数执行,并在$scope.$digest()
被内部调用。 这使您更容易确保所有的手表都被检查,从而刷新所有的数据绑定。 这是一个$apply()
例子:
$scope.$apply(function() { $scope.data.myVar = "Another value"; });
传递给$apply()
函数作为参数的函数将改变$scope.data.myVar
的值。 当函数退出时,AngularJS将调用$scope.$digest()
函数,以便检查所有监视的监视值的变化。
例
为了说明$watch()
, $digest(
)和$apply()
工作的,请看下面的例子:
<div ng-controller="myController"> {{data.time}} <br/> <button ng-click="updateTime()">update time - ng-click</button> <button id="updateTimeButton" >update time</button> </div> <script> var module = angular.module("myapp", []); var myController1 = module.controller("myController", function($scope) { $scope.data = { time : new Date() }; $scope.updateTime = function() { $scope.data.time = new Date(); } document.getElementById("updateTimeButton") .addEventListener('click', function() { console.log("update time clicked"); $scope.data.time = new Date(); }); }); </script>
他的示例将$scope.data.time
variables绑定到一个插值指令,该variables将variables值合并到HTML页面中。 这个绑定在$scope.data.time variable
内部创build一个手表。
该示例还包含两个button。 第一个button有一个ng-click
监听器。 当单击该button时,将调用$scope.updateTime()
函数,之后AngularJS调用$scope.$digest()
以便更新数据绑定。
第二个button从控制器函数内部获得一个标准的JavaScript事件监听器。 当点击第二个button时,执行侦听器function。 正如你所看到的,两个button的侦听器函数几乎相同,但是当第二个button的侦听器函数被调用时,数据绑定不会被更新。 这是因为在执行第二个button的事件侦听器之后, $scope.$digest()
不会被调用。 因此,如果您单击第二个button, $scope.data.time
variables中的时间会更新,但是新的时间不会显示。
为了解决这个问题,我们可以添加一个$scope.$digest()
调用到button事件监听器的最后一行,如下所示:
document.getElementById("updateTimeButton") .addEventListener('click', function() { console.log("update time clicked"); $scope.data.time = new Date(); $scope.$digest(); });
而不是调用button侦听器函数中的$digest()
,也可以像这样使用$apply()
函数:
document.getElementById("updateTimeButton") .addEventListener('click', function() { $scope.$apply(function() { console.log("update time clicked"); $scope.data.time = new Date(); }); });
请注意, $scope.$apply()
函数是如何从button事件侦听器中调用的,以及$scope.data.time
variables的更新如何在作为parameter passing给$apply()
函数的函数中执行。 当$apply()
函数调用完成后,AngularJS在内部调用$digest()
,所有的数据绑定都被更新。
AngularJS扩展了这个事件循环 ,创build了一个叫AngularJS context
。
$表()
每次在UI中绑定某个东西时,都会在$watch
列表中插入$watch
。
User: <input type="text" ng-model="user" /> Password: <input type="password" ng-model="pass" />
这里我们有$scope.user
,它绑定到第一个input,我们有$scope.pass
,它绑定到第二个。 这样做,我们添加两个$watch
到$watch
列表 。
当我们的模板被加载时,AKA在链接阶段,编译器会查找每个指令并创build所有需要的$watch
。
AngularJS提供$watch
, $watchcollection
和$watch(true)
。 下面是一个整齐的图解释所有三个从观察者深入 。
angular.module('MY_APP', []).controller('MyCtrl', MyCtrl) function MyCtrl($scope,$timeout) { $scope.users = [{"name": "vinoth"},{"name":"yusuf"},{"name":"rajini"}]; $scope.$watch("users", function() { console.log("**** reference checkers $watch ****") }); $scope.$watchCollection("users", function() { console.log("**** Collection checkers $watchCollection ****") }); $scope.$watch("users", function() { console.log("**** equality checkers with $watch(true) ****") }, true); $timeout(function(){ console.log("Triggers All ") $scope.users = []; $scope.$digest(); console.log("Triggers $watchCollection and $watch(true)") $scope.users.push({ name: 'Thalaivar'}); $scope.$digest(); console.log("Triggers $watch(true)") $scope.users[0].name = 'Superstar'; $scope.$digest(); }); }
$digest
循环
当浏览器收到一个可以由AngularJS上下文pipe理的事件时, $digest
循环将被触发。 这个循环由两个较小的循环组成。 一个处理$evalAsync
队列,另一个处理$watch list
。 $digest
将遍历我们拥有的$watch
列表
app.controller('MainCtrl', function() { $scope.name = "vinoth"; $scope.changeFoo = function() { $scope.name = "Thalaivar"; } }); {{ name }} <button ng-click="changeFoo()">Change the name</button>
这里我们只有一个$watch
因为ng-click不会创build任何手表。
我们按下button。
- 浏览器收到一个将进入AngularJS上下文的事件
-
$digest
循环将运行,并会要求每个$ watch进行更改。 - 由于正在监视$ scope.name中的变化的$ watch报告了一个变化,它将强制另一个
$digest
循环。 - 新的循环没有报告。
- 浏览器获取控制权,它将更新反映$ scope.name的新值的DOM
- 这里最重要的事情是,进入AngularJS上下文的EVERY事件将运行一个
$digest
循环。 这意味着每次我们在input中写一个字母,循环都会检查这个页面中的每个$watch
。
$适用()
如果在事件被触发时调用$apply
,它将通过angular度上下文,但是如果不调用它,它将在其外面运行。 就这么简单。 $apply
会在内部调用$digest()
循环,它会遍历所有的手表,以确保DOM更新了最新的值。
$apply()
方法将触发整个$scope
链上的观察者,而$digest()
方法只会触发当前$scope
及其children
上的观察者。 当没有任何一个较高级的$scope
对象需要知道本地更改时,可以使用$digest()
。
还有$watchGroup
和$watchCollection
。 特别是, $watchGroup
真的很有用,如果你想调用一个函数来更新一个视图中有多个属性的对象,而不是dom对象,例如canvas,webGL或服务器请求中的其他视图。 这里是文档链接 。
我发现了包含$watch
, $apply
, $digest
和digest循环的非常深入的video:
-
AngularJS – 理解观察者,$ watch,$ watchGroup,$ watchCollection,ng-change
-
AngularJS – 理解摘要循环(摘要阶段或摘要过程或摘要循环)
-
AngularJS教程 – 了解$ apply和$ digest(深入)
以下是这些video中使用的一些幻灯片来解释这些概念(以防万一以上链接被删除/不工作)。
在上面的图片中,“$ scope.c”没有被监视,因为它没有被用在任何数据绑定中(在标记中)。 另外两个( $scope.a
和$scope.b
)将被监视。
从上图中可以看出:AngularJS基于各自的浏览器事件捕获事件,执行摘要循环(遍历所有的变化),执行监视function并更新DOM。 如果不是浏览器事件,摘要周期可以使用$apply
或$digest
手动触发。
更多关于$apply
和$digest
:
刚刚读完所有以上,无聊和困(遗憾,但是是真的)。 非常技术性,深入,细致,干燥。 我为什么要写作? 因为AngularJS是庞大的,所以很多相互连接的概念可以让任何人都疯狂。 我经常问自己,我不够聪明,能理解他们吗? 没有! 这是因为很less有人可以用所有术语来解释这种技术。 好的,让我试试看:
1)他们都是事件驱动的东西。 (我听到笑声,但读到)
如果你不知道事件驱动是什么然后认为你在页面上放置一个button,使用“on-click”来挂接它,等待用户点击它来触发你在function。 或者想想SQL Server / Oracle的“触发器”。
2)$ watch是“点击”。
有什么特别的地方是以2个函数为参数,第一个是事件的值,第二个是考虑到这个值。
3)消化是老板,不知疲倦地检查 ,不过是一个好老板。
4)$ apply可以为你提供手动操作的方式 ,比如防止失败(如果点击没有启动,你强制它运行)。
现在,让我们把它视觉化。 想象一下,让它更容易抓住这个想法:
在一家餐馆,
– 等待者应该接受客户的订单,这是
$watch( function(){return orders;}, function(){Kitchen make it;} );
– 经理人四处奔走,以确保所有的服务员都清醒,对任何客户变化的迹象作出响应。 这是$digest()
– 所有者有最终的权力,驱动每个人的要求,这是$apply()