Knockout開發中文API系列3–使用計算屬性

計算屬性

若是你已經有了一個監控屬性 firstNamelastName,若是你想顯示全名該怎麼作呢?這個時候你就能夠經過計算屬性來實現,這個方法依賴於一個或多個監控屬性,若是任何依賴對象發生改變他們就會跟着改變。html

例如,下面的 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對象或其餘的依賴屬性上去)。app

管理"this"

初學者不妨能夠調過這一節—只要你遵循示例規範你的編碼模式,你不須要關係它。函數

你可能想知道 ko.computed的第二個參數是什麼(前面的代碼中咱們使用到了this),當依賴屬性時定義了this的值,沒有傳遞它進去,你不可能獲得this.firstName()或者 this.lastName().有經驗的JavaScript開發人員以爲this沒什麼的,但若是你不熟悉JavaScript,你就會以爲這樣寫很奇怪。(像C#和Java這類語言不須要開發人員給this賦值,但JavaScript須要,由於在默認狀況下,它的函數自己並非任何對象的一部分)。this

一種簡化的流行慣例編碼

當你須要全程跟蹤this 時,下面的寫法是一種很流行的慣例:若是你將你的 viewmodel's結構this做爲一個變量複製一份(傳統稱之爲self),在之後你可使用self來表明viewmodel而沒必要擔憂它重定義或指別的東西。例如:lua

 
  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求值,當涉及到事件處理時它依然顯得頗有用。你能夠經過在線實例瞭解更多。spa

依賴鏈工做

固然,若是你願意,你能夠建立一個完整的依賴屬性鏈。例如,你可能有:

  • 一、用items 監控屬性來表明一組items項
  • 二、另一個selectedIndexes 監控屬性來用戶已選擇項目的索引
  • 三、一個selectedItems 依賴屬性來返回一組用戶已選擇的Item對象
  • 四、另一個依賴屬性來返回 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上。

事實上它是很簡單的,甚至簡單的有點可愛,跟蹤算法是這樣的:

  • 一、 當你聲明一個依賴屬性時,KO會當即調用求值算法獲得其初始值;
  • 二、 當你的計算函數運行的時候,KO會把監控屬性經過計算獲得的值都記錄在一個Log中;
  • 三、 當你的計算結束的時候,KO會訂閱可以訪問的監控屬性或依賴屬性,訂閱的回調函數是從新運行你的計算函數,循環整個過程,回到步驟1(而且註銷再也不使用的訂閱);
  • 四、 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

依賴屬性參考

一個依賴屬性能夠經過如下方式實現:

一、 ko.computed( evaluator [, targetObject, options] ) 這是用於建立依賴屬性的一種最多見的方式。

  • evaluator --用於計算一個依賴屬性當值的方法
  • targetObject --若是給定,當KO調用回調函數時,定義一個值表示this。參閱管理」this」來了解更多信息。
  • options --依賴屬性的參數對象。能夠參看下面詳細的清單。

二、 ko.computed( options ) --單一參數方式接受一個JavaScript對象或者如下任意屬性來建立一個依賴屬性

  • read --必需參數,傳入方法。用於運算當前依賴屬性當前值的方法。
  • write –可選參數,傳入方法。若是給定,將會使依賴屬性可寫這個方法接收一個外部的值來寫入到依賴屬性中。它一般是使用你本身定義的邏輯來處理傳入的值,一般將值寫入到相關的監控屬性中。
  • owner –可選參數,傳入對象。傳入的對象做爲this的關鍵字在KO調用readwrite方法使用。
  • 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.dependentObservableko.computed,由於它在讀、解釋和類型上更簡單。但你不用擔憂:這不會破壞當前全部的代碼。在實際使用中,ko.dependentObservable 和 ko.computed 是等價的。

相關文章
相關標籤/搜索