How dependency tracking works 依赖跟踪是如何工作的
初学者可以不关心它,但是高级开发会想要知道我们为什么能保证所有的关于KO的请求都自动的跟踪依赖并正确的更新UI...
事实上很简单也很好理解,跟踪的过程是这样的:
1. 任何时候你声明了一个计算监控属性,KO将立刻调用它的计算函数以获取它的初始值。
2. 当这个计算函数执行的时候,KO会在这个计算函数读取的任何一个监控属性(包括其他的计算监控属性)上设置订阅,订阅的回调会被设置为触发这个计算函数的再次运行,循环整个过程回到第一步(处理掉任何不在需要的旧的订阅)。
3. KO通知所有有关你的计算监控属性的订阅。
因此,KO不只监控你的计算函数首次运行时的依赖,它监控每一次执行。这也意味着,例如这个依赖可以是动态变化的:依赖A能够判断这个计算监控属性是否也依赖B或者C,然后,它只会在A或者当前的选择B或C其中一个变化时才会重新计算,你不需要声明所有的以依赖,它们会在代码运行时决定。
另一个技巧是实现计算监控属性的绑定声明很简单,所以,当一个绑定读取一个监控属性的值时,这个绑定将依赖这个监控属性,也就是说如果这个依赖变化了,就会引起绑定的重新计算。
纯计算监控属性的工作机制稍有不同,更多细节请参见 pure computed observables.
Controlling dependencies using peek 使用 peek 控制依赖
KO的自动依赖跟踪通常情况下就是你想要的,但是有时你可能希望显示的控制你的计算监控属性的更新,特别是当你的计算监控属性需要执行一些特殊行为,例如调用Ajax请求。peek函数可以使你在不触发依赖的情况下访问监控属性或计算监控属性。
在下边的例子中,一个计算监控属性用于使用Ajax重新加载依赖另外两个监控属性的叫做 currentPageData的监控属性,任何时候当 pageIndex 变化的时候,这个计算监控属性都将更新,但它忽略了 selectdItem 的改变,因为它是使用的peek进行访问的。在这个场景中,用户通常只是想要在一个新的集合加载的时候使用 selectedItem的当前值用来跟踪。
ko.computed(function(){ var params = { page: this.pageIndex(), selected: this.selectedItem.peek() }; $.getJSON('/some/Json/Service', params, this.currentPageData); }, this);
注意:如果你仅仅只是想要限制计算监控属性频繁的更新,请参见 rateLimit extender.
Ignoring dependencies within a computed 在计算中忽略依赖
ko.ignoreDependencies 在这样的场景中很有用:当你想要在计算中执行代码但又不想触发计算依赖时。当你在自定义绑定中使用代码访问监控属性,但是又不想触发绑定的依赖监控变化的时候它很有用。
ko.ignoreDependencies( callback, callbackTarget, callbackArgs);
例子:
ko.bindingHandler.myBinding = { update: funciton (element, valueAccessor, allBindingsAccessor, viewModel, bindingComtext) { var options = ko.unwrap(valueAccessor()); var value = ko.unwrap(options.value); var afterUpdateHandler = options.afterUpdate; if (typeof afterUpdateHandler === "function") { ko.ignoreDependencies(afterUpdateHandler, viewModel, [value, color]); } $(element).somPlugin("value", value); } }
注意: Why circular dependencies aren't meaningful 为什么循环依赖没有意义
计算监控属性假定映射一组监控属性输入到一个单一监控属性输出,因此,当你的依赖包含循环的时候它将没有意义。循环不同于递归,它类似于表格上两个单元格互相计算,它将导致无限循环。
那么,在你的依赖中出现了循环的时候ko是怎样处理的呢?它通过执行如下的规则来避免无限循环: ko 将不会再次执行一个计算监控属性的计算函数,如果它已经被执行过。 这很可能会影响到你的代码。如下相关的两个场景:当两个计算监控属性互相依赖的时候(很有可能其中一个或两个都应用了 deferEvaluation 选项), 或者当一个计算监控属性写入另一个它依赖(通过依赖链直接或间接)的监控属性。如果你确实需要像这种模式下使用并且要完全避免循环依赖,你可以像上边的例子中那样使用peek函数。