关于Knockout的3个重要概念(Observables,DependentObservables,ObservableArray),本人没法准确表达它的准确含义,因此暂定翻译为(监控属性、依赖监控属性和监控数组),若是有好的建议请指正,多谢。html
Observables数据库
Knockout是在下面三个核心功能是创建起来的:设计模式
这一节,你讲学到3个功能中的第一个。 在这以前, 咱们来解释一下MVVM模式和view model的概念。数组
MVVM and View Models浏览器
Model-View-View Model (MVVM) 是一种建立用户界面的设计模式。 描述的是如何将复杂的UI用户界面分红3个部分:服务器
注意这不是UI自己:它不包含任何按钮的概念或者显示风格。它也不是持续数据模型 – 包含用户正在使用的未保存数据。使用KO的时候,你的view models是不包含任何HTML知识的纯JavaScript 对象。保持view model抽象能够保持简单,以便你能管理更复杂的行为。app
使用KO的时候,你的view就是你带有绑定信息的HTML文档,这些声明式的绑定管理到你的view model上。或者你可使用模板从你的view model获取数据生成HTML。框架
建立一个view model,只须要声明任意的JavaScript object。例如:函数
var myViewModel = {
personName: 'Bob',
personAge: 123
};
你能够为view model建立一个声明式绑定的简单view。例如:下面的代码显示personName 值:post
The name is <span data-bind="text: personName"></span>
Activating Knockout
data-bind属性尽快好用但它不是HTML的原生属性(它严格听从HTML5语法, 虽然HTML4验证器提示有不可识别的属性但依然可用)。因为浏览器不识别它是什么意思,因此你须要激活Knockout 来让他起做用。
激活Knockout,须要添加以下的 <script> 代码块:
ko.applyBindings(myViewModel);
你能够将这个代码块放在HTML底部,或者放在jQuery的$函数或者ready 函数里,而后放在页面上面, 最终生成结果就是以下的HTML代码:
The name is <span>Bob</span>
你可能奇怪ko.applyBindings使用的是什么样的参数,
Observables
如今已经知道如何建立一个简单的view model而且经过binding显示它的属性了。可是KO一个重要的功能是当你的view model改变的时候能自动更新你的界面。当你的view model部分改变的时候KO是如何知道的呢?答案是:你须要将你的model属性声明成observable的, 由于它是很是特殊的JavaScript objects,可以通知订阅者它的改变以及自动探测到相关的依赖。
例如:将上述例子的view model改为以下代码:
var myViewModel = {
personName: ko.observable('Bob'),
personAge: ko.observable(123)
};
你根本不须要修改view – 全部的data-bind语法依然工做,不一样的是他能监控到变化,当值改变时,view会自动更新。
监控属性(observables)的读和写
不是全部的浏览器都支持JavaScript的 getters and setters (好比IE),,因此为了兼容性,使用ko.observable监控的对象都是真实的function函数。
监控属性(observables)的特征就是监控(observed),例如其它代码能够说我须要获得对象变化的通知,因此KO内部有不少内置的绑定语法。因此若是你的代码写成data-bind="text: personName", text绑定注册到自身,一旦personName的值改变,它就能获得通知。
固然调用myViewModel.personName('Mary')改变name的值,text绑定将自动更新这个新值到相应的DOM元素上。这就是如何将view model的改变传播到view上的。
监控属性(Observables)的显式订阅
一般状况下,你不用手工订阅,因此新手能够忽略此小节。高级用户,若是你要注册本身的订阅到监控属性(observables),你能够调用它的subscribe 函数。例如:
myViewModel.personName.subscribe(function (newValue) {
alert("The person's new name is " + newValue);
});
这个subscribe 函数在内部不少地方都用到的。你也能够终止本身的订阅:首先获得你的订阅,而后调用这个对象的dispose函数,例如:
var subscription = myViewModel.personName.subscribe(function (newValue) { /* do stuff */ });
// ...then later...
subscription.dispose(); // I no longer want notifications
大多数状况下,你不须要作这些,由于内置的绑定和模板系统已经帮你作好不少事情了,能够直接使用它们。
若是你已经有了监控属性firstName和lastName,你想显示全称怎么办? 这就须要用到依赖监控属性了 – 这些函数是一个或多个监控属性, 若是他们的依赖对象改变,他们会自动跟着改变。
例如,下面的view model,
var viewModel = {
firstName: ko.observable('Bob'),
lastName: ko.observable('Smith')
};
… 你能够添加一个依赖监控属性来返回姓名全称:
viewModel.fullName = ko.dependentObservable(function () {
return this.firstName() + " " + this.lastName();
}, viewModel);
而且绑定到UI的元素上,例如:
The name is <span data-bind="text: fullName"></span>
… 无论firstName仍是lastName改变,全称fullName都会自动更新(无论谁改变,执行函数都会调用一次,无论改变成什么,他的值都会更新到UI或者其余依赖监控属性上)。
管理‘this’
新手可忽略此小节,你只须要安装上面例子中的代码模式写就好了,无需知道/关注这个this。
你可能疑惑ko.dependentObservable的第二个参数是作什么用的(上面的例子中我传的是viewModel), 它是声明执行依赖监控属性的this用的。 没有它,你不能引用到this.firstName() 和this.lastName()。 老练的JavaScript 开发人员不以为this怎么样,可是若是你不熟悉JavaScript,那就对它就会很陌生。(C#和Java须要不须要为set一个值为设置this,可是JavaScript 须要,由于默认状况下他们的函数自身不是任何对象的一部分)。
不幸的是, JavaScript 对象没有任何办法能引用他们自身,因此你须要经过myViewModelObject.myDependentObservable = ... 的形式添加依赖监控属性到view model对象上。 你不能直接在view model里声明他们,换句话说,你不能写成下面这样:
var viewModel = {
myDependentObservable: ko.dependentObservable(function() {
...
}, /* can't refer to viewModel from here, so this doesn't work */)
}
… 相反你必须写成以下这样:
var viewModel = {
// Add other properties here as you wish
};
viewModel.myDependentObservable = ko.dependentObservable(function() {
...
}, viewModel); // This is OK
只要你知道指望什么,它确实不是个问题。J
依赖链
理所固然,若是你想你能够建立一个依赖监控属性的链。例如:
而后,items或者selectedIndexes 的改变将会影响到全部依赖监控属性的链,全部绑定这些属性的UI元素都会自动更新。多么整齐与优雅!
可写的依赖监控属性
新手可忽略此小节,可写依赖监控属性真的是太advanced了,并且大部分状况下都用不到。
正如所学到的,依赖监控属性是经过计算其它的监控属性而获得的。感受是依赖监控属性正常状况下应该是只读的。那么,有可能让依赖监控属性支持可写么?你只须要声明本身的callback函数而后利用写入的值再处理一下相应的逻辑便可。
你能够像使用普通的监控属性同样使用依赖监控属性 – 数据双向绑定到DOM元素上,而且经过自定义的逻辑拦截全部的读和写操做。这是很是牛逼的特性而且能够在大范围内使用。
例1:分解用户的输入
返回到经典的“first name + last name = full name” 例子上,你可让事情调回来看: 让依赖监控属性fullName可写,让用户直接输入姓名全称,而后输入的值将被解析并映射写入到基本的监控属性firstName和lastName上:
var viewModel = {
firstName: ko.observable("Planet"),
lastName: ko.observable("Earth")
};
viewModel.fullName = ko.dependentObservable({
read: function () {
return this.firstName() + " " + this.lastName();
},
write: function (value) {
var lastSpacePos = value.lastIndexOf(" ");
if (lastSpacePos > 0) { // Ignore values with no space character
this.firstName(value.substring(0, lastSpacePos)); // Update "firstName"
this.lastName(value.substring(lastSpacePos + 1)); // Update "lastName"
}
},
owner: viewModel
});
这个例子里,写操做的callback接受写入的值,把值分离出来,分别写入到“firstName”和“lastName”上。 你能够像普通状况同样将这个view model绑定到DOM元素上,以下:
<p>First name: <span data-bind="text: firstName"></span></p>
<p>Last name: <span data-bind="text: lastName"></span></p>
<h2>Hello, <input data-bind="value: fullName"/>!</h2>
这是一个Hello World 例子的反例子,姓和名都不可编辑,相反姓和名组成的姓名全称倒是可编辑的。
上面的view model演示的是经过一个简单的参数来初始化依赖监控属性。你能够给下面的属性传入任何JavaScript对象:
例2:Value转换器
有时候你可能须要显示一些不一样格式的数据,从基础的数据转化成显示格式。好比,你存储价格为float类型,可是容许用户编辑的字段须要支持货币单位和小数点。你能够用可写的依赖监控属性来实现,而后解析传入的数据到基本 float类型里:
viewModel.formattedPrice = ko.dependentObservable({
read: function () {
return "$" + this.price().toFixed(2);
},
write: function (value) {
// Strip out unwanted characters, parse as float, then write the raw data back to the underlying "price" observable
value = parseFloat(value.replace(/[^\.\d]/g, ""));
this.price(isNaN(value) ? 0 : value); // Write to underlying storage
},
owner: viewModel
});
而后咱们绑定formattedPrice到text box上:
<p>Enter bid price: <input data-bind="value: formattedPrice"/></p>
因此,无论用户何时输入新价格,输入什么格式,text box里会自动更新为带有2位小数点和货币符号的数值。这样用户能够看到你的程序有多聪明,来告诉用户只能输入2位小数,不然的话自动删除多余的位数,固然也不能输入负数,由于write的callback函数会自动删除负号。
例3:过滤并验证用户输入
例1展现的是写操做过滤的功能,若是你写的值不符合条件的话将不会被写入,忽略全部不包括空格的值。
再多走一步,你能够声明一个监控属性isValid 来表示最后一次写入是否合法,而后根据真假值显示相应的提示信息。稍后仔细介绍,先参考以下代码:
var viewModel = {
acceptedNumericValue: ko.observable(123),
lastInputWasValid: ko.observable(true)
};
viewModel.attemptedValue = ko.dependentObservable({
read: viewModel.acceptedNumericValue,
write: function (value) {
if (isNaN(value))
this.lastInputWasValid(false);
else {
this.lastInputWasValid(true);
this.acceptedNumericValue(value); // Write to underlying storage
}
},
owner: viewModel
});
… 按照以下格式声明绑定元素:
<p>Enter a numeric value: <input data-bind="value: attemptedValue"/></p>
<div data-bind="visible: !lastInputWasValid()">That's not a number!</div>
如今,acceptedNumericValue 将只接受数字,其它任何输入的值都会触发显示验证信息,而会更新acceptedNumericValue。
备注:上面的例子显得杀伤力太强了,更简单的方式是在<input>上使用jQuery Validation和number class。Knockout能够和jQuery Validation一块儿很好的使用,参考例子:grid editor 。固然,上面的例子依然展现了一个如何使用自定义逻辑进行过滤和验证数据,若是验证很复杂而jQuery Validation很难使用的话,你就能够用它。
依赖跟踪如何工做的
新手不必知道太清楚,可是高级开发人员能够须要知道为何依赖监控属性可以自动跟踪而且自动更新UI…
事实上,很是简单,甚至说可爱。跟踪的逻辑是这样的:
全部说,KO不只仅是在第一次执行函数执行时候探测你的依赖项,每次它都会探测。举例来讲,你的依赖属性能够是动态的:依赖属性A表明你是否依赖于依赖属性B或者C,这时候只有当A或者你当前的选择B或者C改变的时候执行函数才从新执行。你不须要再声明其它的依赖:运行时会自动探测到的。
另一个技巧是:一个模板输出的绑定是依赖监控属性的简单实现,若是模板读取一个监控属性的值,那模板绑定就会自动变成依赖监控属性依赖于那个监控属性,监控属性一旦改变,模板绑定的依赖监控属性就会自动执行。嵌套的模板也是自动的:若是模板X render模板 Y,而且Y须要显示监控属性Z的值,当Z改变的时候,因为只有Y依赖它,因此只有Y这部分进行了从新绘制(render)。
若是你要探测和响应一个对象的变化,你应该用observables。若是你须要探测和响应一个集合对象的变化,你应该用observableArray 。在不少场景下,它都很是有用,好比你要在UI上须要显示/编辑的一个列表数据集合,而后对集合进行添加和删除。
var myObservableArray = ko.observableArray(); // Initially an empty array
myObservableArray.push('Some value'); // Adds the value and notifies observers
关键点:监控数组跟踪的是数组里的对象,而不是这些对象自身的状态。
简单说,将一对象放在observableArray 里不会使这个对象自己的属性变化可监控的。固然你本身也能够声明这个对象的属性为observable的,但它就成了一个依赖监控对象了。一个observableArray 仅仅监控他拥有的对象,并在这些对象添加或者删除的时候发出通知。
预加载一个监控数组observableArray
若是你想让你的监控数组在开始的时候就有一些初始值,那么在声明的时候,你能够在构造器里加入这些初始对象。例如:
// This observable array initially contains three objects
var anotherObservableArray = ko.observableArray([
{ name: "Bungle", type: "Bear" },
{ name: "George", type: "Hippo" },
{ name: "Zippy", type: "Unknown" }
]);
从observableArray里读取信息
一个observableArray其实就是一个observable的监控对象,只不过他的值是一个数组(observableArray还加了不少其余特性,稍后介绍)。因此你能够像获取普通的observable的值同样,只须要调用无参函数就能够获取自身的值了。 例如,你能够像下面这样获取它的值:
alert('The length of the array is ' + myObservableArray().length);
alert('The first element is ' + myObservableArray()[0]);
理论上你可使用任何原生的JavaScript数组函数来操做这些数组,可是KO提供了更好的功能等价函数,他们很是有用是由于:
下面讲解的均是observableArray的读取和写入的相关函数。
indexOf
indexOf 函数返回的是第一个等于你参数数组项的索引。例如:myObservableArray.indexOf('Blah')将返回以0为第一个索引的第一个等于Blah的数组项的索引。若是没有找到相等的,将返回-1。
slice
slice函数是observableArray相对于JavaScript 原生函数slice的等价函数(返回给定的从开始索引到结束索引之间全部的对象集合)。 调用myObservableArray.slice(...)等价于调用JavaScript原生函数(例如:myObservableArray().slice(...))。
操做observableArray
observableArray 展示的是数组对象类似的函数并通知订阅者的功能。
pop, push, shift, unshift, reverse, sort, splice
全部这些函数都是和JavaScript数组原生函数等价的,惟一不一样的数组改变能够通知订阅者:
myObservableArray.push('Some new value') 在数组末尾添加一个新项
myObservableArray.pop() 删除数组最后一个项并返回该项
myObservableArray.unshift('Some new value') 在数组头部添加一个项
myObservableArray.shift() 删除数组头部第一项并返回该项
myObservableArray.reverse() 翻转整个数组的顺序
myObservableArray.sort() 给数组排序
默认状况下,是按照字符排序(若是是字符)或者数字排序(若是是数字)。
你能够排序传入一个排序函数进行排序,该排序函数须要接受2个参数(表明该数组里须要比较的项),若是第一个项小于第二个项,返回-1,大于则返回1,等于返回0。例如:用lastname给person排序,你能够这样写:myObservableArray.sort (function (left, right) {return left.lastName == right.lastName? 0: (left.lastName < right.lastName? -1: 1) })
myObservableArray.splice() 删除指定开始索引和指定数目的数组对象元素。例如myObservableArray.splice(1, 3) 从索引1开始删除3个元素(第2,3,4个元素)而后将这些元素做为一个数组对象返回。
更多observableArray 函数的信息,请参考等价的JavaScript数组标准函数。
remove和removeAll
observableArray 添加了一些JavaScript数组默认没有但很是有用的函数:
myObservableArray.remove(someItem) 删除全部等于someItem的元素并将被删除元素做为一个数组返回
myObservableArray.remove(function(item) { return item.age < 18 }) 删除全部age属性小于18的元素并将被删除元素做为一个数组返回
myObservableArray.removeAll(['Chad', 132, undefined]) 删除全部等于'Chad', 123, or undefined的元素并将被删除元素做为一个数组返回
destroy和destroyAll(注:一般只和和Ruby on Rails开发者有关)
destroy和destroyAll函数是为Ruby on Rails开发者方便使用为开发的:
myObservableArray.destroy(someItem) 找出全部等于someItem的元素并给他们添加一个属性_destroy,并赋值为true
myObservableArray.destroy(function(someItem) { return someItem.age < 18 }) 找出全部age属性小于18的元素并给他们添加一个属性_destroy,并赋值为true
myObservableArray.destroyAll(['Chad', 132, undefined]) 找出全部等于'Chad', 123, 或undefined 的元素并给他们添加一个属性_destroy,并赋值为true
那么,_destroy是作什么用的?正如我提到的,这只是为Rails 开发者准备的。在Rails 开发过程当中,若是你传入一个JSON对象,Rails 框架会自动转换成ActiveRecord对象而且保存到数据库。Rails 框架知道哪些对象以及在数据库中存在,哪些须要添加或更新, 标记_destroy为true就是告诉框架删除这条记录。
注意的是:在KO render一个foreach模板的时候,会自动隐藏带有_destroy属性而且值为true的元素。因此若是你的“delete”按钮调用destroy(someItem) 方法的话,UI界面上的相对应的元素将自动隐藏,而后等你提交这个JSON对象到Rails上的时候,这个元素项将从数据库删除(同时其它的元素项将正常的插入或者更新)。