Knockout容许您实现复杂的客户端交互,可是几乎全部web应用程序还须要与服务器交换数据,或者至少要序列化数据以供本地存储。交换或存储数据最方便的方式是JSON格式——目前大多数Ajax应用程序都使用这种格式。javascript
Knockout 并不强制您使用任何特定的技术来加载或保存数据。您可使用适合您所选择的服务器端技术的任何方便的机制。最经常使用的机制是jQuery的Ajax助手方法,如getJSON、post和Ajax。您能够从服务器获取数据:css
$.getJSON("/some/url", function(data) { // Now use this data to update your view models, // and Knockout will update your UI automatically })
… 或者能够将数据发送到服务器r:html
var data = /* Your data in JSON format - see below */; $.post("/some/url", data, function(returnedData) { // This callback is executed if the post was successful })
视图模型是JavaScript对象,所以在某种意义上,您可使用任何标准的JSON序列化器(好比JSON)将它们序列化为JSON.stringify(现代浏览器中的一个本机函数)或json2.js
库。然而,您的视图模型可能包含可观察性、计算可观察性和可观察数组,这些数组被实现为JavaScript函数,所以若是不进行额外的工做,就不能始终干净地序列化。java
为了便于序列化视图模型数据,包括可观察对象等,Knockout包括两个帮助函数:react
ko.toJS
— 这个克隆您的view mode的对象图,替换每一个可观察对象的当前值,这样您就获得了一个纯拷贝,它只包含您的数据,没有与Knockout相关的工件。ko.toJSON
— 这将生成一个JSON字符串,表示view model 的数据。在内部,它只是在view model上调用 ko.toJS
而后在结果上使用浏览器的原生JSON序列化器。注意:对于没有原生JSON序列化器的旧浏览器(如ie7或更早版本),要使其工做,还必须引用 json2.js
库。例如,定义一个视图模型,以下所示:git
var viewModel = { firstName : ko.observable("Bert"), lastName : ko.observable("Smith"), pets : ko.observableArray(["Cat", "Dog", "Fish"]), type : "Customer" }; viewModel.hasALotOfPets = ko.computed(function() { return this.pets().length > 2 }, viewModel)
这包含可观测值、计算的可观测值、可观测数组和普通值的混合。您可使用ko.toJSON
将其转换为适合发送到服务器的JSON字符串,以下所示:github
var jsonData = ko.toJSON(viewModel); // Result: jsonData is now a string equal to the following value // '{"firstName":"Bert","lastName":"Smith","pets":["Cat","Dog","Fish"],"type":"Customer","hasALotOfPets":true}'
或者,若是您只想在序列化以前获得简单的JavaScript对象图,请使用ko.toJS
,以下所示:web
var plainJs = ko.toJS(viewModel); // Result: plain js如今是一个纯JavaScript对象,其中没有任何可观察的内容。这只是数据。 // The object is equivalent to the following: // { // firstName: "Bert", // lastName: "Smith", // pets: ["Cat","Dog","Fish"], // type: "Customer", // hasALotOfPets: true // }
请注意,ko.toJSON
接受与 JSON.stringify相同的参数。例如,在调试Knockout应用程序时,拥有视图模型数据的“实时”表示可能颇有用。要为今生成格式良好的显示,您能够将spaces参数传递到ko.toJSON
中,并绑定到您的视图模型,如:ajax
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>
若是您已经从服务器加载了一些数据,而且想要使用它来更新视图模型,那么最直接的方法就是本身动手。例如,json
// Load and parse the JSON var someJSON = /* 忽略: 从服务器上以您想要的方式获取它 */; var parsed = JSON.parse(someJSON); // Update view model properties viewModel.firstName(parsed.firstName); viewModel.pets(parsed.pets);
在许多场景中,这种直接方法是最简单和最灵活的解决方案。固然,当您更新视图模型上的属性时,Knockout将负责更新可视UI以匹配它。
然而,许多开发人员更喜欢使用基于约定的方法来使用传入数据更新视图模型,而不须要为每一个要更新的属性手动编写一行代码。若是视图模型具备许多属性或深度嵌套的数据结构,这将是有益的,由于它能够大大减小您须要编写的手工映射代码的数量。有关这项技术的更多细节,请参见 the knockout.mapping plugin插件。
Knockout observables提供支持读/写值所需的基本功能,并在该值发生变化时通知订阅者。 可是,在某些状况下,您可能但愿向可观察对象添加其余功能。 这可能包括向可观察对象添加附加属性,或者经过在可观察对象前面放置可写的计算可观察对象来拦截写入。 Knockout扩展器提供了一种简单灵活的方法来对可观察的这种类型的扩充。
建立扩展器须要向 ko.extenders
添加一个函数来延伸部分对象。该函数将可观察对象自己做为第一个参数,将任何选项做为第二个参数。而后它能够返回可观察的,或者返回一些新的东西,好比一个计算的可观察的,以某种方式使用原始的可观察的。
这个简单的logChange
extender订阅可观察对象,并使用控制台编写任何更改以及可配置的消息。
ko.extenders.logChange = function(target, option) { target.subscribe(function(newValue) { console.log(option + ": " + newValue); }); return target; };
您能够经过调用一个可观察对象的extend
函数并传递一个包含logChange
属性的对象来使用这个扩展程序。
this.firstName = ko.observable("Bob").extend({logChange: "first name"});
若是 firstName
observable的值被更改成Ted
,那么控制台将显示firstName: Ted
。
本例建立了一个扩展器,该扩展器强制将写入到可观察对象的数据四舍五入到可配置的精度级别。在这种状况下,扩展器将返回一个新的可写计算可观察对象,该可写计算可观察对象将位于实际可观察到的拦截写以前。
Source code: View
<p><input data-bind="value: myNumberOne" /> (round to whole number)</p> <p><input data-bind="value: myNumberTwo" /> (round to two decimals)</p>
Source code: View model
ko.extenders.numeric = function(target, precision) { //create a writable computed observable to intercept writes to our observable var result = ko.pureComputed({ read: target, //always return the original observables value write: function(newValue) { var current = target(), roundingMultiplier = Math.pow(10, precision), newValueAsNum = isNaN(newValue) ? 0 : +newValue, valueToWrite = Math.round(newValueAsNum * roundingMultiplier) / roundingMultiplier; //only write if it changed if (valueToWrite !== current) { target(valueToWrite); } else { //if the rounded value is the same, but a different value was written, force a notification for the current field if (newValue !== current) { target.notifySubscribers(valueToWrite); } } } }).extend({ notify: 'always' }); //initialize with current value to make sure it is rounded appropriately result(target()); //return the new computed observable return result; }; function AppViewModel(one, two) { this.myNumberOne = ko.observable(one).extend({ numeric: 0 }); this.myNumberTwo = ko.observable(two).extend({ numeric: 2 }); } ko.applyBindings(new AppViewModel(221.2234, 123.4525));
注意,为了自动从UI中删除被拒绝的值,必须在计算的观察对象上使用.extend({notify: 'always'})
。若是没有这个,用户可能会输入一个无效的newValue
,当四舍五入时,它会给出一个未更改的valueToWrite
。而后,因为模型值不会更改,因此不会有更新UI中的文本框的通知。使用{notify: 'always'}
会致使文本框刷新(删除被拒绝的值),即便计算的属性没有更改值。
这个例子建立了一个扩展器,它容许根据须要对可观察对象进行标记。该扩展器不返回新对象,而只是向现有的可观察对象添加额外的子可观察对象。因为可观察对象是函数,它们实际上能够有本身的属性。然而,当视图模型被转换成JSON时,子可观察对象将被删除,只剩下实际可观察对象的值。这是一种添加仅与UI相关且不须要发送回服务器的附加功能的好方法。
Source code: View
<p data-bind="css: { error: firstName.hasError }"> <input data-bind='value: firstName, valueUpdate: "afterkeydown"' /> <span data-bind='visible: firstName.hasError, text: firstName.validationMessage'> </span> </p> <p data-bind="css: { error: lastName.hasError }"> <input data-bind='value: lastName, valueUpdate: "afterkeydown"' /> <span data-bind='visible: lastName.hasError, text: lastName.validationMessage'> </span> </p>
Source code: View model
ko.extenders.required = function(target, overrideMessage) { //add some sub-observables to our observable target.hasError = ko.observable(); target.validationMessage = ko.observable(); //define a function to do validation function validate(newValue) { target.hasError(newValue ? false : true); target.validationMessage(newValue ? "" : overrideMessage || "This field is required"); } //initial validation validate(target()); //validate whenever the value changes target.subscribe(validate); //return the original observable return target; }; function AppViewModel(first, last) { this.firstName = ko.observable(first).extend({ required: "Please enter a first name" }); this.lastName = ko.observable(last).extend({ required: "" }); } ko.applyBindings(new AppViewModel("Bob","Smith"));
在对可观察对象的.extend
方法的一次调用中能够应用多个扩展程序。
this.firstName = ko.observable(first).extend({ required: "Please enter a first name", logChange: "first name" });
在这种状况下,required
和logChange
扩展器都将针对咱们的observable执行。
Deferred updates are turned off by default to provide compatibility with existing applications. To use deferred updates for your application, you must enable it before initializing your viewmodels by setting the following option:
The following is a contrived example to demonstrate the ability of deferred updates to eliminate UI updates of intermediate values and how this can improve performance.
Even if you don’t enable deferred updates for your whole application, you can still benefit from this feature by specifically making certain observables deferred. This is done using the deferred
extender:
The following model represents data that you could render as a paged grid:
Although deferred, asynchronous notifications are generally better because of fewer UI updates, it can be a problem if you need to update the UI immediately. Sometimes, for proper functionality, you need an intermediate value pushed to the UI. You can accomplish this using the ko.tasks.runEarly
method. For example:
Although deferred, asynchronous notifications are generally better because of fewer UI updates, it can be a problem if you need to update the UI immediately. Sometimes, for proper functionality, you need an intermediate value pushed to the UI. You can accomplish this using the ko.tasks.runEarly
method. For example:
rateLimit
supports two parameter formats:
Consider the observables in the following code:
In this live example, there’s an instantaneousValue
observable that reacts immediately when you press a key. This is then wrapped inside a delayedValue
computed observable that’s configured to notify only when changes stop for at least 400 milliseconds, using the notifyWhenChangesStop
rate-limit method.
Try it:
Knockout 3.5 introduced the ability to specify a custom rate-limit method by passing a function to the rateLimit
extender rather than just a string. The function is called with three parameters (function, timeout, options) and must return a new, rate-limited function. Whenever the observable has a possibly new value to notify, it will call the returned function, which should then call the original function after some delay based on the rules of the custom method. For example, here is a function that implements debounce but also immediately notifies the initial value:
For a computed observable, the rate-limit timer is triggered when one of the computed observable’s dependencies change instead of when its value changes. The computed observable is not re-evaluated until its value is actually needed—after the timeout period when the change notification should happen, or when the computed observable value is accessed directly. If you need to access the value of the computed’s most recent evaluation, you can do so with the peek
method.
When the value of any observable is primitive (a number, string, boolean, or null), the dependents of the observable are by default notified only when it is set to a value that is actually different from before. So, primitive-valued rate-limited observables notify only when their value is actually different at the end of the timeout period. In other words, if a primitive-valued rate-limited observable is changed to a new value and then changed back to the original value before the timeout period ends, no notification will happen.
If you want to ensure that the subscribers are always notified of an update, even if the value is the same, you would use the notify
extender in addition to rateLimit
:
Knockout version 3.4.0 added support for deferred updates, which works similarly to rate-limiting by making notifications and updates asynchronous. But instead of using a timed delay, deferred updates are processed as soon as possible after the current task, before yielding for I/O, reflow, or redrawing. If you are upgrading to 3.4.0 and have code that uses a short rate-limit timeout (e.g., 0 milliseconds), you could modify it to use deferred updates instead:
If you’d like to migrate code from using the deprecated throttle
extender, you should note the following ways that the rateLimit
extender is different from the throttle
extender.
When using rateLimit
:
change
notifications are delayed, including when calling valueHasMutated
manually. This means you can’t use valueHasMutated
to force a rate-limited observable to notify an un-changed value.throttle
algorithm. To match the throttle
behavior, use the notifyWhenChangesStop
method.In most cases, data-bind attributes provide a clean and succinct way to bind to a view model. However, event handling is one area that can often result in verbose data-bind attributes, as anonymous functions were typically the recommended techinique to pass arguments. For example:
This example shows “add” and “remove” links on multiple levels of parents and children with a single handler attached unobtrusively for each type of link.
有时,您可能会找到机会经过附加新功能到Knockout核心值类型来简化代码。您能够在如下任何类型上定义自定义函数:
因为继承,若是您将一个函数附加到ko.subscribable
,它也将在全部其余函数上可用。若是将一个函数附加到ko.observable
,它将被ko.observableArray
继承,但不会被ko.computed
继承。
要附加自定义函数,请将其添加到如下扩展点之一:
ko.subscribable.fn
ko.observable.fn
ko.observableArray.fn
ko.computed.fn
而后,您的自定义函数将对今后之后建立的全部该类型的值可用。
Note: 最好只将此可扩展性点用于真正适用于普遍场景的自定义函数。若是只打算使用一次,则不须要向这些名称空间添加自定义函数。
如下是定义filterByProperty
函数的方法,该函数将在全部后续建立的ko.observableArray
实例中可用:
ko.observableArray.fn.filterByProperty = function(propName, matchValue) { return ko.pureComputed(function() { var allItems = this(), matchingItems = []; for (var i = 0; i < allItems.length; i++) { var current = allItems[i]; if (ko.unwrap(current[propName]) === matchValue) matchingItems.push(current); } return matchingItems; }, this); }
这将返回一个新的计算值,该值提供一个通过筛选的数组视图,同时保持原始数组不变。由于过滤后的数组是计算可监控到的,因此每当底层数组发生更改时,都会从新计算它。
Source code: View
<h3>All tasks (<span data-bind="text: tasks().length"> </span>)</h3> <ul data-bind="foreach: tasks"> <li> <label> <input type="checkbox" data-bind="checked: done" /> <span data-bind="text: title"> </span> </label> </li> </ul> <h3>Done tasks (<span data-bind="text: doneTasks().length"> </span>)</h3> <ul data-bind="foreach: doneTasks"> <li data-bind="text: title"></li> </ul>
Source code: View model
function Task(title, done) { this.title = ko.observable(title); this.done = ko.observable(done); } function AppViewModel() { this.tasks = ko.observableArray([ new Task('Find new desktop background', true), new Task('Put shiny stickers on laptop', false), new Task('Request more reggae music in the office', true) ]); // Here's where we use the custom function this.doneTasks = this.tasks.filterByProperty("done", true); } ko.applyBindings(new AppViewModel());
若是您倾向于大量过滤可观察数组,那么全局地向全部可观察数组添加filterByProperty
可能会使您的代码更整洁。但若是只是偶尔须要过滤,则能够选择不附加到ko.observableArray.fn
,而只是手工构造doneTasks
,以下所示
this.doneTasks = ko.pureComputed(function() { var all = this.tasks(), done = []; for (var i = 0; i < all.length; i++) if (all[i].done()) done.push(all[i]); return done; }, this);