2. 控制流

1. foreach绑定

目的

foreach绑定会遍历一个数组,为每个数组项生成重复的元素标记结构并做关联。这在渲染列表或表格的时候特别有用。

假设你的数组是一个监控数组,之后无论你进行添加,移除还是重新排序,对应的UI结构也会发生相应变化 -- 插入或移除标记结构,或者重排已存在的DOM元素。这不会影响其他的DOM元素,这远比数组发生改变后重新渲染生成foreach输出结构快多了。

当然,你可以嵌套任意数量的foreach绑定,或者其他的控制流绑定,比如if绑定和with绑定。

例子1:遍历一个数组

这个例子用foreach生成一个只读表格,每个数组项对应一行。

<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>

例子2:add/remove实例

view:

<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>

viewmodel:

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绑定会为每个实体生成一个对应的标记段。

    另外,可以传入一个javascript对象字面量的如叫data的属性,也就是你想遍历的数组,这个对象字面量可能有其他属性,比如afterAdd或者include Destroyed,在下面看这些扩展项的详细内容并看在例子中如何使用的。

    如果你的数组是一个监控数组,foreach绑定会对数组的内容发生的任何改变都会做出响应,即通过添加或删除相应的DOM标记块。

  • 额外参数

注意1:通过$data引用数组中的每个数据实体

如上面的例子所示,foreach绑定可以遍历数组并引用实体的属性。比如,例子1引用了每个数组实体的firstNamelastName属性。

但是如果你想引用实体本身怎么办(而不是实体的属性)?在这种情况下,你可以使用特殊的上下文属性$data。在foreach标记块里面,它的意思是当前项的意思。例子:

<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作为引用每个属性的前缀,比如,例子1可以改为如下形式:

<td data-bind="text: $data.firstName"></td>

但是你没有必要这样做,因为默认情况下,fristName会在$data上下文下计算。

注意2:使用\(index,\)parent和其他上下文属性

如上面例子2所示, 你可以使用$index来获取当前项的索引值(从0开始),$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$parent的更多信息和其他的上下文属性,请看文档5.2绑定上下文

注意3:使用 as 给 foreach项取别名

如例子1所示,你可以通过$data上下文变量引用每个数组实体。在某些情况下,给当前对象一个更具有可读性的名字很有用,可以使用 as关键字达到目的:

<ul data-bind="foreach: { data: people, as: 'person' }"></ul>

现在在foreach代码块内,绑定都可以使用person来表示people数组中的当前项,这在你存在嵌套foreach绑定,,内层的foreach循环想引用更高一层的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>

提示:传递给 as 的字符串字面量(如, as: 'category', 而不是 as: category),因为您是创建一个名字为传过来的字符串字面量的新变量,而不是读取一个已存在的变量的值。

注意 4: 不使用包含容器使用foreach

在某些情况下,你可能想要重复渲染一部分HTML标签,但是你没有任何元素标签用来绑定foreach绑定。比如,你可能会想实现如下情形:

<ul>
<li class="header">Header item</li>
<!-- 下面内容由数组动态渲染生成 -->
<li>Item A</li>
<li>Item B</li>
<li>Item C</li>
</ul>

在这个例子里,<ul>标签里没有合适的地方放置一个一般的foreach绑定(因为你不想重复渲染header item项),你也不能在<ul>里面再嵌套一个容器标签(因为在<ul>标签里面只允许插入<li>标签)。

为了处理这种情况,你可以使用无容器的控制流语法,这个基于注释标签来实现,例子如下:

<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>

The <!-- ko --> and <!-- /ko --> 注释作为开始/结束标记, 在容器里面定义一个“虚拟元素”。 Knockout 可以理解虚拟元素这种语法,然后就像绑定在一个真正的容器元素上一样。

注意 5: 怎样检测到数组改发生了变化并进行相应处理

当你修改你的模型数组的内容(通过增加、移动、删除里面的实体),foreach绑定会使用一个高效的差异算法找出什么发生了改变,这样就能更新对应的DOM元素节点。这意味着它可以处理同时改变的任意组合。

  • 当你添加数组实体,foreach会把你设置的模板复制一个新的副本然后插入已存在的DOM节点里呈现出来。
  • 当你删除数组实体,foreach删除对应的DOM元素节点。
  • 当你对数组实体进行重排(对象实体没有增删),foreach通常只会移动对应的DOM元素到新的位置

