Computed Observables
如果你有監控屬性firstName和lastName的話,此時如果你想要顯示全名?
這個時候computed(以前叫做依賴)監控屬性就出馬了,這是一個函數用來依賴一個或者多個監控屬性,並且當其中的任何一個依賴對象被改變的時候都將會自動更新。
例如,view model類
function AppViewModel() { this.firstName = ko.observable('Bob'); this.lastName = ko.observable('Smith'); }
你可以增加一個computed計算依賴的來得到一個全名
function AppViewModel() { // ... leave firstName and lastName unchanged ... this.fullName = ko.computed(function() { return this.firstName() + " " + this.lastName(); }, this); }
現在你可以綁定到它的UI元素,e.g
The name is <span data-bind="text: fullName"></span>
當firstName或
lastName發生改變它都會更新(不管誰改變,執行函數都會調用一次,不管改變成什么,他的值都會更新到UI或者其他依賴監控屬性上)
管理this
初學者跳過,你只需要安裝上面例子中的代碼模式寫就行了,無需知道/關注這個this。
ko.computed的第二個參數是什么,你是否很疑惑?
在前面的代碼,我們在定義computed依賴的時候用到了this,沒有它,將不能夠引用到
this.firstName()
或 this.lastName()。
老練的Javascript程序員就覺得很平常,但是假如不怎么了解Javascript就會覺得難以理解(如C#和Java程序員不需要設置此值,但JavaScript呢,作用域是可以被改變的)
A popular convention that simplifies things
可以用一個簡單的辦法去簡化這種行為
這是一種比較流行的辦法用於避免追蹤this:
如果你的模型的構造函數復制一個引用this到一個不同的變量(通常稱為self),然后你可以用self的在你的模型和不必擔心它被重新定義指的是別的東西。
比如說
function AppViewModel() { var self = this; self.firstName = ko.observable('Bob'); self.lastName = ko.observable('Smith'); self.fullName = ko.computed(function() { return self.firstName() + " " + self.lastName(); }); }
因為self是在函數的閉包中被捕獲,在任何嵌套函數仍然是同一個,例如ko.computed的evaluator,當你設計到事件句柄的時候這個技巧更有用,可以看看更多的例子live examples.
Dependency chains just work
依賴鏈的工作
當然,你希望你能創建一個計算監控屬性鏈,例如,你可以這樣
- 監控屬性items表述一組列表項
- 監控屬性selectedIndexes保存着被用戶選上的列表項的索引
- 依賴監控屬性selectedItems 返回的是selectedIndexes 對應的列表項數組
- 另一個依賴監控屬性返回的true或false依賴於 selectedItems 的各個列表項是否包含一些屬性(例如,是否新的或者還未保存的)。一些UI element(像按鈕的啟用/禁用)的狀態取決於這個值)。
- 然后,items或者selectedIndexes 的改變將會影響到所有依賴監控屬性的鏈,所有綁定這些屬性的UI元素都會自動更新。多么整齊與優雅!
可寫的計算監控屬性
初學者跳過,可寫的computed observables是比較高級的了,在大多情況下都是用不到的
當你學到了,計算監控屬性的值是通過計算其他監控屬性得到,在感覺上計算監控屬性正常情況下僅僅是只讀的。
這樣看起來很奇怪,那么,computed observables是否可以支持可寫呢?
你只需要提供自己的回調函數做一些事情,在寫值的時候。
Example 1: Decomposing user input
分解用戶的輸入
返回到經典的 “first name + last name = full name” 實例,你可以把事情返過來看:
用fullName給計算監控屬性寫入寫東西,所以你能直接編輯出全名,讓用戶直接輸入姓名全稱,然后輸入的值將被解析並映射寫入到基本的監控屬性firstName和lastName上:
function MyViewModel() { this.firstName = ko.observable('Planet'); this.lastName = ko.observable('Earth'); this.fullName = ko.computed({ read: function () { return this.firstName() + " " + this.lastName(); }, write: function (value) { var lastSpacePos = value.lastIndexOf(" "); if (lastSpacePos > 0) { // Ignore values with no space character this.firstName(value.substring(0, lastSpacePos)); // Update "firstName" this.lastName(value.substring(lastSpacePos + 1)); // Update "lastName" } }, owner: this }); } ko.applyBindings(new MyViewModel());
在這里例子,write的回調句柄傳入了一個值被分解后傳入到“firstName” 和“lastName” 上,並且寫的那些值會返回給相關的監控屬性
跟平常一樣將這個view model綁定到DOM元素上,如下:
<p>First name: <span data-bind="text: firstName"></span></p> <p>Last name: <span data-bind="text: lastName"></span></p> <h2>Hello, <input data-bind="value: fullName"/>!</h2>
這是一個Hello World 例子的反例子,姓和名都不可編輯,相反姓和名組成的姓名全稱卻是可編輯的。
之前的視圖模型的代碼演示單個參數的語法是為了初始化計算監控屬性,看computed observable reference 以下全部可用選項的列表
Example 2: A value converter
轉化一個value
有時候你可能需要顯示一些不同格式的數據,從基礎的數據轉化成顯示格式。
比如,你存儲價格為float類型,但是允許用戶編輯的字段需要支持貨幣單位和小數點。
你可以用可寫的依賴監控屬性來實現,然后解析傳入的數據到基本 float類型里:
function MyViewModel() { this.price = ko.observable(25.99); this.formattedPrice = ko.computed({ read: function () { return '$' + this.price().toFixed(2); }, write: function (value) { // Strip out unwanted characters, parse as float, then write the raw data back to the underlying "price" observable value = parseFloat(value.replace(/[^\.\d]/g, "")); this.price(isNaN(value) ? 0 : value); // Write to underlying storage }, owner: this }); } ko.applyBindings(new MyViewModel());
然后我們綁定formattedPrice到text box上:
<p>Enter bid price: <input data-bind="value: formattedPrice"/></p>
所以,不管用戶什么時候輸入新價格,輸入什么格式,text box里會自動更新為帶有2位小數點和貨幣符號的數值。
這樣用戶可以看到你的程序有多聰明,來告訴用戶只能輸入2位小數,否則的話自動刪除多余的位數,當然也不能輸入負數,因為write的callback函數會自動刪除負號。
Example 3: Filtering and validating user input
過濾並驗證用戶輸入
例1展示的是寫操作過濾的功能,如果你寫的值不符合條件的話將不會被寫入,忽略所有不包括空格的值。
再多走一步,你可以聲明一個監控屬性isValid 來表示最后一次寫入是否合法,然后根據真假值顯示相應的提示信息。
稍后仔細介紹,先參考如下代碼:
function MyViewModel() { this.acceptedNumericValue = ko.observable(123); this.lastInputWasValid = ko.observable(true); this.attemptedValue = ko.computed({ read: this.acceptedNumericValue, write: function (value) { if (isNaN(value)) this.lastInputWasValid(false); else { this.lastInputWasValid(true); this.acceptedNumericValue(value); // Write to underlying storage } }, owner: this }); } ko.applyBindings(new MyViewModel());
… 按照如下格式聲明綁定元素:
<p>Enter a numeric value: <input data-bind="value: attemptedValue"/></p> <div data-bind="visible: !lastInputWasValid()">That's not a number!</div>
現在,acceptedNumericValue 將只接受數字,其它任何輸入的值都會觸發顯示驗證信息,而會更新acceptedNumericValue。
備注:上面的例子顯得殺傷力太強了,更簡單的方式是在<input>上使用jQuery Validation和number class。
Knockout可以和jQuery Validation一起很好的使用,參考例子:grid editor 。
當然,上面的例子依然展示了一個如何使用自定義邏輯進行過濾和驗證數據,如果驗證很復雜而jQuery Validation很難使用的話,你就可以用它。
How dependency tracking works
依賴跟蹤如何工作的
初學者可以跳過,但是高級開發人員可以需要知道為什么依賴監控屬性能夠自動跟蹤並且自動更新UI…
其實很簡單而且可愛的,這個跟蹤的算法是這樣的:
- 當你聲明一個依賴監控屬性的時候,KO會立即調用執行函數並且獲取初始化值。
- 當你的執行函數運行的時候,KO會把任何在監控屬性(或者計算監控屬性))讀到的值都會都記錄到一個Log列表里。
- 執行函數結束以后,KO會向所有Log里需要依賴到的對象進行訂閱。訂閱的callback函數是重新運行你的執行函數。然后回頭重新執行上面的第一步操作(並且注銷不再使用的訂閱)。
- 最后KO會通知所有訂閱它的訂閱者,告訴它們我已經設置了新值。
所有說,KO不僅僅是在第一次執行函數執行時候探測你的依賴項,每次它都會探測。舉例來說,你的依賴屬性可以是動態的:依賴屬性A代表你是否依賴於依賴屬性B或者C,這時候只有當A或者你當前的選擇B或者C改變的時候執行函數才重新執行。你不需要再聲明其它的依賴:運行時會自動探測到的。
另外一個技巧是:一個模板輸出的綁定是依賴監控屬性的簡單實現,如果模板讀取一個監控屬性的值,那模板綁定就會自動變成依賴監控屬性依賴於那個監控屬性,監控屬性一旦改變,模板綁定的依賴監控屬性就會自動執行。
Controlling dependencies using peek
使用Peek控制依賴
Knockout’s自動跟蹤依賴通常下是你想要的。但是你可能有時候需要控制某一個監控屬性去更新你的計算依賴屬性,特別是如果你的計算依賴可執行一些操作,
比如Ajax請求,那么peek函數就能夠讓你訪問一個observable或者computed observable而不是創建一個依賴
在下面的例子,一個
ko.computed(function() { var params = { page: this.pageIndex(), selected: this.selectedItem.peek() }; $.getJSON('/Some/Json/Service', params, this.currentPageData); }, this);
注釋:加入你不想要依賴屬性太頻繁的更新,你可以看 throttle extender.
Determining if a property is a computed observable
假如有一個屬性是依賴屬性
在一些場景中,如果你是處理一個依賴屬性它是有用的編程方式,Knockout提供一個應用函數
ko.isComputed 將會幫助你解決這些情況
例如,數據從服務器返回回來,你可以要排除依賴屬性
for (var prop in myObject) { if (myObject.hasOwnProperty(prop) && !ko.isComputed(myObject[prop])) { result[prop] = myObject[prop]; } }
此外,Knockout 提供了類似的功能,能夠對監控屬性和依賴屬性起到作用
- ko.isObservable:返回true的,監控屬性,監控數組和所有的依賴屬性
- ko.isWriteableObservable:返回true, 監控屬性,監控數組,和 可寫的依賴屬性
Computed Observable Reference
引用依賴屬性
一個依賴屬性可以有下面的形式構造出來:
- ko.computed( evaluator [, targetObject, options] ) 最常見的情況,這種形式的支持創建一個依賴屬性
evaluator — 一個函數,用來求出依賴屬性當前的值
targetObject — 就是回調函數中,引用當前的this,要指定作用域,詳情看managing
this
options
— 為依賴屬性的配置更多的屬性- ko.computed( options ) 創建一個依賴屬性,傳入的是一個單個對象:
read
— 必選,一個用來執行取得依賴監控屬性當前值的函數。write — 可選,如果聲明的依賴屬性是可寫的,那么這個函數接受一個值,那么其他代碼將會試着寫入到依賴屬性,過自定義邏輯將值再寫入各個基礎的監控屬性上。
owner
— 可選,如果聲明,它就是KO調用read或write的callback時用到的this。deferEvaluation
— 可選,假如是true,那么依賴屬性的值你不能獲取,默認情況下,依賴屬性獲取這個值的話會立刻創建disposeWhen
— 可選,待翻譯,等分析源碼的時候補上disposeWhenNodeIsRemoved — 可選,待翻譯,等分析源碼的時候補上
依賴屬性可提供以下功能
dispose()
— 手動配置依賴屬性,清除所有訂閱依賴,如果你想要停止依賴屬性,當正在更新或者想要清除依賴屬性的內存extend(extenders)
— 給依賴屬性擴展一些內容getDependenciesCount()
— 返回當前被依賴屬性依賴的數量getSubscriptionsCount()
— 返回當前依賴屬性的訂閱數量(或者從其他計算機的依賴屬性或手動訂閱)isActive()
— 返回依賴屬性支持更新,如果沒有依賴關系,將是無效的peek()
— 返回當前沒有創建依賴關系的值(看peek
)subscribe( callback [,callbackTarget, event] )
— 手工注冊依賴通知
有些地方比較拗口,等看完源碼后就能補准翻譯了~~