Knockout开发中文API系列3–使用计算属性

本文介绍了如何利用Knockout.js的计算属性功能,结合现有的firstName和lastName属性,动态生成全名。通过创建计算属性fullName,实现在用户编辑firstName和lastName时,全名能够实时更新到页面上,提升用户体验。文章还详细阐述了管理this、可写计算属性、依赖跟踪以及依赖属性的使用技巧,包括如何避免循环依赖,以及如何控制依赖更新的时机。同时,文章还展示了如何使用peek方法来控制特定监控属性的依赖,以及如何通过配置选项定制计算属性的行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

计算属性

如果你已经有了一个监控属性 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.   
  4.     this.fullName = ko.computed(function() {   
  5.         return this.firstName() + " " + this.lastName();   
  6.     }, this);   
  7. }  

下面你就可以将它绑定到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.   
  4.     self.firstName = ko.observable('Bob');   
  5.     self.lastName = ko.observable('Smith');   
  6.     self.fullName = ko.computed(function() {   
  7.         return self.firstName() + " " + self.lastName();   
  8.     });   
  9. }  

由于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监控属性上。

Code
  1. function MyViewModel() {   
  2.     this.firstName = ko.observable('Planet');   
  3.     this.lastName = ko.observable('Earth');   
  4.   
  5.     this.fullName = ko.computed({   
  6.         read: function () {   
  7.             return this.firstName() + " " + this.lastName();   
  8.         },   
  9.         write: function (value) {   
  10.             var lastSpacePos = value.lastIndexOf(" ");   
  11.             if (lastSpacePos > 0) { // Ignore values with no space character   
  12.                 this.firstName(value.substring(0, lastSpacePos)); // Update "firstName"   
  13.                 this.lastName(value.substring(lastSpacePos + 1)); // Update "lastName"   
  14.             }   
  15.         },   
  16.         owner: this  
  17.     });   
  18. }   
  19.   
  20. 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"查看完整的可选参数选项。

示例二:值转换

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

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

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

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

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

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

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

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

Code
  1. function MyViewModel() {   
  2.     this.acceptedNumericValue = ko.observable(123);   
  3.     this.lastInputWasValid = ko.observable(true);   
  4.   
  5.     this.attemptedValue = ko.computed({   
  6.         read: this.acceptedNumericValue,   
  7.         write: function (value) {   
  8.             if (isNaN(value))   
  9.                 this.lastInputWasValid(false);   
  10.             else {   
  11.                 this.lastInputWasValid(true);   
  12.                 this.acceptedNumericValue(value); // Write to underlying storage   
  13.             }   
  14.         },   
  15.         owner: this  
  16.     });   
  17. }   
  18.   
  19. 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 的当前值用于追踪。

Code
  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。例如,你可能想要从发给服务器的数据中排除依赖属性。

Code
  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是一个以数据模型(data model)为基础的能够帮助你创建富文本,响应显示和编辑用户界面的JavaScript类库。任何时候如果你的UI需要自动更新(比如:更新依赖于用户的行为或者外部数据源的改变),KO能够很简单的帮你实现并且很容易维护。 重要特性: 优雅的依赖追踪 - 不管任何时候你的数据模型更新,都会自动更新相应的内容。 Elegant dependency tracking - automatically updates the right parts of your UI whenever your data model changes. 声明式绑定 - 浅显易懂的方式将你的用户界面指定部分关联到你的数据模型上。 Declarative bindings - a simple and obvious way to connect parts of your UI to your data model. You can construct a complex dynamic UIs easily using arbitrarily nested binding contexts. 轻易可扩展 - 几行代码就可以实现自定义行为作为新的声明式绑定。 Trivially extensible - implement custom behaviors as new declarative bindings for easy reuse in just a few lines of code. 额外的好处: 纯JavaScript类库 – 兼容任何服务器端和客户端技术 Pure JavaScript library - works with any server or client-side technology 可添加到Web程序最上部 – 不需要大的架构改变 Can be added on top of your existing web application - without requiring major architectural changes 简洁的 - Gzip之前大约25kb Compact - around 13kb after gzipping 兼容任何主流浏览器 - (IE 6+、Firefox 2+、Chrome、Safari、其它) Works on any mainstream browser - (IE 6+, Firefox 2+, Chrome, Safari, others) 采用行为驱动开发 - 意味着在新的浏览器和平台上可以很容易通过验证。 Comprehensive suite of specifications - (developed BDD-style) means its correct functioning can easily be verified on new browsers and platforms 开发人员如果用过Silverlight或者WPF可能会知道KO是MVVM模式的一个例子。如果熟悉 Ruby on Rails 或其它MVC技术可能会发现它是一个带有声明式语法的MVC实时form。换句话说,你可以把KO当成通过编辑JSON数据来制作UI用户界面的一种方式… 不管它为你做什么 Developers familiar with Ruby on Rails, ASP.NET MVC, or other MV* technologies may see MVVM as a real-time form of MVC with declarative syntax. In another sense, you can think of KO as a general way to make UIs for editing JSON data… whatever works for you :) OK, 如何使用它? 简单来说:声明你的数据作为一个JavaScript 模型对象(model object),然后将DOM 元素或者模板(templates)绑定到它上面. The quickest and most fun way to get started is by working through the interactive tutorials. Once you’ve got to grips with the basics, explore the live examples and then have a go with it in your own project. KO和jQuery (或Prototype等)是竞争关系还是能一起使用? 所有人都喜欢jQuery! 它是一个在页面里操作元素和事件的框架,非常出色并且易使用,在DOM操作上肯定使用jQuery,KO解决不同的问题。 Everyone loves jQuery! It’s an outstanding replacement for the clunky, inconsistent DOM API we had to put up with in the past. jQuery is an excellent low-level way to manipulate elements and event handlers in a web page. I certainly still use jQuery for low-level DOM manipulation. KO solves a different problem. 如果页面要求复杂,仅仅使用jQuery需要花费更多的代码。 例如:一个表格里显示一个列表,然后统计列表的数量,Add按钮在数据行TR小于5调的时候启用,否则就禁用。jQuery 没有基本的数据模型的概念,所以需要获取数据的数量(从table/div或者专门定义的CSS class),如果需要在某些SPAN里显示数据的数量,当添加新数据的时候,你还要记得更新这个SPAN的text。当然,你还要判断当总数>=5条的时候禁用Add按钮。 然后,如果还要实现Delete功能的时候,你不得不指出哪一个DOM元素被点击以后需要改变。 As soon as your UI gets nontrivial and has a few overlapping behaviors, things can get tricky and expensive to maintain if you only use jQuery. Consider an example: you’re displaying a list of items, stating the number of items in that list, and want to enable an ‘Add’ button only when there are fewer than 5 items. jQuery doesn’t have a concept of an underlying data model, so to get the number of items you have to infer it from the number of TRs in a table or the number of DIVs with a certain CSS class. Maybe the number of items is displayed in some SPAN, and you have to remember to update that SPAN’s text when the user adds an item. You also must remember to disable the ‘Add’ button when the number of TRs is 5. Later, you’re asked also to implement a ‘Delete’ button and you have to figure out which DOM elements to change whenever it’s clicked. Knockout的实现有何不同? 使用KO非常简单。将你的数据描绘成一个JavaScript数组对象myItems,然后使用模板(template)转化这个数组到表格里(或者一组DIV)。不管什么时候数组改变, UI界面也会响应改变(不用指出如何插入新行或在哪里插入),剩余的工作就是同步了。例如:你可以声明绑定如下一个SPAN显示数据数量(可以放在页面的任何地方,不一定非要在template里): It’s much easier with KO. It lets you scale up in complexity without fear of introducing inconsistencies. Just represent your items as a JavaScript array, and then use a foreach binding to transform this array into a TABLE or set of DIVs. Whenever the array changes, the UI changes to match (you don’t have to figure out how to inject new TRs or where to inject them). The rest of the UI stays in sync. For example, you can declaratively bind a SPAN to display the number of items as follows: There are <span data-bind="text: myItems().count"></span> items就是这些!你不需要写代码去更新它,它的更新依赖于数组myItems的改变。同样, Add按钮的启用和禁用依赖于数组 myItems 的长度,如下: That’s it! You don’t have to write code to update it; it updates on its own when the myItems array changes. Similarly, to make the ‘Add’ button enable or disable depending on the number of items, just write: <button data-bind="enable: myItems().count < 5">Add</button>之后,如果你要实现Delete功能,不必指出如何操作UI元素,只需要修改数据模型就可以了。 Later, when you’re asked to implement the ‘Delete’ functionality, you don’t have to figure out what bits of the UI it has to interact with; you just make it alter the underlying data model. 总结:KO没有和jQuery或类似的DOM 操作API对抗竞争。KO提供了一个关联数据模型和用户界面的高级功能。KO本身不依赖jQuery,但是你可以一起同时使用jQuery, 生动平缓的UI改变需要真正使用jQuery。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值