需要注意的是重排并不是十分可靠:为了确保算法能高效执行,它被优化为可以检测少量的数组实体的 简单 移动。如果算法检测到大量的同时排序组合,而且与插入和删除无关,这时候为了执行速度,它会通过 “删除”和“添加”来代替简单的 “移动”,在这种情况下,对应的DOM元素节点会删除,然后重新添加。大多数开发者不会遇到这种极端情况,甚至你遇到了,最终用户体验仍然是一致的。

注意 6: 默认通过隐藏实体来表示销毁实体

有时你想要删除一个数组实体,但是实际上并没有真正的删除。这就是所谓的非破坏性删除。想明白具体是怎么实现的,请看pobservableArray的destroy函数

默认情况下,foreach绑定会跳过(比如,隐藏)任何标记为destroyed的数组实体。如果你想要显示销毁的实体,你可以使用includeDestroyed 选项,如下所示:

<div data-bind='foreach: { data: myArray, includeDestroyed: true }'>
...
</div>

注意 7: 生成DOM元素过程动画化或生成后进行处理

如果你想在生成DOM元素后进行一些自定义逻辑操作,你可以使用afterRender/afterAdd/beforeRemove/beforeMove/afterMove这些回调函数。在下面有这些函数的说明。

注意:这些回调函数仅用于通过列表中的变化触发相关的动画。如果你想在新的DOM节点被添加的时候附加一些其他行为(比如,进行事件处理,调用第三方插件)。而你把你想实现的新行为作为自定义绑定反而会更简单,因为之后你可以在任何地方使用你实现的新行为,独立于foreach绑定。

这是一个应用afterAdd的简单例子,在添加新项的时候使用经典的“黄色淡出”效果。这个需要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>

