对于Angular JS中$apply()的理解

Angular最引人注目的特性就是双向绑定,然而它是怎么作到的,我能够来总结两点:express

  • 将变化的数据从model传向view-->$apply编程

  • 将变化的数据从view传向model-->$watch浏览器


这连个函数都是基于scope的基础上,对scope对象的成员变化情况进行传播的。那么,我不妨是从scope开始。那么什么是scope呢?借用官方文档的一段话:app

 

“scope is an object that refers to the application model. It is an execution context for expressions. Scopes are arranged in hierarchical structure which mimic the DOM structure of the application. Scopes can watch expressions and propagate events.”框架

 

  看完后,类比到其余的编程语言上,感受Scope就像是Data Model的做用域同样,为Expressions的执行提供上下文。编程语言

 

Scope的特性函数

  接下来,看看Scope有哪些特性呢?性能

  • Scope提供$watch方法监视Model的变化。学习

  • Scope提供$apply方法传播Model的变化。spa

  • Scope能够继承,用来隔离不一样的application components和属性访问权限。

  • Scope为Expressions的计算提供上下文。

     对于这四点特性,由于我以前学习过ActionScript、C++、Java,因此第1、3、四点不难理解,惟独第二点感受有点云里雾里。本着打破沙锅问到底的原则,我仍是经过Google搜到了一些东西。

 

源起Javascript

     首先,乍一看,$scope.$apply()彷佛就是一个使得bindings获得更新的普普统统的方法。但稍微多想一点,为何咱们须要它?通常在何时用它呢?用弄明白这两个问题,还得从Javascript提及。在Javascript代码里,都是按照必定顺序来执行的,当轮到一个代码片断执行的时候,浏览器就只会去执行当前的片断,不会作任何其余的事情。因此有时候一些作得不是很好的网页,当点击了某个东西以后会卡住,Javascript的工做方式就是会致使这一现象缘由之一!下面咱们有一段代码来感觉一下:

 

var button = document.getElementById('clickMe');

 

function buttonClicked () {

  alert('the button was clicked');

}

 

button.addEventListener('click', buttonClicked);

function timerComplete () {

  alert('timer complete');

}

setTimeout(timerComplete, 5000);


     当加载Javascript代码时,先先找一个一个id叫“clickMe”的按钮,而后添加一个监听器,而后设置超时。等待5秒,会弹出一个对话框。若是刷新页面并当即点击clickMe按钮,会弹出一个对话框,若是你不点击OK,timerComplete函数永远没有机会执行。那就是由于成员的变化没有被更新

  

如何更新bindings

     好了,扯了一些看似不相关的东西以后,咱们回归正题。angular JS是怎么知道何时数据的变化和页面须要更新的呢?代码须要知道何时数据被修改了,可是如今又没有一种方法直接去通知说某个对象上的数据变了(尽管ECMAScript 5正在尝试解决这一问题,但也仍是处于实验阶段)。而目前比较主流的策略有如下有两种解决方案。一种是须要用特殊的对象,让全部的数据都只能经过调用对象的方法设置,而不是直接经过property指定。这样的话,全部的修改就能够被记录下来了,就知道何时页面须要更新了。这样作的弊端就是咱们必须去继承一个特殊的对象。对于赋值也只能经过object.set('key', 'value')而不是object.key=value的方式。在框架中,像EmberJS和KnockoutJS就是这么干的(虽然我都没接触过——囧)。另外一种就是angular JS采用的方式,在每一次Javascript代码执行序列执行结束后都去检查是否有数据的改变。这看起来彷佛并不高效,甚至严重影响性能。可是angular JS采用了一些比较巧妙的手段解决了这个问题(之后有机会好好研究)。这么作的好处就是,咱们能够随便使用任意对象,对于赋值方式也没有限制,并且对于数据的改变也能觉察到。

     对于angular JS采起的这种解决方案,咱们关心的是何时数据发生了变化,而这也正是$scope.$apply()派上用场的地方。对于检查绑定的数据到底有没有发生变化,其实是由$scope.$digest()完成的,可是咱们几乎历来就没有直接调用过这个方法,而是调用$scope.$apply()方法,是由于在$scope.$apply()方法里面,它会去调用$scope.$digest()方法。$scope.$apply()方法带一个函数或者一个表达式,而后执行它,最后调用$scope.$digest()方法去更新bindings或者watchers。

 

 

 

何时用$apply()

     仍是那个问题,那咱们到底何时须要去调用$apply()方法呢?状况很是少,实际上几乎咱们全部的代码都包在$scope.$apply()里面,像ng-click,controller的初始化,$http的毁掉函数等。在这些状况下,咱们不须要本身调用,实际上咱们也不能本身调用,不然在$apply()方法里面再调用$apply()方法会抛出错误。若是咱们须要在一个新的执行序列中运行代码时才真正须要用到它,并且当且仅当这个新的执行序列不是被angular JS的库的方法建立的,这个时候咱们须要将代码用$scope.$apply()包起来。下面用一个例子解释:

[HTML]

<div ng:app ng-controller="Ctrl">{{message}}</div>

[Javascript]

functionCtrl($scope) {

  $scope.message ="Waiting 2000ms for update";    

  setTimeout(function () {

    $scope.message ="Timeout called!";

     // AngularJS unaware of update to $scope

  }, 2000); 

}

     上面的代码执行后页面上会显示:Waiting 2000ms for update。显然数据的更新没有被angular JS觉察到。

     接下来,咱们将Javascript的代码稍做修改,用$scope.$apply()包起来。

[Javascript]

functionCtrl($scope) {

  $scope.message ="Waiting 2000ms for update"; 

  setTimeout(function () {

    $scope.$apply(function () {

       $scope.message ="Timeout called!";

      });

  }, 2000); 

}

     此次与以前不一样的是,页面上先会显示:Waiting 2000ms for update,等待2秒后内容会被更改成:Timeout called! 。显然数据的更新被angular JS觉察到了。

     NOTE:咱们不该该这样作,而是用angular JS提供的$timeout方法,这样它就会被自动用$apply方法包起来了。

 

 

 

科学是把双刃剑

     最后,咱们再来瞅一眼$scope.$apply()和$scope.$apply(function)方法吧!虽然angular JS为咱们作了不少事情,可是咱们也所以丢失了一些机会。从下面的伪代码一看便知:

 

function$apply(expr) {

  try {

    return$eval(expr);

  } catch(e) {

    $exceptionHandler(e);

  } finally {

    $root.$digest();

  }

}


  它会捕获全部的异常而且不会再抛出来,最后都会调用$digest()方法。

相关文章
相关标签/搜索