计算属性

如果你已经有了一个监控属性 firstName和lastName,如果你想显示全名该怎么做呢?这个时候你就可以通过计算属性来实现,这个方法依赖于一个或多个监控属性,如果任何依赖对象发生改变他们就会跟着改变。

例如,下面的 view model:

  1. function AppViewModel() {
  2. this.firstName = ko.observable('Bob');
  3. this.lastName = ko.observable('Smith');
  4. }

你可以添加一个计算属性来返回全名,例如:

  1. function AppViewModel() {
  2. // ... leave firstName and lastName unchanged ...
  3. this.fullName = ko.computed(function() {
  4. return this.firstName() + " " + this.lastName();
  5. }, this);
  6. }

下面你就可以将它绑定到UI对象上了,如:

  1. The name is <span data-bind="text: fullName"></span>

当firstName或者lastName 变化,它将会随时更新(当依赖关系发生变化,你的计算函数就会被调用,并且它的值都会更新到UI对象或其他的依赖属性上去)。

管理"this"

初学者不妨可以调过这一节—只要你遵循示例规范你的编码模式,你不需要关系它。

你可能想知道 ko.computed的第二个参数是什么(前面的代码中我们使用到了this),当依赖属性时定义了this的值,没有传递它进去,你不可能得到this.firstName()或者 this.lastName().有经验的JavaScript开发人员觉得this没什么的,但如果你不熟悉JavaScript,你就会觉得这样写很奇怪。(像C#和Java这类语言不需要开发人员给this赋值,但JavaScript需要,因为在默认情况下,它的函数本身并不是任何对象的一部分)。

一种简化的流行惯例

当你需要全程跟踪this 时,下面的写法是一种很流行的惯例:如果你将你的 viewmodel's结构this作为一个变量复制一份(传统称之为self),在以后你可以使用self来代表viewmodel而不必担心它重定义或指别的东西。例如:

  1. function AppViewModel() {
  2. var self = this;
  3. self.firstName = ko.observable('Bob');
  4. self.lastName = ko.observable('Smith');
  5. self.fullName = ko.computed(function() {
  6. return self.firstName() + " " + self.lastName();
  7. });
  8. }

由于self 在闭合的方法内部也是可以捕获到的,所以在任何任何嵌套函数当中,它仍然可用并保持一致性。 如ko.computed求值,当涉及到事件处理时它依然显得很有用。你可以通过在线实例了解更多。

依赖链工作

当然,如果你愿意,你可以创建一个完整的依赖属性链。例如,你可能有:

  • 1、用items 监控属性来代表一组items项
  • 2、另外一个selectedIndexes 监控属性来用户已选择项目的索引
  • 3、一个selectedItems 依赖属性来返回一组用户已选择的Item对象
  • 4、另外一个依赖属性来返回 true 或者false ,来表示selectedItems中是否包含一些属性(如新的或尚未保存的)。一些UI元素,比如一个按钮可以基于此值来控制其启用或禁止。

然后,当改变 items 或者selectedIndexes都会影响到所有的依赖属性链,然后依次更新到绑定了这些项的UI。非常的整洁和优雅。

可写的计算属性

初学者可以调过这一小节,可写的计算属性相对来说比较高级,在大多数情况下也是没有必要的。

正如你上面所学的,计算属性是通过计算其他监控属性而得到的一个值。从这个意义上说,计算属性通常情况下是只读的,你可能会比较惊讶,怎么可能让计算属性变的可写。你仅仅只需要提供一个回调函数来实现值的写入。

然后你可以把这个可写的计算属性当成一个普通的监控属性来使用,通过你自定义的逻辑来实现它的读和写。这个强大的功能可以拓宽我们对KO的使用范围,你可以通过链式语法在一个View Model上传入多个监控属性或者计算属性,例如: myViewModel.fullName('Joe Smith').age(50)。

示例一:分解用户输入

返回到前面经典的“first name + last name = full name” 示例,你可以在返回全名之前,使fullName 计算属性变得可写,所以用户可以直接编辑全名,而程序可以将其输入的值进行解析并映射到底层绑定到firstName 和lastName监控属性上。

  1. function MyViewModel() {
  2. this.firstName = ko.observable('Planet');
  3. this.lastName = ko.observable('Earth');
  4. this.fullName = ko.computed({
  5. read: function () {
  6. return this.firstName() + " " + this.lastName();
  7. },
  8. write: function (value) {
  9. var lastSpacePos = value.lastIndexOf(" ");
  10. if (lastSpacePos > 0) { // Ignore values with no space character
  11. this.firstName(value.substring(0, lastSpacePos)); // Update "firstName"
  12. this.lastName(value.substring(lastSpacePos + 1)); // Update "lastName"
  13. }
  14. },
  15. owner: this
  16. });
  17. }
  18. ko.applyBindings(new MyViewModel());

在这个例子当中,write 回调事件来处理用户输入的值将其分解成“firstName”和“lastName”两个部分,并将这些值返回到底层监控属性上。你可以按照如下的方式将你的view model绑定到你的DOM对象上:

  1. <p>First name: <span data-bind="text: firstName"></span></p>
  2. <p>Last name: <span data-bind="text: lastName"></span></p>
  3. <h2>Hello, <input data-bind="value: fullName"/>!</h2>

这和Hello World示例是完全不同的,因为在这里“firstName”和“lastName”是不可编辑而全名确实可编辑的。
前面的 view model代码只用到了一个参数进行初始化计算属性,你可以点击"computed observable reference"查看完整的可选参数选项。

示例二:值转换

有时你可能需要对底层存储的一个数据进行简单的转换之后显示给用户。例如:你可能需要存储浮点值来表示价格,但想让用户价格单位符号和固定的小数数位都一样。你可以使用一个可写的计算属性来完成价格转换,用户传入一个浮点型值自动映射成想要的格式。

  1. function MyViewModel() {
  2. this.price = ko.observable(25.99);
  3. this.formattedPrice = ko.computed({
  4. read: function () {
  5. return '$' + this.price().toFixed(2);
  6. },
  7. write: function (value) {
  8. // Strip out unwanted characters, parse as float, then write the raw data back to the underlying "price" observable
  9. value = parseFloat(value.replace(/[^\.\d]/g, ""));
  10. this.price(isNaN(value) ? 0 : value); // Write to underlying storage
  11. },
  12. owner: this
  13. });
  14. }
  15. ko.applyBindings(new MyViewModel());

这样就可以简单的将价格值绑定到text文本上

  1. <p>Enter bid price: <input data-bind="value: formattedPrice"/></p>

现在,任何时候用户输入一个新价格,文本框中会立即更新成带有单位符号和固定小数位格式的价格数字,而无论是输入什么格式的值。这是一种很好的用户体验,因为用户能够看到软件能够很好理解他们的输入并将其转换成价格。他们知道他们不能输入两位以上的小数位,如果他们输入,额外的小数位会被立即删掉,同样,他们也不能输入负值,因为write回调方法会忽略任何的减号。

示例三:筛选和验证用户输入

示例一中展示的是写操作过滤的功能,如果你写的值不符合条件的话将不会被写入,忽略所有不包括空格的值。

更进一步,你可以声明一个监控属性isValid 来表示最后一次写入是否合法,然后根据真假值显示相应的提示信息。稍后仔细介绍,先参考如下代码:

  1. function MyViewModel() {
  2. this.acceptedNumericValue = ko.observable(123);
  3. this.lastInputWasValid = ko.observable(true);
  4. this.attemptedValue = ko.computed({
  5. read: this.acceptedNumericValue,
  6. write: function (value) {
  7. if (isNaN(value))
  8. this.lastInputWasValid(false);
  9. else {
  10. this.lastInputWasValid(true);
  11. this.acceptedNumericValue(value); // Write to underlying storage
  12. }
  13. },
  14. owner: this
  15. });
  16. }
  17. ko.applyBindings(new MyViewModel());

按照如下格式绑定DOM元素:

  1. <p>Enter a numeric value: <input data-bind="value: attemptedValue"/></p>
  2. <div data-bind="visible: !lastInputWasValid()">That's not a number!</div>

现在,acceptedNumericValue将只接受数值,而在更新acceptedNumericValue值之前,任何其他输入的值将触发显示验证消息。

备注:上面的例子中,对于输入值进行数字验证显得有些琐碎,这样做显得得不偿失,更简单的做法是在 <input>  元素上使用Jquery验证它是否是数值类型。Knockout 和jQuery 可以很好的在一起工作,可以grid editor参考这个例子。当然,上面的例子依然展示了一个如何使用自定义逻辑进行过滤和验证数据,如果验证很复杂而jQuery Validation又无法很好实现的时候,就可以这样使用它。

依赖跟踪是如何工作的

初学者可以不必知道这一点,但是高级开发人员可以通过这节来了解依赖监控属性可以通过KO自动跟踪并被更新到UI上。

事实上它是很简单的,甚至简单的有点可爱,跟踪算法是这样的:

  • 1、 当你声明一个依赖属性时,KO会立即调用求值算法得到其初始值;
  • 2、 当你的计算函数运行的时候,KO会把监控属性通过计算得到的值都记录在一个Log中;
  • 3、 当你的计算结束的时候,KO会订阅能够访问的监控属性或依赖属性,订阅的回调函数是重新运行你的计算函数,循环整个过程,回到步骤1(并且注销不再使用的订阅);
  • 4、 KO会通知所有的订阅者,你的依赖属性已经被设置了新值。

所以说,KO并不仅仅是在第一次执行计算函数时检测你的依赖项,它每次都会检测。这意味着,你的依赖是可以动态的,举例来说:依赖A能决定你是否也依赖于B或C,这时候只有当A或者你选择的B或者C发生变化时计算函数才能运行。你不需要定义依赖关系:在代码运行时会自动检测到。

另外声明绑定是依赖属性的一种简单优美的实现。所以,一个绑定是读取监控属性的值,这个绑定变成这个监控属性的依赖,当监控属性发生改变的时候,会引起这个绑定被重新计算。

使用peek控制依赖

Knockout的自动依赖跟踪通常不是你想要的,但是你有时可能需要控制那些会更新依赖属性值的监控属性,特别是依赖属性会执行某些操作时,比如一个Ajax请求。peek方法可以帮助你在不需要创建依赖的情况下去控制一个监控属性或者依赖属性。

在下面的例子中,依赖属性通过Ajax方法和其他两个监控属性参数来重新加载一个名为currentPageData 的监控属性。当pageIndex发生变化时,依赖属性会被更新,但会忽略掉selectedItem 的变化,因为它是通过peek方法控制的。在这种情况下,用户可能希望仅仅在数据被加载时才使用selectedItem 的当前值用于追踪。

  1. ko.computed(function() {
  2. var params = {
  3. page: this.pageIndex(),
  4. selected: this.selectedItem.peek()
  5. };
  6. $.getJSON('/Some/Json/Service', params, this.currentPageData);
  7. }, this);

注意:如果你不想一个依赖属性过于频繁的更新,你可以参考throttle扩展

注意:为什么循环依赖是没有意义的

依赖属性是一个虚设的监控属性输入到一个单一的监控属性输出之间的映射。因此,它并不会包含在你的依赖链循环中。循环不类似于递归,它们类似于各自含有计算方法的两个电子表格的单元格。这将导致一个无限的运算循环。

如果你的依赖图当中含有一个循环的话,Knockout是如何处理的呢?可以通过执行下面的规则来避免无限循环:Knockout当它已经运算过它就不会再重新运算。这个不太可能影响你的代码。在下面两种有关情况下:当两个依赖属性互相依赖(可能其中一个或两个都使用了deferEvaluation 选项),或者一个依赖属性写到另外一个含有依赖关系的依赖属性上(无论是直接或间接的通过依赖链)。如果你想使用这些模式并且想完全避免循环依赖,你可以使用peek 方法来实现上述功能。

确定一个属性是依赖属性

在某些情况下,通过编程的方式来处理一个依赖属性是非常有用的方法。Knockout 提供了一个很实用的方法:ko.isComputed。例如,你可能想要从发给服务器的数据中排除依赖属性。

  1. for (var prop in myObject) {
  2. if (myObject.hasOwnProperty(prop) && !ko.isComputed(myObject[prop])) {
  3. result[prop] = myObject[prop];
  4. }
  5. }

此外,Knockout提供了类似的方法用来操作监控属性或者依赖属性:

ko.isObservable-当是observables、observableArrays或者 computed observables时返回true

ko.isWriteableObservable-当是observables、observableArrays或者可写的 computed observables时返回true

依赖属性参考

一个依赖属性可以通过以下方式实现:

1、 ko.computed( evaluator [, targetObject, options] ) 这是用于创建依赖属性的一种最常见的方式。

  • evaluator --用于计算一个依赖属性当值的方法
  • targetObject --如果给定,当KO调用回调函数时,定义一个值表示this。参阅管理”this”来了解更多信息。
  • options --依赖属性的参数对象。可以参看下面详细的清单。

2、 ko.computed( options ) --单一参数方式接受一个JavaScript对象或者以下任意属性来创建一个依赖属性

  • read --必需参数,传入方法。用于运算当前依赖属性当前值的方法。
  • write –可选参数,传入方法。如果给定,将会使依赖属性可写这个方法接收一个外部的值来写入到依赖属性中。它通常是使用你自己定义的逻辑来处理传入的值,通常将值写入到相关的监控属性中。
  • owner –可选参数,传入对象。传入的对象作为this的关键字在KO调用read和write方法使用。
  • deferEvaluation –可选参数,传入ture或者false。如果设置为true,则依赖属性的值直到有实际访问它之前它的值是不会重新计算的。默认情况下,依赖属性的值在创建过程中就已经初始化了。
  • disposeWhen –可选参数,传入方法。如果给出,该传入方法将会在每一次运算结束之后被调用来释放依赖属性。真正的结果就是触发依赖属性的disposal方法。
  • disposeWhenNodeIsRemoved –可选参数,传入方法。如果给出,当指定的DOM元素被KO删除的时候依赖属性的disposal方法会被触发。当元素的绑定被模版或者控制流程绑定方法移除的时候,此功能是用来释放依赖属性。

依赖属性提供了以下方法:

  • dispose()–释放依赖属性,清除所有的依赖订阅。此方法非常有用,当你想停止一个依赖属性以避免其更新或者清除一个内存中的依赖属性而那些存在依赖关系的监控值是不会被清除的。
  • extend(extenders)–用于扩展依赖属性。
  • getDependenciesCount()–返回依赖属性当前依赖关系数量。
  • getSubscriptionsCount()–返回依赖属性当前订阅数量(无论是其他的依赖属性或手动订阅)。
  • isActive ()–返回依赖属性在以后是否会被更新,一个依赖属性如果没有依赖关系是无效的。
  • peek ()–返回当前依赖属性的值而无需创建依赖关系(可以参考peek)。
  • subscribe( callback [,callbackTarget, event] )–注册一个手动订阅来通知依赖属性的变化。

依赖属性发生了什么

在Knockout2.0之前,计算属性被称之为依赖属性,在2.0版本中,我们决定重命名ko.dependentObservable为ko.computed,因为它在读、解释和类型上更简单。但你不用担心:这不会破坏当前所有的代码。在实际使用中,ko.dependentObservable 和 ko.computed 是等价的。

Knockout开发中文API系列3–使用计算属性的更多相关文章

  1. Knockout开发中文API系列4–监控属性数组

    PS:这个翻译系列好久都没有更新了,实在是不应该,一方面是由于时间不多,另一方面也由于自身惰性太大,从今天起接着更新,会在最近的一月内把这个系列中文API文档翻译完整. 如果你想侦测并响应一个对象的变 ...

  2. Knockout开发中文API系列4–绑定关键字

    目的 Visible绑定通过绑定一个值来确定DOM元素显示或隐藏 示例 <div data-bind="visible: shouldShowMessage"> You ...

  3. Knockout开发中文API系列1

    从本节开始介绍关于KnockoutJs相关的内容,本节主要介绍knockoutjs一些重要特性与优点,以及它与Jquery等框架库之间的区别. 1.Knockout.js是什么? Knockout是一 ...

  4. Knockout开发中文API系列2–创建数据模型和监控属性

    Observables,这个词的翻译来自汤姆大叔,对于部分翻译不是很准确的,欢迎大家留言,以得到更好的翻译. Knockout是建立在以下3个核心功能之上的: 1.    属性监控与依赖跟踪 2.   ...

  5. flask开发restful api系列(8)-再谈项目结构

    上一章,我们讲到,怎么用蓝图建造一个好的项目,今天我们继续深入.上一章中,我们所有的接口都写在view.py中,如果几十个,还稍微好管理一点,假如上百个,上千个,怎么找?所有接口堆在一起就显得杂乱无章 ...

  6. flask开发restful api系列(1)

    在此之前,向大家说明的是,我们整个框架用的是flask + sqlalchemy + redis.如果没有开发过web,还是先去学习一下,这边只是介绍如果从开发web转换到开发移动端.如果flask还 ...

  7. flask开发restful api系列(7)-蓝图与项目结构

    如果有几个原因可以让你爱上flask这个极其灵活的库,我想蓝图绝对应该算上一个,部署蓝图以后,你会发现整个程序结构非常清晰,模块之间相互不影响.蓝图对restful api的最明显效果就是版本控制:而 ...

  8. flask开发restful api系列(6)-配置文件

    任何一个好的程序,配置文件必不可少,而且非常重要.配置文件里存储了连接数据库,redis的用户密码,不允许有任何闪失.要有灵活性,用户可以自己配置:生产环境和开发环境要分开,最好能简单的修改一个东西, ...

  9. flask开发restful api系列(5)-短信验证码

    我们现在开发app,注册用户的时候,不再像web一样,发送到个人邮箱了,毕竟个人邮箱在移动端填写验证都很麻烦,一般都采用短信验证码的方式.今天我们就讲讲这方面的内容. 首先,先找一个平台吧.我们公司找 ...

随机推荐

  1. windows(64位)下使用curl命令

    Curl命令可以通过命令行的方式,执行Http请求.在Elasticsearch中有使用的场景,因此这里研究下如何在windows下执行curl命令. 工具下载 在官网处下载工具包:http://cu ...

  2. shell脚本监控Linux系统的登录情况

    一.登录日志记录 vi /etc/profile 在最后面添加一行: echo "`who`" > /var/log/login.log 二.监控日志文件 #!/bin/ba ...

  3. XGB 调参基本方法

    - xgboost 基本方法和默认参数 - 实战经验中调参方法 - 基于实例具体分析 在训练过程中主要用到两个方法:xgboost.train()和xgboost.cv(). xgboost.trai ...

  4. Ubuntu18.04 修改DNS

    Ubuntu18.04 修改DNS sudo vim /etc/systemd/resolved.conf 修改如下: [Resolve] DNS=119.29.29.29 保存退出后 systemc ...

  5. 设置Myeclipse中的代码格式化、注释模板及保存时自动格式化

    1:设置注释的模板: 下载此模板:  codetemplates.xml 搜索Dangzhang,将其改为你自己的姓名,保存 打开eclipse/myeclipse选择 window-->Pre ...

  6. LogStash如何通过jdbc 从mysql导入elasticsearch

    input { stdin { } jdbc { # mysql jdbc connection string to our backup databse jdbc_connection_string ...

  7. JS两日期相减

    JS两日期相减,主要用到下面两个方法 dateObject.setFullYear(year,month,day) 方法 stringObject.split(separator) 方法 functi ...

  8. JDK1.5新特性,基础类库篇,System类

    一. 背景 System.getenv(String)方法继续有效:增加了新的System.getenv()方法,返回保存环境变量的Map<String,String>. 同时增加了以纳秒 ...

  9. Lua中and、or的一些特殊使用方法

    Lua中的逻辑运算符:与(and).或(or)和非(not),与其它语言的逻辑运算符功能一致,这里不做赘述.仅仅说一点,全部的逻辑运算符将false和nil视为假,其它不论什么东西视为真.0也视为真. ...

  10. ssm redis 数据字典在J2EE中的多种应用与实现

    数据字典在项目中是不可缺少的“基础设施”,关于数据字典如何设计如何实现,今天抽空讲一下吧 先看一下表设计: 通过自定义标签来实现页面的渲染: public class DataDictValueTag ...