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


計算屬性

如果你已經有了一個監控屬性 firstNamelastName,如果你想顯示全名該怎么做呢?這個時候你就可以通過計算屬性來實現,這個方法依賴於一個或多個監控屬性,如果任何依賴對象發生改變他們就會跟着改變。

例如,下面的 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調用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 是等價的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM