- 一个监控函数,用于指定所关注的那部分数据。
- 一个监听函数,用于在数据变更的时候接受提示。
作为一名Angular用户,一般来说,是监控一个表达式,而不是使用监控函数。监控表达式是一个字符串,比如说“user.firstName”,通常在数据绑定,指令的属性,或者JavaScript代码中指定,它被Angular解析和编译成一个监控函数。在这篇文章的后面部分我们会探讨这是如何做的。在这篇文章中,我们将使用稍微低级的方法直接提供监控功能。
为了实现$watch,我们需要存储注册过的所有监听器。我们在Scope构造函数上添加一个数组:
function Scope() { this.$$watchers = []; }
在Angular框架中,双美元符前缀$$表示这个变量被当作私有的来考虑,不应当在外部代码中调用。
现在我们可以定义$watch方法了。它接受两个函数作参数,把它们存储在$$watchers数组中。我们需要在每个Scope实例上存储这些函数,所以要把它放在Scope的原型上:
Scope.prototype.$watch = function(watchFn, listenerFn) { var watcher = { watchFn: watchFn, listenerFn: listenerFn }; this.$$watchers.push(watcher); };
另外一面就是$digest函数。它执行了所有在作用域上注册过的监听器。我们来实现一个它的简化版,遍历所有监听器,调用它们的监听函数:
Scope.prototype.$digest = function() { _.forEach(this.$$watchers, function(watch) { watch.listenerFn(); }); };
现在我们可以添加监听器,然后运行$digest了,这将会调用监听函数:
http://jsbin.com/oMaQoxa/2/embed?js,console
这些本身没什么大用,我们要的是能检测由监控函数指定的值是否确实变更了,然后调用监听函数。
脏值检测
如同上文所述,监听器的监听函数应当返回我们所关注的那部分数据的变化,通常,这部分数据就存在于作用域中。为了使得访问作用域更便利,在调用监控函数的时候,使用当前作用域作为实参。一个关注作用域上fiestName属性的监听器像这个样子:
function(scope) { return scope.firstName; }
这是监控函数的一般形式:从作用域获取一些值,然后返回。
$digest函数的作用是调用这个监控函数,并且比较它返回的值和上一次返回值的差异。如果不相同,监听器就是脏的,它的监听函数就应当被调用。
想要这么做,$digest需要记住每个监控函数上次返回的值。既然我们现在已经为每个监听器创建过一个对象,只要把上一次的值存在这上面就行了。下面是检测每个监控函数值变更的$digest新实现: