让我们来看看Angular在后台是如何工作的。如何只使用几行代码就得到神奇的数据绑定?最重要的是理解$digest循环是如何工作的,以及如何使用$apply()方法。
在标准的浏览器流程中,当事件被触发时(比如点击一个链接)。浏览器会执行注册给该事件的回调函数。
页面加载、$http请求返回响应、鼠标移动以及按钮被点击等情况都会触发事件。
当事件被触发时,JavaScript就会创建一个事件对象,并执行这个事件对象所在的监听特定事件的所有函数。然后它会运行JavaScript函数内的回调方法,这会回到浏览器中,还可能更新DOM。
注意:同一时间不能运行两个事件。浏览器会等待前一个事件处理程序执行完成,再调用下一个事件处理程序。
在非Angular JavaScript环境中,可以给div的点击事件附加一个回调函数。无论何时,只要发现元素上的点击事件,这个回调函数就会运行;
var div = document.getElementById("clickDiv");
div.addEventListener("click",function(evt){
console.log("evt",evt);
})
1.1 $watch 列表
html>
<html lang="zh_CN" data-ng-app="app">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<script src="vendor/angular/angular.js">script>
head>
<body >
<input type="text" class="form-control" id="orgName" ng-model="orgName">
Hello {{name}}
body> html>
无路何时,只要用户更新这个输入字段,UI中的{{name}}就会改变。发生这一变化是因为我们把UI中的输入字段绑定给了scope.name属性。为了更新这个视图,Angular需要追踪变化。它是通过给$watch列表添加一个监控函数做到这一点的。
$scope对象上的属性只会在其被用于视图时绑定。在上述例子中,我们只给$watch列表添加了一个函数。
记住,对于所有绑定给同一$scope对象的UI元素,只会添加一个$watch到$watch列表中。
这个$watch列表会在$digest循环中通过一个叫做“脏值检查”的程序解析。
angular在我们所写的绝大部分代码中都会触发比较事件。比如:controller 初始化的时候,ng-click事件和所有以ng-开头的事件执行后,$http 回调完成后,都会触发脏检查。
当然,触发脏检查的点是在函数执行完之后,但不表明异步调用也执行完成,所以,如果我们的功能是异步的,那你会发现我们的改变并没有更新到dom上。
来个demo?好,请看:
function Ctrl($scope) { $scope.message = "Waiting 2000ms for update"; setTimeout(function () { $scope.message = "Timeout called!"; // AngularJS unaware of update to $scope }, 2000); }
dom上显示的message是 “Waiting 2000ms for update,永远都不是 “Timeout called!”
这就是 $apply的应用场景,使用$scope.$apply()
手动触发脏检查。其实angular还贴心的提供了一个$timeout
,那 $timeout和setTimeout
在功能上有什么区别吗?可以说没有,唯一的区别就是,使用$timeout
异步完成之后,angular会自动触发$apply()
。
如果已经在一个具有$apply环境中,调用$apply()会抛出异常, 有空你可以试一试。
$apply()接受一个function的参数,function中被绑定的对象会被脏检查,function不能是异步的哦,这个很好理解,如果让你去实现这个apply函数,$apply()不带参数时,会将当前作用域的所有监听对象都脏检查一遍,所以不带参数是有害的, 必然会做很多无用的脏检查,浪费性能。
脏检查只要是由 $digest() 完成,$apply()被调用之后,最终会触发 $digest() ,
$digest 主要代码如下:
if ((watchers = current.$$watchers)) { // process our watches length = watchers.length; while (length--) { try { watch = watchers[length]; // Most common watches are on primitives, in which case we can short // circuit it with === operator, only when === fails do we use .equals if (watch && (value = watch.get(current)) !==(last = watch.last) && !(watch.eq ? equals(value, last) : (typeof value ==number' && typeof last == 'number' && isNaN(value) && isNaN(last)))) { dirty = true; watch.last = watch.eq ? copy(value) : value; watch.fn(value, ((last === initWatchVal) ? value : last), current); if (ttl < 5) { logIdx = 4 - ttl; if (!watchLog[logIdx]) watchLog[logIdx] = []; logMsg = (isFunction(watch.exp)) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp; logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last); watchLog[logIdx].push(logMsg); } } } catch (e) { $exceptionHandler(e); } }
遍历watchers,比较监视的属性是否变化.