Knockout是微软出品,是MVVM模型领域内的先驱,使用函数偷龙转凤,最短编辑长度算法实现DOM的同步,兼容IE6。
Knockout是一个JS的MVVM模式的实现,Knockout是建立在3个核心功能之上:
- 属性监控与依赖追踪
- 声明式绑定
- 模板机制
MVVM
MVVM是微软WPF结合发展演变的一种新型的架构框架。
MVVM设计模式的优点
- 低耦合
Model的修改不会影响视图的展现,ViewModel可绑定不同的View,当View变化时Model可以不变,当Model变化时View也可以不变。 - 开发独立性
前端更加专注于界面,后端更加专注于逻辑与数据。 - 重用性
ViewModel可对应多个视图,如PC或手机。 - 测试方便
MVVM(Model-View-View Model)是创建用户界面的设计模式,它将UI界面分为3部分:
Model
Model用于存储应用程序的数据,其数据表示业务领域的对象和数据操作,它独立于任何界面。使用KO时通常是使用AJAX向服务端请求数据赖读写数据模型Model。ViewModel
纯粹用于描述数据内容和页面操作的数据模型。ViewModel不是UI本身,不具有任何HTML和样式的概念。ViewModel不是持久化的数据模型,它仅仅是用户当前使用的未保存的数据。使用KO时,ViewModel是纯粹的不包含HTML的JS对象,保持ViewModel抽象在使用时可保持简单,因此可更简单的操作管理更简单的行为。View
View表示ViewModel状态的一个可见、互动的UI界面,主要用于显示ViewModel的数据信息、发送用户命令以及在ViewModel发生变化时保持自动更新。
使用KO时,View层主要是HTML文档式声明式的绑定到ViewModel,将其关联起来。另外可利用模板从ViewModel中获取对象动态生成的HTML。
知识点
操作流程
- 创建数据模型
- 添加绑定属性
- 设置监控属性
- 激活KO
使用KO创建ViewModel仅需声明一个JS对象
var vm= {username:“username”, nickname:"nickname"};
创建视图声明式绑定到ViewModel对象上
data-bind
属性并不是HTML固有对象,它遵守HTML5尽管验证器指出它是一个无法验证的属性,但HTML4使用不会造成任何问题。只是浏览器并不是它是什么意思,所以需要激活Knockout使其生效。
ko.applyBinding(vm, document.getElementById('app'));
激活KO的applyBinding
的参数
- 激活KO时用于声明式绑定的ViewModel对象
- 设置使用
data-binding
属性的HTML元素或容器,可选。
监控
创建数据模型后,例如绑定来实现它的属性。KO最重要的属性时当数据模型发生变化时,能够自动更新UI界面。KO是如何知道数据模型已经发生改变呢?此时,需将model属性声明成observable
的,因此它是非常特殊的JS对象,能够通知用户它的改变以及自动检测依赖关系。
var vm = {
username:ko.observable("default value"),
nickname:ko.observable("default value")
};
读取和写入监控
由于并不是所有浏览器都只是JS的getters
和setters
,为了兼容,ko.observable()
监控的对象都是方法。
- 读取当前监控的属性值,调用
observable()
方法。
vm.username()
- 写入新值到监控属性上,调用
observable()
方法,将需要写入的值作为参数化传入即可。
vo.username("new value");
- 一次性改变model对象监控的对个属性值,可使用链式方法。
vm.username("new value").nickname("new value")
监控属性最重要的一点是可以随时监控,也就是说,其他代码可以告诉它那些是需要通知发生变化的。这就是为什么KO会有如此多的内置绑定语法。
var vm = {username:"defualt value"};
vm.username("new value"); //text会自动更新i新值到对应的DOM对象元素上
监控属性的显示订阅
如果想要注册自己的订阅来通知监控属性的变化可使用 subscribe()
方法
vm.username.subscribe(function(newval){
});
subscribe()
方法在KO内部使用较多,也可以终止自己的订阅,首先是要创建订阅然后调用 dispose()
方法即可。
var subscribe = vm.username.subscribe(function(newval){
});
subcribe.dispose();//终止订阅
监控属性数组
使用监控属性observable
可发现并响应一个对象的改变,observableArray
可针对一个集合的变化进行监控。监控属性数组在显示或编辑多个值以及界面部分显示隐藏(如增减条目)操作中使用频繁。
// 实例化监控数组
var arr = ko.observableArray();
// 向监控数组中添加记录
arr.push("value");
//实例化并批量添加监控数组元素
var arr = ko.observableArray([
{username:"alice", usertype:"manager"},
{username:"alibaba", usertype:"operator"}
]);
// 获取监控数组长度
console.log(arr.length, arr()[0]);
监控数组常用方法
// 新增元素
push(value)
//删除元素并返回其值
pop()
//数组头部添加
unshihft(value)
//删除头部元素
shift()
//数组反转
reverse()
//数组排序
sort(callback)
//数组截取
splice(start_position, element_count)
//移除元素,匹配元素并删除后返回新数组
remove(element)
remove(callback)
//移除所有
removeAll()
removeAll(match_condition)
数组双向绑定
observableArray
跟踪的是数组中的对象,而不是对象的状态。也就是说observableArray
仅仅跟踪它所拥有的对象,在对象被添加或删除时,会通知listeners
。
要监控对象的属性变更,需要编写代码,因为observableArray()
返回的是一个数组,可使用原生JS的数组方法对其操作。
App
错误提示
监控
var arylist = ["Manager", "Partner", "Agent"];
var objary = [
{key:"管理员", value:"MANAGER"},
{key:"系统管理员", value:"SA"},
{key:"渠道商", value:"PARTNER"},
{key:"代理商", value:"AGENT"}
];
var ViewModel = function() {
var self = this;
self.datalist = ko.observableArray(arylist);
self.objlist = ko.observableArray(objary);
self.selectedItem = ko.observable();
};
ko.applyBindings(new ViewModel(), document.getElementById("id"));
绑定
data-bind="options:datalist, selectedOptions:selectedItem"
data-bind="options:objlist, optionsText:'key', optionsValue:'value', selectedOptions:selectedItem"
计算属性
计算属性computed()
依赖于一个或多个监控属性observable
,如果任何依赖对象发生改变,他们就会跟着改变。
function ViewModel()
{
this.username = ko.observable("defaule value");
this.usertype = ko.observable("default value");
this.account = ko.computed(function(){
return this.username() +"("+ this.usertype()+")";
},this);
}
当依赖属性时定义了this
的值,没有传递它的进入,注意在默认情况下this
是函数本身并不是任何对象的一部分。当需要全程跟踪this
时,一种简化的流行惯例是:如果将ViewModel结构this
作为一个变量复制一份(传统称为self
),其后可使用self
来表示ViewModel而不必担心它重定义或指代其他的东西。
function ViewModel()
{
var self = this;
self.username = ko.observable("default value");
self.usertype = ko.observable("default value");
self.account = ko.computed(function(){
return self.username() + "("+self.usertype+")";
});
}
由于self
在闭合的方法内部也是可以捕获到的,所以在任何嵌套函数中,仍可用并保持一致性。
由于计算属性时通过计算其他监控属性而得到的结果,从这个意义上说,计算属性通常情况下是只读的。如何将计算属性变得可写,只要提供回调函数来实现值的写入即可。当然,也可将可写的计算作为普通的监控属性使用,通过自定义的逻辑来实现它的读写。
分解用户输入
function ViewModel(){
this.key = ko.observable();
this.val = ko.observable();
this.cfg = ko.computed({
read:function(){
return this.key() + "=" + this.val();
},
write:function(value){
var value = value.trim(" ");
var pos = value.lastIndexOf("=");
if(pos>0){
var key = value.substring(0,pos);
this.key(key);
var val = value.substring(pos+1);
this.val(val);
}
},
owner:this
});
}
ko.applyBindings(new ViewModel(), document.getElementById('app'));
值转换
function ViewModel(){
this.price = ko.observable(1.234);
this.format = ko.computed({
read:function(){
return "$" + parseFloat(this.price()).toFixed(2);
},
write:function(value){
value = value.replace(/[^\.\d]/g, "");
value = parseFloat(value);
value = isNaN(value) ? 0 : value;
this.price(value);
},
owner:this
});
}
ko.applyBindings(new ViewModel(), document.getElementById('app'));
依赖跟踪
跟踪算法流程
- 当声明一个依赖属性时,KO会立即调用求值算法获得其初始值。
- 当计算函数运行时,KO会把监控属性通过计算得到的结果记录到Log中。
- 当计算结束时,KO会订阅能够访问的监控属性或依赖属性,订阅的回调函数时重新运行计算函数,循环整个过程,回到步骤1,并注销不再使用的订阅。
- KO通知所有订阅者,依赖属性已经被设置了新值。
KO并不仅仅是在第一次执行计算函数时,检测依赖项。而是每次都会检测,这意味着,依赖可以是动态的。
声明绑定是依赖属性的简单实现,当一个绑定为读取监控属性的值,这个绑定会变为监控属性的依赖。当监控属性发生改变的时候,会引起绑定被重新计算。
数据绑定
数据绑定有三种形式:
- 一次性绑定
one-time
一次性绑定为从ViewModel绑定到UI只进行一次绑定操作,程序不会继续追踪数据在两者中的变化。可用于报表数据,数据仅仅会加载一次。 - 单向绑定
one-way
单向绑定即object-UI
的绑定,只有当ViewModel中数据发生变化时,UI的数据才会随之发生变化,反之不然。 - 双向绑定
two-way
双向绑定是无论数据在object
还是UI中发生变化时,应用程序将会更新另一方。这是最灵活的绑定方式,同时代价也是最大的。数据绑定只是作为元素的自定义属性写入标签内,并不能决定它是何种绑定。
KO中如果值是通过ko.observable
定义的则说明是双向绑定,否则为一次性绑定,在KO中是不存在单向绑定的。
绑定参数
显示隐藏绑定
visible
绑定是通过绑定一个 值来确定DOM元素的显示和隐藏状态
visiable show area
var vm = {
isShow:ko.observable(true)
};
vm.isShow(false);
visible绑定会设置元素的element.style.display
,当visible为false时为隐藏状态即display为none的状态,它的优先级要高于CSS中定义的隐藏样式。
可使用函数或表达式来控制元素的显示和隐藏
visible show area
var vm = {
datalist:ko.observableArray([]);
};
datalist.push(element);
文本绑定
text
绑定主要是让DOM元素显示参数值,通常绑定在和
这样的元素上非常有用,实际上可绑定在任何DOM元素上。
var vm = {
message:ko.observable();
};
vm.message("hello");
KO将参数值设置为DOM元素内容,DOM元素之前的内容将会被覆盖掉。
- 如果参数是
observable
值,text
绑定将会在值变化时更新内容。 - 如果参数不是
observable
值,text
绑定仅仅会设置DOM元素内容一次,以后不会再更新。 - 如果传入的不是一个值或一个字符串,那么显示的文本将是
toString()
处理后的结果。 - 可使用函数或表达式来决定
text
值,如果想要text
值可编程化,可创建计算属性,在运算函数中编码决定text
中显示内容。
var vm = {};
vm.price:ko.observable(10.11);
vm.rating = ko.computed(function(){
return this.price() > 100 ? "expensive" : "affordable";
}, vm);
循环绑定
app
错误提示
page title
page description
var datalist = [
{id:1, username:"alice", usertype:"manager"},
{id:2, username:"alibaba", usertype:"operator"},
{id:3, username:"alex", usertype:"boss"},
];
function ViewModel()
{
var self = this;
self.username = ko.observable();
self.usertype = ko.observable();
self.datalist = ko.observableArray(datalist);
self.add = function(){
var item = {};
item.id = self.datalist().length + 1;
item.username = self.username();
item.usertype = self.usertype();
console.log(item);
self.datalist.push(item)
};
self.delete = function(){
self.datalist.remove(this);
};
}
ko.applyBindings(new ViewModel(), document.getElementById("app"));
var datalist = [
{id:1, username:"alice", usertype:"manager"},
{id:2, username:"alibaba", usertype:"operator"},
{id:3, username:"alex", usertype:"boss"},
];
var vm = {};
vm.username = ko.observable();
vm.usertype = ko.observable();
vm.datalist = ko.observableArray(datalist);
vm.add = function(){
var item = {};
item.id = vm.datalist.length + 1;
item.username = vm.username();
item.usertype = vm.usertype();
vm.datalist.push(item)
};
vm.delete = function(){
vm.datalist.remove(this);
};
ko.applyBindings(vm, document.getElementById("app"));