API详情:

  • afterRender -在foreach第一次初始化的时候关联数组中每个实体和关联数组添加新实体两种情况下,通过模板复制出对应的DOM节点并插入文档后触发,删除节点不会触发,knockout会在回调函数提供如下参数:
  1. 要插入的dom元素的dom节点数组(也就是foreach绑定内的所有dom节点,如果存在换行,换行符会识别为#text节点)
  2. 正要进行绑定的数据项
  • afterAdd - 和afterRender 类似,但是仅仅在新实体添加到数组的时候触发(在foreach第一次遍历数组的初始内容的时候不会触发)。afterAdd通常用于添加效果。如jQuery的$(domNode).fadeIn()。这样添加新项的时候会有一个动画效果。knockout会在回调函数提供如下参数:
  1. 将要插入document的DOM节点
  2. 将要插入数组的节点的索引值
  3. 要添加的数组元素
  • beforeRemove - 在数组项要被移除的时候触发。但在对应的DOM节点移除之前。如果你指定了一个beforeRemove回调函数,你需要手动去删除Dom节点。常见场景比如使用jQuery’的 $(domNode).fadeOut()来给删除节点添加动画— 在这种情况下, Knockout 不能知道DOM节点实际上要多久才会删除(谁知道你的动画会占多少时间),所以应该由您删除DOM节点。knockout会在回调函数提供如下参数:
  1. 您要移除的DOM节点
  2. 要移除的元素在数组中的索引值
  3. 要移除的数组元素
  • beforeMove - 在数组项改变了在数组中的位置之前触发,在DOM节点移动之前。需要注意的是beforeMove会使所有的数组元素的索引发生改变,所以如果你想在数组的头部添加一个新项, 然后回调函数(如果有定义)会被剩余的元素触发,因为所有的元素的索引值都加一,你可以使用beforeMove存储移动之前的数组元素的原始位置,这样你可以利用存储的数据在afterMove生成移动特效.。knockout会在回调函数提供如下参数:
  1. 可能被移动的DOM节点
  2. 要移动的数组实体在数组中的位置
  3. 要移动的数组实体
  • afterMove - 在数组项改变了在数组中的位置之后触发。在foreach更新了对应等等DOM节点之后,需要注意的是afterMove会被所有索引发生改变的数组元素的触发,所以如果你在数组的头部插入一个新项,回调函数(如果有定义)会被剩余的元素触发,因为所有的元素的索引值都加一。。knockout会在回调函数提供如下参数:
  1. 可能被移动的DOM节点
  2. 要移动的数组实体在数组中的位置
  3. 要移动的数组实体

要看应用afterAddbeforeRemove的例子,请跳转到animated transitions查看。

最好通过调试一个例子来查看:

html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<link rel="stylesheet" type="text/css" href="index.css"/>
<script src="../../js/lib/jquery-1.7.2/jquery-1.7.2.js" type="text/javascript" charset="utf-8"></script>
<script src="../../js/lib/knockout/knockout-3.3.0.js" type="text/javascript" charset="utf-8"></script>
<script src="index.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<input type="button" value="add" data-bind="click:add" />
<input type="button" value="delete" data-bind="click:del" />
<input type="button" value="move first to last" data-bind="click:move" />
<ul class="list" data-bind = "foreach:{data:list,afterRender:afterRender,afterAdd:afterAdd,beforeRemove:beforeRemove,beforeMove:beforeMove,afterMove:afterMove}">
<li data-bind = "text:text"></li>
</ul>
<ul data-bind="foreach:loglist" style=" width: 300px;height:600px;overflow:auto;border: 1px solid red;position: absolute;top: 0;right: 0;display: block;">
<li data-bind = "text:text"></li>
</ul>
</body>
</html>

javascript:

function main() {
var vm = {
list: ko.observableArray([{
text: "aaa"
}, {
text: "bbb"
}, {
text: "ccc"
}]),
loglist: ko.observableArray(),
add: function() {
this.list.push({
text: "time:"+Math.random().toFixed(5)
});
},
move:function(){
var item = this.list()[0];
this.list.remove(item);
$(".list >li:first").remove();
this.list.push(item);
},
del:function(){
var item=this.list()[0];
this.list.remove(item);
},
afterRender: function(eles,obj) {
// debugger;
// vm.loglist.push({text:"afterRender:"+obj.text});
},
afterAdd: function(ele,index,obj) {
// debugger;
// vm.loglist.push({text:"afterAdd:index:"+index+"text:"+obj.text});
},
beforeRemove: function(ele,index,obj) {
// debugger;
// vm.loglist.push({text:"beforeRemove:index:"+index+"text:"+obj.text});
// ele.parentNode.removeChild(ele);
},
beforeMove: function(ele,index,obj) {
// debugger;
// vm.loglist.push({text:"beforeMove:index:"+index+"text:"+obj.text});
},
afterMove: function(ele,index,obj) {
// debugger;
// vm.loglist.push({text:"afterMove:index:"+index+"text:"+obj.text});
},
};
ko.applyBindings(vm);
}
window.onload = function() {
main();
}

依赖

只依赖核心库

Knockout v3.4.0 中文版教程-16-控制流-foreach绑定的更多相关文章

  1. Knockout v3.4.0 中文版教程-1-入门和安装

    英文原版教程:http://knockoutjs.com/documentation/introduction.html 注:此教程根据英文原版翻译,仅作练习,如有不足或错误,请指正 说明: 对原文中 ...

  2. Knockout v3.4.0 中文版教程-10-绑定-控制文本内容和外观-visible绑定

    4.绑定 1. 控制文本内容和外观 1. visible绑定 目的 visible绑定可以根据你传入绑定的值控制关联的DOM元素显示或隐藏. 例子 <div data-bind="vi ...

  3. Knockout v3.4.0 中文版教程-6-计算监控-可写的计算监控

    2.可写的计算监控 初学者可能想要跳过本节 - 可写的计算监控是相当高级的部分,在大多数情况下不是必需的. 通常,计算监控是一个通过其他监控值计算出的值,因此是只读的. 令人惊讶的是,可以使计算监控值 ...

  4. Knockout v3.4.0 中文版教程-14-控制文本内容和外观-style绑定

    5. style绑定 目的 style绑定用来给关联的DOM元素添加或移除一个或多个样式值.在如下情况很有用,比如,当某些值为负时,高亮显示,或者设置容器元素的宽度来匹配数值的改变. (注意:如果你不 ...

  5. Knockout v3.4.0 中文版教程-13-控制文本内容和外观-css绑定

    4. css绑定 目的 css绑定可以给关联的DOM元素添加或移除一个或多个CSS类.该绑定很有用,比如,当一些值为负数时高亮这些值为红色. (注意:如果你不想使用一个CSS类选择器来附加样式而想直接 ...

  6. Knockout v3.4.0 中文版教程-9-计算监控-API参考

    5.参考 下面的内容描述了如何构建和使用计算监控. 1. 构建一个计算监控 可以用如下的形式构建一个计算监控: ko.computed( evaluator [, targetObject, opti ...

  7. Knockout v3.4.0 中文版教程-8-计算监控-纯计算属性

    4.纯计算属性 纯计算监控在KO 3.2.0中开始引入,比大多数应用程序使计算监控有更大的性能提升和内存优化.这是因为在自身没有订阅的时候不会保持订阅状态.特性如下 阻止内存泄露 - 避免在应用程序里 ...

  8. Knockout v3.4.0 中文版教程-5-计算监控-使用计算监控

    3. 计算监控 1.使用计算监控 如果你有一个监控的属性firstName和另一个lastName,但你想显示全名怎么办? 这就是引入计算监控的原因-这是依赖于一个或多个其他的observables函 ...

  9. Knockout v3.4.0 中文版教程-4-通过监控数组工作

    2.通过监控数组工作 1. 监控数组 如果你想检测或者响应一个对象的改变,你用observables.如果你想检测和响应一个集合的改变,使用observableArray.这个在很多情况下都非常有用, ...

随机推荐

  1. C8051F单片机定时器的定时

    假设C8051F020单片机的晶振是sysclk=22114800HZ,即每秒计22114800个数经过Div=12分频后得到定时器的计数频率Tclk=sysclk/12,每秒计22114800÷12 ...

  2. 配置Gradle构建

    构建基础配置 Android Studio包含一个顶级的构建文件和每个模块的构建文件.构建文件被称为 build.gradle,它是一个纯文本文件,它使用Groovy语法来配置由Android Gra ...

  3. 一些API

    /** * Goto the specified frame index, and pause at this index. * @param startIndex The animation wil ...

  4. vue axios post不能本地json

    vue 脚本架里axios post是不能本地json,GET可以 解决这个问题需要自己在node里写脚本: 在build里新建立fakedata.js var express = require(' ...

  5. Linux KDE 设置显示桌面的快捷键 win+d

    原文链接:http://blog.sina.com.cn/s/blog_4b91893c0100sxxg.html 到KDE下以后发现显示桌面的快捷键被用来显示平铺窗口,在Win下的时候一直用这个快捷 ...

  6. kafka-->storm-->mongodb

    目的: 通过Spout发射kafka的数据,到bolt统计每一个单词的个数,将这些记录更新到mongodb中. Spout的nextTuple方法会一直处于一个while循环这中,每一条数据发送给bo ...

  7. C#遍历文件夹下全部文件

    public static List<string> GetFile(string path, List<string> FileList, string RelativePa ...

  8. 远程文件拷贝(fastcopy为例)

    远程地址格式如下:\\IP地址\磁盘符号$\文件夹名称(如:127.0.0.1\\c$\\image)拷贝了image文件夹下面的所有文件,但是如果远程机器有密码的话要先在本机先输入远程的目标地址然后 ...

  9. UVA1515 Pool construction (最小割模型)

    如果不允许转化'#'和'.'的话,那么可以直接在'#'和'.'之间连容量为b的边,把所有'#'和一个源点连接, 所有'.'和一个汇点连接,流量不限,那么割就是建围栏(分割'#'和'.')的花费. 问题 ...

  10. Jordan 标准型的实例

    将学习到什么 练习一下如何把一个矩阵化为 Jordan 标准型.   将矩阵化为 Jordan 标准型需要三步: 第一步 求出矩阵 \(A \in M_n\) 全部的特征值 \(\lambda_1,\ ...