在knockout.js绑定表达式中,我可以使用$data
,$parent
和$root
伪变量。当我使用在JavaScript中声明的ko.computed
可观察对象时,我如何才能获得这些伪变量的等量?
我有一个父视图模型与子集合,和父视图模型有一个selectedChild
可观察对象。考虑到这一点,我可以使用数据绑定表达式将CSS类添加到当前选择的子元素中:
<ul data-bind="foreach: children">
<li data-bind="text: name,
css: {selected: $data === $root.selectedChild()},
click: $root.selectChild"></li>
</ul>
<script>
vm = {
selectedChild: ko.observable(),
children: [{name: 'Bob'}, {name: 'Ned'}],
selectChild: function(child) { vm.selectedChild(child); }
};
ko.applyBindings(vm);
</script>
但是我的视图模型将变得更复杂,我希望"我被选中了吗?"能够做更多的事情,而不仅仅是为单个元素添加单个CSS类。我真的想在子视图模型上创建一个isSelected
计算属性,这样我就可以添加其他依赖于它的计算属性。
我试着写JavaScript,指的是$data
和$root
,在偶然的情况下,knockout可能会定义这些变量,并以某种方式让它们在范围内,当它调用我的computed
求值器函数:
{
name: 'Bob',
isSelected: ko.computed(function(){ return $data === $root.selectedChild(); })
}
但是没有这样的运气:在我的求值器function
中,$data
和$root
都是undefined
。
我也试过在我的评估器中使用ko.contextFor
,因为它确实可以访问$data
和$root
。不幸的是,在我的求值函数中,contextFor
也总是返回undefined
。(无论如何,我对这个策略并没有抱太大的希望——如果我必须像这样在它背后进行操作,那么我并不清楚knockout能够很好地跟踪依赖关系。)
我总是可以在每个子视图模型上手动设置一个属性,它指向父视图模型。但我知道淘汰赛有能力为我做到这一点,我想至少在我自己写之前探索一下我是否可以使用它的机制。
看起来应该可以将上面的绑定表达式转换为计算的可观察对象——毕竟,knockout已经做到了这一点:
另一个巧妙的技巧是声明性绑定简单地实现为计算的可观察对象。
但是,当我编写自己的计算可观察对象时,我如何处理$data
和$root
伪变量?
伪变量仅在数据绑定上下文中可用。理想情况下,视图模型本身不应该知道或依赖于显示它的视图。
因此,当在视图模型中添加计算的可观察对象时,您不知道它将如何绑定(例如将是$root)。视图模型或视图模型的一部分甚至可以在不同的层次上单独绑定到页面的多个区域,因此伪变量将根据您开始使用的元素而不同。
这取决于你想要完成什么,但是如果你想让你的孩子有一个isSelected
计算的可观察对象,表明这个项目是否与父视图模型上选择的项目相同,那么你需要找到一种方法让父视图对孩子可用。
一个选择是将父类传递给子类的构造函数。你甚至不需要将指向父对象的指针作为子对象的属性添加,只需要在计算的可观察对象中直接使用它即可。
例如:
var Item = function(name, parent) {
this.name = ko.observable(name);
this.isSelected = ko.computed(function() {
return this === parent.selectedItem();
}, this);
};
var ViewModel = function() {
this.selectedItem = ko.observable();
this.items = ko.observableArray([
new Item("one", this),
new Item("two", this),
new Item("three", this)
]);
};
示例如下:http://jsfiddle.net/rniemeyer/BuH7N/
如果你只关心所选状态,那么你可以调整它,将selectedItem
可观察对象的引用传递给子构造函数,如http://jsfiddle.net/rniemeyer/R5MtC/
如果您的父视图模型存储在全局变量中,那么您可以考虑不将其传递给子视图模型,而是直接使用它,如:http://jsfiddle.net/rniemeyer/3drUL/。但是我更喜欢把引用传递给子对象。
根据我的经验,如果Item
在应用程序期间存活,那么@RP Niemeyer的答案中的方法是可以的。但如果不是,它可能导致内存泄漏,因为Item
的计算可观察对象建立了与ViewModel
相反的依赖关系。同样,如果你从来没有摆脱任何Item
对象,这是可以的。但是,如果你试图摆脱Item
,它们将不会被垃圾收集,因为knockout仍然有反向依赖引用。
你可以确保dispose()的计算,也许在Item
的cleanup()方法中,当项目消失时被调用,但你必须记住,无论何时删除Item
s。
相反,为什么不让Item
不那么聪明,让ViewModel
告诉它什么时候被选中?只要让Item
的isSelected()
成为一个常规的旧的可观察对象,然后在ViewModel
中订阅selectedItem
并在订阅中更新。
或者,使用@RP Niemeyer的pub/sub解决方案。(公平地说,这个解决方案是在他回答这个问题之后产生的。)但是,您仍然需要清理,因为它也会创建反向依赖项。但至少耦合少了。
请参阅我最近关于同一主题的问题的答案。