一、目的
foreach绑定为数组中的每个元素复制一系列标记,并将标记绑定到对应的数组元素上。这对于绘制list和table非常有用。如果数组是一个observable数组,当添加,删除,或者重排序数组元素时,foreach绑定可以非常高效的更新UI来匹配---通过插入或删除标记,或者重排序DOM元素来实现,而且不会影响到其他DOM元素。这比数组改变后重新生成所有标记要快得多。当然,你可以在其它控制流绑定如if绑定,with绑定中随意嵌套foreach绑定。
二、例子
例子1、在数组中进行迭代这个例子使用foreach绑定生成一个只读的table,每一行是数组中的一项。
<table>
<thead>
<tr><th>First name</th><th>Last name</th></tr>
</thead>
<tbody data-bind="foreach: people">
<tr>
<td data-bind="text: firstName"></td>
<td data-bind="text: lastName"></td>
</tr>
</tbody>
</table>
<script type="text/javascript">
ko.applyBindings({
people: [
{ firstName: 'Bert', lastName: 'Bertington' },
{ firstName: 'Charles', lastName: 'Charlesforth' },
{ firstName: 'Denise', lastName: 'Dentiste' }
]
});
</script>
运行效果:
这个例子说明,如果数组是observable的,UI会与数组的改变保持同步。
<h4>People</h4>
<ul data-bind="foreach: people">
<li>
Name at position <span data-bind="text: $index"> </span>:
<span data-bind="text: name"> </span>
<a href="#" data-bind="click: $parent.removePerson">Remove</a>
</li>
</ul>
<button data-bind="click: addPerson">Add</button>
function AppViewModel() {
var self = this;
self.people = ko.observableArray([
{ name: 'Bert' },
{ name: 'Charles' },
{ name: 'Denise' }
]);
self.addPerson = function() {
self.people.push({ name: "New at " + new Date() });
};
self.removePerson = function() {
self.people.remove(this);
}
}
ko.applyBindings(new AppViewModel());
foreach参数说明:
主要参数:参数是你想要迭代的数组。foreach绑定将为每个数组项生成html标记。当然也可以传递一个JavaScript object作为参数,包含你想要迭代的数据,这个object可能还包含其它属性,例如:afterAdd,includeDestroyed。另外,如果数组是observable的,foreach绑定将根据数组内容的改变,增加或删除相应的html标记。
一些注意事项:
1、使用$data访问每个数组项上面的例子中,在foreach绑定里,可以直接引用数组项的元素,例如firstName和lastName。如果想引用数组项本身,而不是它的某个属性,可以使用$data参数,表示当前数组项。
<ul data-bind="foreach: months">
<li>
The current item is: <b data-bind="text: $data"></b>
</li>
</ul>
<script type="text/javascript">
ko.applyBindings({
months: [ 'Jan', 'Feb', 'Mar', 'etc' ]
});
</script>
可以使用$data作为属性的前缀访问数组项的各个属性。例如:
<td data-bind="text: $data.firstName"></td>
2、使用 $index, $parent等其它环境属性
可以使用$index引用从0开始的某个数组项。$index是observable的,当增加或删除数组项时,index会自动改变。同样的,可以使用$parent引用foreach之外的数据。例如:
<h1 data-bind="text: blogPostTitle"></h1>
<ul data-bind="foreach: likes">
<li>
<b data-bind="text: name"></b> likes the blog post <b data-bind="text: $parent.blogPostTitle"></b>
</li>
</ul>
查看绑定环境一文,了解更多$index等环境属性的知识。
3、用'as'给foreach项取别名。
正如1所述,你可以通过$data引用数组项。在别的一些情形中,可能需要给当前项取一个容易理解的别名。
<ul data-bind="foreach: { data: people, as: 'person' }"></ul>
现在在foreach循环中,可以使用person引用当前数组项。当在嵌套foreach,在内层循环需要引用上层循环数据时,这个做法非常有用。
<ul data-bind="foreach: { data: categories, as: 'category' }">
<li>
<ul data-bind="foreach: { data: items, as: 'item' }">
<li>
<span data-bind="text: category.name"></span>:
<span data-bind="text: item"></span>
</li>
</ul>
</li>
</ul>
<script>
var viewModel = {
categories: ko.observableArray([
{ name: 'Fruit', items: [ 'Apple', 'Orange', 'Banana' ] },
{ name: 'Vegetables', items: [ 'Celery', 'Corn', 'Spinach' ] }
])
};
ko.applyBindings(viewModel);
</script>
4、无容器使用foreach绑定
在一些情形下,你可能没有容器元素来进行foreach绑定,例如:
<ul>
<li class="header">Header item</li>
<!-- The following are generated dynamically from an array -->
<li>Item A</li>
<li>Item B</li>
<li>Item C</li>
</ul>
在这个例子中,没有地方可以供你插入foreach绑定。你可以使用无容器控制流语法,它基于注释标签:
<ul>
<li class="header">Header item</li>
<!-- ko foreach: myItems -->
<li>Item <span data-bind="text: $data"></span></li>
<!-- /ko -->
</ul>
<script type="text/javascript">
ko.applyBindings({
myItems: [ 'A', 'B', 'C' ]
});
</script>
<!-- ko --> 和<!-- /ko -->注释起着开始和结束标记的作用,定义了一个虚拟的元素,html标记放在它里面。Ko能够理解这种虚拟元素语法并进行绑定,就象真的有一个容器一样。
5、 数组改变如何检测和处理
当修改数组内容时(添加,移动或者删除项),foreach绑定使用一个高效的算法计算出哪些发生了改变,然后更新相应的UI:
当添加数组项时,foreach会绘制模板并将它们插入到存在的DOM中去。
当删除数组项时,foreach简单地删除相应的DOM元素。
当重排序数组项时,foreach仅仅移动相应的DOM元素。
注意,重排序检测并不能百分百保证仅仅移动相应的DOM元素。为了保证算法快速完成,重排序对检测少数数组项的简单移动进行了优化。如果算法检测到大量重排序和不相关的插入,删除,为了加快速度,它可能会选择删除后重新添加,而不是仅仅移动DOM元素。
6、 被销毁的数组项默认隐藏
有时需要标记一些数组项是已删除的,但不是真正的删除它们,这被称为非破坏性删除。如果想要了解如何做到的,请参阅observableArray的销毁函数。
默认情况下,foreach绑定会隐藏被标记为销毁的数组项。如果想要显示这些项,使用includeDestroyed参数。例如:
<div data-bind='foreach: { data: myArray, includeDestroyed: true }'>
...
</div>
7、对生成的DOM元素进行后期处理和动画
如果需要对生成的DOM元素再运行一些自定义的逻辑,可一使用一些回调函数,例如:afterRender/afterAdd/beforeRemove/beforeMove/afterMove
注意:这些回调函数仅仅用于触发和list相关的一些动画。如果你的目标是为一个新添加的DOM元素附加其他行为--例如事件处理,或者激活第三方UI控件,那么去实现一个自定义的绑定会相对来说更容易一些,因为你可以在任何地方使用它们而不是仅仅在foreach绑定中。
举一个简单的例子,使用afterAdd实现一个经典的yellow fade效果(这需要JQuery动画插件)。
<ul data-bind="foreach: { data: myItems, afterAdd: yellowFadeIn }">
<li data-bind="text: $data"></li>
</ul>
<button data-bind="click: addItem">Add</button>
<script type="text/javascript">
ko.applyBindings({
myItems: ko.observableArray([ 'A', 'B', 'C' ]),
yellowFadeIn: function(element, index, data) {
$(element).filter("li")
.animate({ backgroundColor: 'yellow' }, 200)
.animate({ backgroundColor: 'white' }, 800);
},
addItem: function() { this.myItems.push('New item'); }
});
</script>
回调函数简介:
被插入的DOM元素数组
绑定的数据项
afterAdd----- 类似于afterRender,但仅当新数组项被添加到数组时才被触发。一个通常用法是在afterAdd里调用类似于jQuery’s $(domNode).fadeIn(),提供一个添加的动画效果。参数:
被添加的DOM节点
被添加的数组项Index
被添加的数组项
beforeRemove--- -在数组项被删除后,对应的DOM节点删除前触发。如果指定了该回调,必须手动删除节点。该回调通常可以调用jQuery’s $(domNode).fadeOut()来为删除提供动画效果,然后再手动删除该节点。参数:
待删除的DOM节点
删除的数组项index
删除的数组项
beforeMove----- 在数组项改变了在数组中的位置后,在相应的节点移动前触发。需要注意的是,该回调会应用到序号发生改变的所有的数组项,所以当你在数组开头插入一个项,那么所有的元素都会触发这个回调。你可以在该回调里,存储受影响的元素的原始屏幕坐标,然后在afterMove回调里提供动画效果。参数:
待删除的DOM节点
被删除的数组项序号
被删除的数组元素
afterMove----- 在数组里的数组项改变的位置,且DOM元素已经更新后触发。需要注意的是,该回调会应用到所有序号发生改变的数组项,所以当你在数组开头插入一个项,那么所有的元素都会触发这个回调。参数:
被删除的DOM节点
被删除的数组项序号
被删除的数组元素