为了实现这一块功能,我们首先来定义一个测试文件并断言你可以使用watch来注册一个监视器,并且当有人调用了digest的时候监视器的监听函数会被调用。
在scope_spec.js文件中添加一个嵌套的describe块。并创建一个beforeEach函数来初始化这个scope,以便我们可以在进行每个测试时重复它:
test/scope_spec.js
------ describe("Scope", function() { it("can be constructed and used as an object", function() { var scope = new Scope(); scope.aProperty = 1; expect(scope.aProperty).toBe(1); }); describe("digest", function() { var scope; beforeEach(function() { scope = new Scope(); }); it("calls the listener function of a watch on first $digest", function() { var watchFn = function() { return 'wat'; }; var listenerFn = jasmine.createSpy(); scope.$watch(watchFn, listenerFn); scope.$digest(); expect(listenerFn).toHaveBeenCalled(); }); }); });
在上面的这个测试中我们调用了watch来在这个scope上注册一个监视器。我们现在对于监视函数本身并没有什么兴趣,因此我们随便提供了一个函数来返回一个常数值。作为监听函数,我们提供了一个JasmineSpy。接着我们调用了digest并检查这个监听器是否真正被调用。
首先,这个Scope需要有一些地方去存储所有被注册的监视器。我们现在就在Scope构造函数中添加一个数组存储它们:
src/scope.js
----- function Scope(){ this.$$watchers = []; }
上面代码中的$$前缀在AngularJS框架中被认为是私有变量,它们不应该在应用的外部被调用。
现在我们可以来定义watch函数了。它接收两个函数作为参数,并且将它们储存在$watchers数组中。我们想要每一个Scope对象都拥有这个函数,因此我们将它添加到Scope的原型中:
src/scope.js ----- Scope.prototype.$watch = function(watchFn, listenerFn) { var watcher = { watchFn: watchFn, listenerFn: listenerFn }; this.$$watchers.unshift(watcher); };
最后我们应该有一个digest函数。现在,我们来定义一个digest函数的简化版本,它仅仅只是会迭代所有的注册监视器并调用它们的监听函数:
digest能够持续的迭代所有监视函数,直到被监视的值停止变化。多做几次digest是我们能够获得运用于监视器并依赖于其他监视器的变化。
首先,我们新建名为$$digestOnce,并且调整它以便它能够在所有监视器上运行一遍,然后返回一个布尔值来说明有没有任何变化:
src/scope.js
---- Scope.prototype.$$digestOnce = function(){ var length = this.$$watchers.length; var watcher, newValue, oldValue, dirty; while(length--){ watcher = this.$$watchers[length]; newValue = watcher.watchFn(this); oldValue= watcher.last; if(newValue !== oldValue){ watcher.last == newValue; watcher.listenerFn(newValue, oldValue, this); dirty = true; } } return dirty; };
接着,我们重定义digest以便它能够运行“外循环”,在变化发生时调用$digestOnce:
src/scope.js
----- Scope.prototype.$digest = function(){ var dirty; do { dirty = this.$$digestOnce(); } while (dirty); };