Knockout是构建在3个核心的特性上的:
- 监控属性(Observables)和依赖跟踪(Dependency tracking)
- 声明式绑定(Declarative bindings)
- 模版(Declarative bindings)
一、模型-视图-视图模型(MVVM)是一种设计模式用来构建用户界面,它描述了如何让一个复杂的UI界面分解成3个部分
1、模型:你应用存储的数据。数据包括对象和业务操作。
(例如:银子账户可以完成转账功能)并且独立于任何的UI,使用KO的时候,通常说是向服务器调用Ajax读写这个存储的模型数据。
2、视图模型:
在UI上,纯code描述的数据以及操作。
例如,如果你实现列表编辑,你的view model应该是一个包含列表项items的对象和暴露的add/remove列表项(item)的操作方法。
注意这不是UI本身:它不包含任何按钮的概念或者显示风格。使用KO的时候,你的view models是不包含任何HTML知识的纯JavaScript 对象。保持view model抽象可以保持简单,以便你能管理更复杂的行为。
3、视图:——带有data-bind的html
一个可见的,交互式的,表示view model状态的UI。 从view model显示数据,发送命令到view model(例如:当用户click按钮的时候) ,任何view model状态改变的时候更新。
使用KO的时候,你的view就是你带有绑定信息的HTML文档,这些声明式的绑定管理到你的view model上。或者你可以使用模板从你的view model获取数据生成HTML
二
1、激活Knockout
data-bind 属性不是HTML本身持有的。你需要激活Knockout让这个属性生效。
激活Knockout,需要添加如下的 <script> 代码块:
ko.applyBindings(myViewModel);
你可能比较疑惑,ko.applyBindings使用了什么参数?
第一个参数就是view model模型对象,你想要用来激活声明绑定。
可选参数,你能通过第二个参数来定义上下文,也就是说可以在指定的文档范围内查找 data-bind属性
- 例如:
ko.applyBindings(myViewModel, document.getElementById('someElementId'))
- 它的现在是只有作为someElementId 的元素和子元素才能激活KO功能。 好处是你可以在同一个页面声明多个view model,用来区分区域。
2、Observables 监控属性
KO有一个核心的功能,当你的view model发生改变的时候它会自动更新你的UI。
当你的view model发现改变时怎么才能让KO知道呢?
回答:你需要把模型的属性声明称监控属性,因为它是非常特殊的javascript对象,能够通知在改变的时候通知订阅者,并且能够自动侦测依赖。
例如:改写以前一个view model对象
var
myViewModel = {
personName: ko.observable(
'Bob'
),
personAge: ko.observable(123)
};
3、Reading and writing observables 监控属性的读和写
番外:
Getters和Setters使你可以快速获取或设置一个对象的数据。一般来说,一个对象拥有两个方法,分别用于获取和设置某个值,比如:
{
getValue: function(){
return this._value;
},
setValue: function(val){
this._value = val;
}
}
不是所有的游览器都支持JavaScript getters 和 setter。
所以为了兼容,ko.observable 对象实际上一个 functions。
读监控属性当前的值,直接调用不需要参数。
- 例如:
myViewModel.personName()
will return'Bob'
, andmyViewModel.personAge()
will return123
.
写一个新的值到监控属性,调用监控属性并且传入一个新的值作为一个参数。
- 例如:
myViewModel.personName('Mary')
将把值变成'Mary'。
在一个model对象中写一个值到多个监控属性,你将能用到链式 语法。
- 例如:
- myViewModel.personName('Mary').personAge(50)
- 将把name的值变'Mary'并且age的值变成50.
observables的意义就是能够被监控(observed),其他代码可以这样说,它想要更改的通知。所以KO内部有很多内置的绑定语法。所以,当你写data-bind="text: personName",这个text会注册绑定它自己被通知改变,当personName的值改变,它就能得到通知(假设这是一个可以observable的值)。当你用myViewModel.personName('Mary')改变这个name值是value = ’Mary’时,text绑定将自动更新这个新值到相应的DOM元素上,这就是如何改变视图模型自动传播到视图的。
4、Explicitly subscribing to observables 显式订阅监控属性
假如你想注册自己的订阅通知的变化来观察,你能够调用它的subscribe方法,例如
var
subscription = myViewModel.personName.subscribe(
function
(newValue) {
/* do stuff */
});
// ...then later...
subscription.dispose();
// I no longer want notifications
|
|
myViewModel.personName.extend({ notify:
'always'
});
三
1、KO是什么?
它的出现主要是为了方便的解决下面的问题:
- UI元素较多,用户交互比较频繁,需要编写大量的手工代码维护UI元素的状态、样式等属性?
- UI元素之间关系比较紧密,比如操作一个元素,需要改变另外一个元素的状态?
- DOM元素与Js对象之间的数据同步?
- 前端javascript代码组织不理想?用户输入数据校验、DOM操作、后台交互…,交织在一起?
1、基础概念一:viewModel
负责处理UI事件的响应,响应用户操作。
- 负责保存领域模型在前端的变体Model’,比如:Student模型,在UI元素与Model之间同步数据(用户修改input-->ko修改model,反之亦然)
- 在需要的时候,可以使用Helper方法轻松地从viewModel中剥离出需要传递给Server的数据,通过ajax方式与后台交互。
- 负责接收Server端发送过来的数据(可能是Ajax请求),更新模型数据,同时更新UI展现。
2、基础概念二:Observable与computed
纵观KO的所有应用场景,基本上这2个属性至少会用到一个。个人认为这是KO最常使用的东西。他们用法如下:
- Observable(监控属性):监控自身属性的变化,向外部发送通知。外部通过subscribe方法来订阅属性的变化事件。
- Computed(依赖属性):在早期版本中叫做dependentObservable,它通常依赖于其他的Observable,通过计算得出自己的数据。当依赖项改变的时候,computed属性会接到通知,然后同步更新自身
*这里提2点:
- 虽然本文称之为“属性”,但是本质上他们是js的function对象,所以访问的时候需要加()号
- 默认情况下Computed的同步发生在任意的Observable变化的时候,可是某些情况下我们可能不希望它更新的如此频繁,比如用户正在输入的过程中。KO有其他办法来延迟更新,在本系列后面会有专门文章介绍。
3、如何激活KO绑定
KO中,绑定是需要激活的,可以理解为把viewModel的数据与Html文档的DOM元素进行分析和关联。
通常是在页面元素、viewModel数据加载完毕之后,就可以激活绑定了。当然你可以在任何时候你想进行绑定的时候来激活。
只需要下面的代码:
var model = new AppViewModel();//实例化一个viewModel ko.applyBindings(model); //绑定到整个页面
这样KO会在整个Body中寻找需要进行绑定的元素,与viewModel进行绑定。当然你也可以指定绑定的根节点,这样的好处:
- 可以缩小KO查找绑定的范围(毕竟不是整个页面都需要进行binding)
- 可以同时使用多个viewModel,分别负责不同区域的绑定
很简单,加一个参数即可:
var model = new AppViewModel(); ko.applyBindings(model, document.getElementById("Demo1")); //Demo1可能是一个Div
4、实例讲解:Observable
(1)定义
var
myViewModel = {
personName: ko.observable(
'Bob'
),
//定义叫做personName的监控属性
personAge: ko.observable(123)
//定义叫做personAge的监控属性
};
(2)读取
var
a=myViewModel.personName();
//a为'Bob'
(3)写入、连续写入(链式调用)
myViewModel.personName(
'Mary'
).personAge(50);
//同时修改了2个属性值
(4)订阅属性修改事件
myViewModel.personName.subscribe(
function
(newValue) {
alert(
"The person's new name is "
+ newValue);
});
myViewModel.personName(
'换个名字'
);
//这时候会弹出alert。因为上面订阅了。
四、
1、Computed Observables
如果你有监控属性
firstName和lastName的话,此时如果你想要显示全名?
这个时候computed(以前叫做依赖)监控属性就出马了,这是一个函数用来依赖一个或者多个监控属性,并且当其中的任何一个依赖对象被改变的时候都将会自动更新。
例如,view model类
function
AppViewModel() {
this
.firstName = ko.observable(
'Bob'
);
this
.lastName = ko.observable(
'Smith'
);
}
你可以增加一个computed计算依赖的来得到一个全名
function
AppViewModel() {
// ... leave firstName and lastName unchanged ...
this
.fullName = ko.computed(
function
() {
return
this
.firstName() +
" "
+
this
.lastName();
},
this
);
}
现在你可以绑定到它的UI元素
The name is <span data-bind=
"text: fullName"
></span>
当
firstName或
lastName发生改变它都会更新(不管谁改变,执行函数都会调用一次,不管改变成什么,他的值都会更新到UI或者其他依赖监控属性上)
2、管理this
ko.computed的第二个参数是什么,你是否很疑惑?
在前面的代码,我们在定义computed依赖的时候用到了this,没有它,将不能够引用到
this.firstName()
或this.lastName()。
老练的Javascript程序员就觉得很平常,但是假如不怎么了解Javascript就会觉得难以理解(如C#和Java程序员不需要设置此值,但JavaScript呢,作用域是可以被改变的)
ko.computed(
function
(){
},
this
);
3、A popular convention that simplifies things
可以用一个简单的办法去简化这种行为
这是一种比较流行的办法用于避免追踪this:
如果你的模型的构造函数复制一个引用this到一个不同的变量(通常称为self),然后你可以用self的在你的模型和不必担心它被重新定义指的是别的东西。
比如说
function
AppViewModel() {
var
self =
this
;
self.firstName = ko.observable(
'Bob'
);
self.lastName = ko.observable(
'Smith'
);
self.fullName = ko.computed(
function
() {
return
self.firstName() +
" "
+ self.lastName();
});
}
因为self是在函数的闭包中被捕获,在任何嵌套函数仍然是同一个,例如ko.computed的evaluator,当你设计到事件句柄的时候这个技巧更有用。
4、Dependency chains just work依赖链的工作
当然,你希望你能创建一个计算监控属性链,例如,你可以这样
- 监控属性items表述一组列表项
- 监控属性selectedIndexes保存着被用户选上的列表项的索引
- 依赖监控属性selectedItems 返回的是selectedIndexes 对应的列表项数组
- 另一个依赖监控属性返回的true或false依赖于 selectedItems 的各个列表项是否包含一些属性(例如,是否新的或者还未保存的)。一些UI element(像按钮的启用/禁用)的状态取决于这个值)。
- 然后,items或者selectedIndexes 的改变将会影响到所有依赖监控属性的链,所有绑定这些属性的UI元素都会自动更新。
5、How dependency tracking works依赖跟踪如何工作的
这个跟踪的算法是这样的:
- 当你声明一个依赖监控属性的时候,KO会立即调用执行函数并且获取初始化值。
- 当你的执行函数运行的时候,KO会把任何在监控属性(或者计算监控属性))读到的值都会都记录到一个Log列表里。
- 执行函数结束以后,KO会向所有Log里需要依赖到的对象进行订阅。订阅的callback函数是重新运行你的执行函数。然后回头重新执行上面的第一步操作(并且注销不再使用的订阅)。
- 最后KO会通知所有订阅它的订阅者,告诉它们我已经设置了新值。
所有说,KO不仅仅是在第一次执行函数执行时候探测你的依赖项,每次它都会探测。举例来说,你的依赖属性可以是动态的:依赖属性A代表你是否依赖于依赖属性B或者C,这时候只有当A或者你当前的选择B或者C改变的时候执行函数才重新执行。你不需要再声明其它的依赖:运行时会自动探测到的。
另外一个技巧是:一个模板输出的绑定是依赖监控属性的简单实现,如果模板读取一个监控属性的值,那模板绑定就会自动变成依赖监控属性依赖于那个监控属性,监控属性一旦改变,模板绑定的依赖监控属性就会自动执行。
6、
Controlling dependencies using peek
使用Peek控制依赖
Knockout’s自动跟踪依赖通常下是你想要的。但是你可能有时候需要控制某一个监控属性去更新你的计算依赖属性,特别是如果你的计算依赖可执行一些操作,
比如Ajax请求,那么peek函数就能够让你访问一个observable或者computed observable而不是创建一个依赖
在下面的例子,一个
2345678ko.computed(
function
() {
var
params = {
page:
this
.pageIndex(),
selected:
this
.selectedItem.peek()
};
$.getJSON(
'/Some/Json/Service'
, params,
this
.currentPageData);
},
this
);
7、
Determining if a property is a computed observable
假如有一个属性是依赖属性
在一些场景中,如果你是处理一个依赖属性它是有用的编程方式,Knockout提供一个应用函数
ko.isComputed 将会帮助你解决这些情况
例如,数据从服务器返回回来,你可以要排除依赖属性
23456for
(
var
prop
in
myObject) {
if
(myObject.hasOwnProperty(prop) && !ko.isComputed(myObject[prop])) {
result[prop] = myObject[prop];
}
}
此外,Knockout 提供了类似的功能,能够对监控属性和依赖属性起到作用
- ko.isObservable:返回true的,监控属性,监控数组和所有的依赖属性
- ko.isWriteableObservable:返回true, 监控属性,监控数组,和可写的依赖属性
8、
Computed Observable Reference
引用依赖属性
一个依赖属性可以有下面的形式构造出来:
- ko.computed( evaluator [, targetObject, options] ) 最常见的情况,这种形式的支持创建一个依赖属性
evaluator — 一个函数,用来求出依赖属性当前的值
targetObject — 就是回调函数中,引用当前的this,要指定作用域,详情看managing
this
options
— 为依赖属性的配置更多的属性
- ko.computed( options ) 创建一个依赖属性,传入的是一个单个对象:
read
— 必选,一个用来执行取得依赖监控属性当前值的函数。write — 可选,如果声明的依赖属性是可写的,那么这个函数接受一个值,那么其他代码将会试着写入到依赖属性,过自定义逻辑将值再写入各个基础的监控属性上。
owner
— 可选,如果声明,它就是KO调用read或write的callback时用到的this。deferEvaluation
— 可选,假如是true,那么依赖属性的值你不能获取,默认情况下,依赖属性获取这个值的话会立刻创建disposeWhen
— 可选,待翻译,等分析源码的时候补上disposeWhenNodeIsRemoved — 可选,待翻译,等分析源码的时候补上
9、
依赖属性可提供以下功能
dispose()
— 手动配置依赖属性,清除所有订阅依赖,如果你想要停止依赖属性,当正在更新或者想要清除依赖属性的内存extend(extenders)
— 给依赖属性扩展一些内容getDependenciesCount()
— 返回当前被依赖属性依赖的数量getSubscriptionsCount()
— 返回当前依赖属性的订阅数量(或者从其他计算机的依赖属性或手动订阅)isActive()
— 返回依赖属性支持更新,如果没有依赖关系,将是无效的peek()
— 返回当前没有创建依赖关系的值(看peek
)subscribe( callback [,callbackTarget, event] )
— 手工注册依赖通知