如何使用$ 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团队给了我们两种声明一些$scopevariables的方法(看下面)。

$ watch有助于监听$ scope的变化

有两种方法可以将$scopevariables声明为被监视。

  1. 通过expression式<span>{{myVar}}</span>在模板中使用它
  2. 通过$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列表有相当好的解释。 这是伴随文本的图片:

$ digest循环

无论代码如何访问范围 – 通常控制器和指令(它们的链接函数和/或其控制器)都可以设置一个“ 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函数返回$scopevariablesscope.data.myVar 。 如果这个variables的值改变,将返回一个不同的值,AngularJS将调用监听器函数。

请注意值函数如何将范围作为参数(名称中不含$)。 通过这个参数,值函数可以访问$scope和它的variables。 值函数也可以看全局variables,如果你需要的话,但是大多数情况下你会看$scopevariables。

如果值已经改变,监听器函数应该做任何事情。 也许你需要改变另一个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.timevariables绑定到一个插值指令,该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.timevariables中的时间会更新,但是新的时间不会显示。

为了解决这个问题,我们可以添加一个$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.timevariables的更新如何在作为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(); }); } 

http://jsfiddle.net/2Lyn0Lkb/

$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。

  1. 浏览器收到一个将进入AngularJS上下文的事件
  2. $digest循环将运行,并会要求每个$ watch进行更改。
  3. 由于正在监视$ scope.name中的变化的$ watch报告了一个变化,它将强制另一个$digest循环。
  4. 新的循环没有报告。
  5. 浏览器获取控制权,它将更新反映$ scope.name的新值的DOM
  6. 这里最重要的事情是,进入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()