首页 文章

如何使用$ scope . $ watch和$ scope . $在AngularJS中申请?

提问于
浏览
1030

我不明白如何使用 $scope.$watch$scope.$apply . 官方文档没有帮助 .

具体我不明白:

  • 他们是否连接到DOM?

  • 如何更新模型的DOM更改?

  • 它们之间的连接点是什么?

我试过this tutorial,但理解 $watch$apply 是理所当然的 .

$apply$watch 做了什么,以及如何正确使用它们?

7 回答

  • 1680

    还有 $watchGroup$watchCollection . 具体来说,如果你想调用一个函数来更新一个在一个不是dom对象的视图中有多个属性的对象,那么 $watchGroup 真的很有帮助 . canvas,webGL或服务器请求中的其他视图 . 在这里,文档link .

  • 17

    This blog已经涵盖所有创建示例和可理解的解释 .

    AngularJS $scope 函数 $watch(), $digest()$apply() 是AngularJS中的一些核心函数 . 理解 $watch()$digest()$apply() 对于理解AngularJS至关重要 .

    当您从视图中的某个位置创建数据绑定到$ scope对象上的变量时,AngularJS会在内部创建"watch" . Watch 意味着AngularJS监视 $scope object 上变量的变化 . 框架是"watching"变量 . Watch 是使用 $scope.$watch() 函数创建的,我将在本文后面介绍 .

    在应用程序的关键点,AngularJS调用 $scope.$digest() 函数 . 此函数遍历所有监视并检查是否有任何监视变量已更改 . 如果监视变量已更改,则调用相应的侦听器函数 . 监听器函数执行它需要做的任何工作,例如更改HTML文本以反映监视变量的新值 . 因此, $digest() 函数触发数据绑定更新 .

    大多数时候AngularJS会为你调用$ scope . $ watch()和 $scope.$digest() 函数,但在某些情况下你可能需要自己调用它们 . 因此,了解它们的工作方式真的很棒 .

    $scope.$apply() 函数用于执行某些代码,然后在此之后调用 $scope.$digest() ,因此将检查所有监视并调用相应的监听侦听器函数 . 将AngularJS与其他代码集成时, $apply() 函数非常有用 .

    我将在本文的其余部分详细介绍 $watch(), $digest()$apply() 函数 .

    $ watch()

    $scope.watch() 函数创建一个变量的监视 . 注册表时,将两个函数作为参数传递给 $watch() 函数:

    • 一个值函数

    • 一个监听器功能

    这是一个例子:

    $scope.$watch(function() {},
                  function() {}
                 );
    

    第一个函数是值函数,第二个函数是监听器函数 .

    值函数应返回正在监视的值 . 然后,AngularJS可以根据watch函数上次返回的值检查返回的值 . 这样AngularJS可以确定值是否已更改 . 这是一个例子:

    $scope.$watch(function(scope) { return scope.data.myVar },
                  function() {}
                 );
    

    此示例valule函数返回 $scope 变量 scope.data.myVar . 如果此变量的值发生更改,则将返回不同的值,AngularJS将调用侦听器函数 .

    注意value函数如何将范围作为参数(名称中没有$) . 通过此参数,value函数可以访问 $scope 及其变量 . 如果您需要,值函数也可以查看全局变量,但大多数情况下您会观察 $scope 变量 .

    如果值已更改,则侦听器函数应执行其需要执行的操作 . 也许您需要更改另一个变量的内容,或者设置HTML元素或其他内容 . 这是一个例子:

    $scope.$watch(function(scope) { return scope.data.myVar },
                  function(newValue, oldValue) {
                      document.getElementById("").innerHTML =
                          "" + newValue + "";
                  }
                 );
    

    此示例将HTML元素的内部HTML设置为变量的新值,嵌入在b元素中,使值变为粗体 . 当然,您可以使用代码 {{ data.myVar } 完成此操作,但这只是您在侦听器函数中可以执行的操作的示例 .

    $ digest()

    $scope.$digest() 函数遍历 $scope object 中的所有监视及其子$ scope对象(如果有的话) . 当 $digest() 遍历 Watch 时,它会调用每个 Watch 的值功能 . 如果value函数返回的值与上次调用时返回的值不同,则调用该监视的监听器函数 .

    只要AngularJS认为有必要,就会调用 $digest() 函数 . 例如,在执行了按钮单击处理程序之后,或者在 AJAX 调用返回之后(在执行了done()/ fail()回调函数之后) .

    您可能会遇到AngularJS没有为您调用 $digest() 函数的一些极端情况 . 您通常会通过注意数据绑定不更新显示的值来检测到这一点 . 在这种情况下,请调用 $scope.$digest() 和它应该管用 . 或者,您可以使用 $scope.$apply() 代替我将在下一节中解释 .

    $ apply()

    $scope.$apply() 函数将一个函数作为执行的参数,然后在内部调用 $scope.$digest() . 这使您更容易确保检查所有 Watch ,从而刷新所有数据绑定 . 这是一个 $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}}
    
        
    <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 变量绑定到插值指令,该指令将变量值合并到HTML页面中 . 此绑定在 $scope.data.time variable 上内部创建了一个监视 .

    该示例还包含两个按钮 . 第一个按钮附有一个 ng-click 侦听器 . 单击该按钮时,将调用 $scope.updateTime() 函数,然后AngularJS调用 $scope.$digest() 以便更新数据绑定 .

    第二个按钮从控制器函数内部获取一个标准的JavaScript事件监听器 . 单击第二个按钮时,将执行侦听器功能 . 如您所见,两个按钮的侦听器函数几乎相同,但是当调用第二个按钮的侦听器函数时,不会更新数据绑定 . 这是因为在执行第二个按钮的事件侦听器后未调用 $scope.$digest() . 因此,如果单击第二个按钮,时间将在 $scope.data.time 变量中更新,但永远不会显示新时间 .

    为了解决这个问题,我们可以在按钮事件监听器的最后一行添加 $scope.$digest() 调用,如下所示:

    document.getElementById("updateTimeButton")
            .addEventListener('click', function() {
        console.log("update time clicked");
        $scope.data.time = new Date();
        $scope.$digest();
    });
    

    而不是在按钮监听器函数内调用 $digest() ,您也可以使用 $apply() 函数,如下所示:

    document.getElementById("updateTimeButton")
            .addEventListener('click', function() {
        $scope.$apply(function() {
            console.log("update time clicked");
            $scope.data.time = new Date();
        });
    });
    

    注意如何从按钮事件侦听器内部调用 $scope.$apply() 函数,以及如何在作为参数传递给 $apply() 函数的函数内执行 $scope.data.time 变量的更新 . 当 $apply() 函数调用在内部完成AngularJS调用 $digest() 时,所有数据绑定都会更新 .

  • 58

    AngularJS扩展了这个事件循环,创建了一个名为 AngularJS context 的东西 .

    $watch()

    每次在UI中绑定某些内容时,都会在 $watch 列表中插入 $watch .

    User: <input type="text" ng-model="user" />
    Password: <input type="password" ng-model="pass" />
    

    这里我们有 $scope.user ,它绑定到第一个输入,我们有 $scope.pass ,它绑定到第二个输入 . 这样做我们将两个 $watch es添加到 $watch 列表中 .

    当我们的模板被加载,AKA处于链接阶段时,编译器将查找每个指令并创建所需的所有 $watch .

    AngularJS提供 $watch$watchcollection$watch(true) . 下面是一个简洁的图表,解释了从watchers in depth中取得的所有三个 .

    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上下文管理的事件时,将触发 $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不会创建任何 Watch .

    我们按下按钮 .

    • 浏览器接收将进入AngularJS上下文的事件

    • $digest 循环将运行并将询问每个$ watch是否有变化 .

    • 由于正在监视$ scope.name中的更改的 $watch 报告更改,它将强制另一个 $digest 循环 .

    • 新循环报告没有 .

    • 浏览器获取控制权,它将更新反映$ scope.name新值的DOM

    • 这里重要的是进入AngularJS上下文的每个事件都将运行 $digest 循环 . 这意味着每次我们在输入中写入一个字母时,循环将在此页面中的每个 $watch 处运行检查 .

    $ apply()

    如果在触发事件时调用 $apply ,它将通过角度上下文,但如果您不调用它,它将在其外部运行 . 就是这么简单 . $apply 将在内部调用 $digest() 循环,它将遍历所有监视以确保使用新更新的值更新DOM .

    $apply() 方法将触发整个 $scope 链上的观察者,而 $digest() 方法将仅触发当前 $scope 及其 children 上的观察者 . 如果没有高级 $scope 对象需要了解本地更改,则可以使用 $digest() .

  • 9

    刚读完以上所有内容,无聊而困倦(抱歉,但这是真的) . 非常技术性,深入,细致和干燥 . 我为什么要写作?因为AngularJS是庞大的,许多相互关联的概念可以让任何人变得疯狂 . 我经常问自己,我是不是很聪明才能理解它们?没有!这是因为很少有人可以用 for-dummie language 解释所有术语中的技术!好的,让我试试:

    1) They are all event-driven things. (我听到了笑声,但请继续阅读)

    如果你不知道什么是事件驱动的话那么你想在页面上放一个按钮,使用“点击”连接它,等待用户点击它以触发你在里面种植的动作功能 . 或者想一想SQL Server / Oracle的“触发器” .

    2) $watch is "on-click".

    有什么特别的,它需要2个函数作为参数,第一个给出事件的值,第二个考虑值...

    3) $digest is the boss who checks around tirelessly ,bla-bla-bla但是一个好老板 .

    4) $apply gives you the way when you want to do it manually ,就像防故障一样(如果点击不启动,则强制它运行 . )

    Now, let's make it visual. Picture this to make it even more easy to grab the idea:

    In a restaurant,

    • WAITERS应该接受客户的订单,这是
    $watch(
      function(){return orders;},
      function(){Kitchen make it;}
    );
    
    • 经理跑来跑去确保所有服务员都醒着,以应对客户的任何变化 . 这是 $digest()

    • OWNER有能力根据要求驱动每个人,这是 $apply()

  • 153

    在AngularJS中,我们更新模型,我们的视图/模板“自动”更新DOM(通过内置或自定义指令) .

    $ apply和$ watch都是Scope方法,与DOM无关 .

    Concepts页面(第"Runtime"节)对$ digest循环,$ apply,$ evalAsync队列和$ watch列表有很好的解释 . 这是文本附带的图片:

    $digest loop

    无论什么代码都可以访问范围 - 通常是控制器和指令(它们的链接功能和/或它们的控制器) - 可以设置“watchExpression " that AngularJS will evaluate against that scope. This evaluation happens whenever AngularJS enters its $digest loop (in particular, the " $监视列表”循环) . 您可以观看单个范围属性,您可以定义一个函数来一起观看两个属性,您可以观察数组的长度等 .

    当事情发生在“AngularJS内部” - 例如,你输入一个启用了AngularJS双向数据绑定的文本框(即使用ng-model),$ http回调触发等等 - $ apply已被调用,所以我们在上图中的“AngularJS”矩形内 . 将评估所有watchExpressions(可能不止一次 - 直到没有检测到进一步的更改) .

    当事情发生在“AngularJS之外” - 例如,你在一个指令中使用了bind()然后该事件触发,导致你的回调被调用,或者一些jQuery注册的回调触发 - 我们仍然在“Native”矩形中 . 如果回调代码修改了任何$ watch正在观看的内容,请调用$ apply进入AngularJS矩形,导致$ digest循环运行,因此AngularJS会注意到更改并发挥其魔力 .

  • 38

    您需要了解AngularJS如何工作才能理解它 .

    摘要周期和$ scope

    首先,AngularJS定义了一个所谓的 digest cycle 的概念 . 这个循环可以被认为是一个循环,在此循环期间,AngularJS检查所有 $scope 对所有变量 watched 是否有任何变化 . 因此,如果您在控制器中定义了 $scope.myVar 且此变量为 marked for being watched ,则您隐式告诉AngularJS在循环的每次迭代中监视 myVar 上的更改 .

    一个自然的后续问题是:是否所有人都被关注了 $scope ?幸运的是,没有 . 如果您要监视 $scope 中每个对象的更改,那么很快就会需要一段时间来评估摘要循环,您很快就会遇到性能问题 . 这就是为什么AngularJS团队给我们两种方式来声明一些 $scope 变量被观察(见下文) .

    $ watch有助于监听$ scope更改

    有两种方法可以将 $scope 变量声明为被监视 .

    • 通过表达式 <span>{{myVar}}</span> 在模板中使用它

    • 通过 $watch 服务手动添加

    广告1)这是最常见的情况,我以前看过它,但你不知道这在后台创造了一个 Watch . 是的,它有!使用AngularJS指令(例如 ng-repeat )也可以创建隐式监视 .

    广告2)这就是你如何创建自己的 watches . $watch 服务可帮助您在 $scope 附加的某个值发生更改时运行某些代码 . 它很少使用,但有时很有帮助 . 例如,如果要在每次'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 function as of an integration mechanism . 你看,每次你直接更改一些 watched variable attached to the $scope 对象时,AngularJS都会知道发生了变化 . 这是因为AngularJS已经知道要监控这些变化 . 因此,如果它发生在框架管理的代码中,摘要周期将继续 .

    但是,有时您希望 change some value outside of the AngularJS world 并看到更改正常传播 . 考虑一下 - 你有一个 $scope.myVar 值,它将在jQuery的 $.ajax() 处理程序中修改 . 这将在未来的某个时刻发生 . AngularJS可以被指示等待jQuery .

    为了解决这个问题,已经引入了 $apply . 它可以让您明确地开始消化循环 . 但是,您应该只使用它来将一些数据迁移到AngularJS(与其他框架集成),但是从不将此方法与常规AngularJS代码结合使用,因为AngularJS会抛出错误 .

    所有这些都与DOM有关吗?

    好吧,你应该再次按照教程,既然你知道这一切 . 只要没有任何变化,摘要周期将通过评估附加到所有 $scope 的每个观察者来确保UI和JavaScript代码保持同步 . 如果摘要循环中没有更多的更改,则认为它已完成 .

    您可以在Controller中显式地将对象附加到 $scope 对象,也可以直接在视图中以 {{expression}} 形式声明它们 .

    我希望这有助于澄清有关这一切的一些基本知识 .

    进一步阅读:

  • 15

    我发现了非常深入的视频,涵盖了 $watch$apply$digest 和摘要周期:

    以下是在这些视频中使用的几张幻灯片来解释这些概念(以防万一,如果上述链接被删除/不工作) .

    在上图中,"$scope.c"未被监视,因为它未在任何数据绑定中使用(在标记中) . 另外两个( $scope.a$scope.b )将被观看 .

    从上图:根据各自的浏览器事件,AngularJS捕获事件,执行摘要周期(通过所有 Watch 进行更改),执行监视功能并更新DOM . 如果不是浏览器事件,则可以使用 $apply$digest 手动触发摘要周期 .

    有关 $apply$digest 的更多信息:

相关问题