Knockout應用開發指南 第二章:監控屬性(Observables)

關於Knockout的3個重要概念(Observables,DependentObservables,ObservableArray),本人沒法準確表達它的準確含義,因此暫定翻譯爲(監控屬性、依賴監控屬性和監控數組),若是有好的建議請指正,多謝。html

1     建立帶有監控屬性的view model

Observables數據庫

Knockout是在下面三個核心功能是創建起來的:設計模式

  1. 監控屬性(Observables)和依賴跟蹤(Dependency tracking)
  2. 聲明式綁定(Declarative bindings)
  3. 模板(Templating)

 這一節,你講學到3個功能中的第一個。 在這以前, 咱們來解釋一下MVVM模式和view model的概念。數組

 MVVM and View Models瀏覽器

Model-View-View Model (MVVM) 是一種建立用戶界面的設計模式。 描述的是如何將複雜的UI用戶界面分紅3個部分:服務器

  •  model: 你程序裏存儲的數據。這個數據包括對象和業務操做(例如:銀子帳戶能夠完成轉帳功能), 而且獨立於任何UI。使用KO的時候,一般說是向服務器調用Ajax讀寫這個存儲的模型數據。
  •  view model: 在UI上,純code描述的數據以及操做。例如,若是你實現列表編輯,你的view model應該是一個包含列表項items的對象和暴露的add/remove列表項(item)的操做方法。

    注意這不是UI自己:它不包含任何按鈕的概念或者顯示風格。它也不是持續數據模型 – 包含用戶正在使用的未保存數據。使用KO的時候,你的view models是不包含任何HTML知識的純JavaScript 對象。保持view model抽象能夠保持簡單,以便你能管理更復雜的行爲。app

  •  view: 一個可見的,交互式的,表示view model狀態的UI。 從view model顯示數據,發送命令到view model(例如:當用戶click按鈕的時候) ,任何view model狀態改變的時候更新。

使用KO的時候,你的view就是你帶有綁定信息的HTML文檔,這些聲明式的綁定管理到你的view model上。或者你可使用模板從你的view model獲取數據生成HTML。框架

建立一個view model,只須要聲明任意的JavaScript object。例如:函數

var myViewModel = {
personName: 'Bob',
personAge: 123
};

你能夠爲view model建立一個聲明式綁定的簡單view。例如:下面的代碼顯示personName 值:性能

The name is <span data-bind="text: personName"></span>

 

Activating Knockout

data-bind屬性儘快好用但它不是HTML的原生屬性(它嚴格聽從HTML5語法, 雖然HTML4驗證器提示有不可識別的屬性但依然可用)。因爲瀏覽器不識別它是什麼意思,因此你須要激活Knockout 來讓他起做用。

激活Knockout,須要添加以下的 <script> 代碼塊:

ko.applyBindings(myViewModel);

你能夠將這個代碼塊放在HTML底部,或者放在jQuery的$函數或者ready 函數裏,而後放在頁面上面, 最終生成結果就是以下的HTML代碼:

The name is <span>Bob</span>

你可能奇怪ko.applyBindings使用的是什麼樣的參數,

  •  第一個參數是你想用於聲明式綁定
  • 第二個參數(可選),能夠聲明成使用data-bind的HTML元素或者容器。例如,ko.applyBindings(myViewModel, document.getElementById('someElementId'))。它的如今是隻有做爲someElementId 的元素和子元素才能激活KO功能。 好處是你能夠在同一個頁面聲明多個view model,用來區分區域。

 

Observables

如今已經知道如何建立一個簡單的view model而且經過binding顯示它的屬性了。可是KO一個重要的功能是當你的view model改變的時候能自動更新你的界面。當你的view model部分改變的時候KO是如何知道的呢?答案是:你須要將你的model屬性聲明成observable的, 由於它是很是特殊的JavaScript objects,可以通知訂閱者它的改變以及自動探測到相關的依賴。

 

例如:將上述例子的view model改爲以下代碼:

var myViewModel = {
personName: ko.observable('Bob'),
personAge: ko.observable(123)
};

你根本不須要修改view – 全部的data-bind語法依然工做,不一樣的是他能監控到變化,當值改變時,view會自動更新。

 

監控屬性(observables)的讀和寫

不是全部的瀏覽器都支持JavaScript的 getters and setters (好比IE),,因此爲了兼容性,使用ko.observable監控的對象都是真實的function函數。

  •  讀取監控屬性(observable)的值,只須要直接調用監控屬性(observable)(不須要參數),例如myViewModel.personName() 將返回'Bob', myViewModel.personAge() 將返回 123。
  • 寫一個新值到監控屬性(observable)上,調用這個observable屬性並當新值做爲參數。例如:調用myViewModel.personName('Mary') 將更新name值爲'Mary'。
  • 給一個model對象的多個屬性寫入新值,你可使用鏈式語法。例如:myViewModel.personName('Mary').personAge(50) 將會將name更新爲 'Mary' 而且 將age更新爲 50.

監控屬性(observables)的特徵就是監控(observed),例如其它代碼能夠說我須要獲得對象變化的通知,因此KO內部有不少內置的綁定語法。因此若是你的代碼寫成data-bind="text: personName", text綁定註冊到自身,一旦personName的值改變,它就能獲得通知。

固然調用myViewModel.personName('Mary')改變name的值,text綁定將自動更新這個新值到相應的DOM元素上。這就是如何將view model的改變傳播到view上的。

 

監控屬性(Observables)的顯式訂閱

一般狀況下,你不用手工訂閱,因此新手能夠忽略此小節。高級用戶,若是你要註冊本身的訂閱到監控屬性(observables),你能夠調用它的subscribe 函數。例如:

myViewModel.personName.subscribe(function (newValue) {
alert("The person's new name is " + newValue);
});

這個subscribe 函數在內部不少地方都用到的。你也能夠終止本身的訂閱:首先獲得你的訂閱,而後調用這個對象的dispose函數,例如:

var subscription = myViewModel.personName.subscribe(function (newValue) { /* do stuff */ });
// ...then later...
subscription.dispose(); // I no longer want notifications

大多數狀況下,你不須要作這些,由於內置的綁定和模板系統已經幫你作好不少事情了,能夠直接使用它們。

 

2     使用依賴監控屬性(Dependent Observables)

若是你已經有了監控屬性firstName和lastName,你想顯示全稱怎麼辦? 這就須要用到依賴監控屬性了 – 這些函數是一個或多個監控屬性, 若是他們的依賴對象改變,他們會自動跟着改變。

例如,下面的view model,

var viewModel = {
firstName: ko.observable('Bob'),
lastName: ko.observable('Smith')
};

… 你能夠添加一個依賴監控屬性來返回姓名全稱:

viewModel.fullName = ko.dependentObservable(function () {
return this.firstName() + " " + this.lastName();
}, viewModel);

而且綁定到UI的元素上,例如:

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

… 無論firstName仍是lastName改變,全稱fullName都會自動更新(無論誰改變,執行函數都會調用一次,無論改變成什麼,他的值都會更新到UI或者其餘依賴監控屬性上)。

 

管理‘this’

新手可忽略此小節,你只須要安裝上面例子中的代碼模式寫就好了,無需知道/關注這個this。

你可能疑惑ko.dependentObservable的第二個參數是作什麼用的(上面的例子中我傳的是viewModel), 它是聲明執行依賴監控屬性的this用的。 沒有它,你不能引用到this.firstName() 和this.lastName()。 老練的JavaScript 開發人員不以爲this怎麼樣,可是若是你不熟悉JavaScript,那就對它就會很陌生。(C#和Java須要不須要爲set一個值爲設置this,可是JavaScript 須要,由於默認狀況下他們的函數自身不是任何對象的一部分)。

 

不幸的是, JavaScript 對象沒有任何辦法能引用他們自身,因此你須要經過myViewModelObject.myDependentObservable = ... 的形式添加依賴監控屬性到view model對象上。 你不能直接在view model裏聲明他們,換句話說,你不能寫成下面這樣:

var viewModel = {
myDependentObservable: ko.dependentObservable(function() {
...
}, /* can't refer to viewModel from here, so this doesn't work */)
}

… 相反你必須寫成以下這樣:

var viewModel = {
// Add other properties here as you wish
};

viewModel.myDependentObservable = ko.dependentObservable(function() {
...
}, viewModel); // This is OK

只要你知道指望什麼,它確實不是個問題。J

 

依賴鏈

理所固然,若是你想你能夠建立一個依賴監控屬性的鏈。例如:

  • 監控屬性items表述一組列表項
  • 監控屬性selectedIndexes保存着被用戶選上的列表項的索引
  • 依賴監控屬性selectedItems 返回的是selectedIndexes 對應的列表項數組
  • 另外一個依賴監控屬性返回的true或false依賴於 selectedItems 的各個列表項是否包含一些屬性(例如,是否新的或者還未保存的)。一些UI element(像按鈕的啓用/禁用)的狀態取決於這個值)。

 而後,items或者selectedIndexes 的改變將會影響到全部依賴監控屬性的鏈,全部綁定這些屬性的UI元素都會自動更新。多麼整齊與優雅!

 

可寫的依賴監控屬性

新手可忽略此小節,可寫依賴監控屬性真的是太advanced了,並且大部分狀況下都用不到。

正如所學到的,依賴監控屬性是經過計算其它的監控屬性而獲得的。感受是依賴監控屬性正常狀況下應該是隻讀的。那麼,有可能讓依賴監控屬性支持可寫麼?你只須要聲明本身的callback函數而後利用寫入的值再處理一下相應的邏輯便可。

你能夠像使用普通的監控屬性同樣使用依賴監控屬性 – 數據雙向綁定到DOM元素上,而且經過自定義的邏輯攔截全部的讀和寫操做。這是很是牛逼的特性而且能夠在大範圍內使用。

 

例1:分解用戶的輸入

返回到經典的「first name + last name = full name」 例子上,你可讓事情調回來看: 讓依賴監控屬性fullName可寫,讓用戶直接輸入姓名全稱,而後輸入的值將被解析並映射寫入到基本的監控屬性firstName和lastName上:

var viewModel = {
firstName: ko.observable("Planet"),
lastName: ko.observable("Earth")
};

viewModel.fullName = ko.dependentObservable({

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: viewModel
});

這個例子裏,寫操做的callback接受寫入的值,把值分離出來,分別寫入到「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 例子的反例子,姓和名都不可編輯,相反姓和名組成的姓名全稱倒是可編輯的。

上面的view model演示的是經過一個簡單的參數來初始化依賴監控屬性。你能夠給下面的屬性傳入任何JavaScript對象:

  •    read — 必選,一個用來執行取得依賴監控屬性當前值的函數。
  •    write — 可選,若是聲明將使你的依賴監控屬性可寫,別的代碼若是這個可寫功能寫入新值,經過自定義邏輯將值再寫入各個基礎的監控屬性上。
  •    owner — 可選,若是聲明,它就是KO調用read或write的callback時用到的this。查看「管理this」獲取更新信息。

 例2:Value轉換器

 有時候你可能須要顯示一些不一樣格式的數據,從基礎的數據轉化成顯示格式。好比,你存儲價格爲float類型,可是容許用戶編輯的字段須要支持貨幣單位和小數點。你能夠用可寫的依賴監控屬性來實現,而後解析傳入的數據到基本 float類型裏:

viewModel.formattedPrice = ko.dependentObservable({

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: viewModel
});

而後咱們綁定formattedPrice到text box上:

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

因此,無論用戶何時輸入新價格,輸入什麼格式,text box裏會自動更新爲帶有2位小數點和貨幣符號的數值。這樣用戶能夠看到你的程序有多聰明,來告訴用戶只能輸入2位小數,不然的話自動刪除多餘的位數,固然也不能輸入負數,由於write的callback函數會自動刪除負號。

 

例3:過濾並驗證用戶輸入

例1展現的是寫操做過濾的功能,若是你寫的值不符合條件的話將不會被寫入,忽略全部不包括空格的值。

再多走一步,你能夠聲明一個監控屬性isValid 來表示最後一次寫入是否合法,而後根據真假值顯示相應的提示信息。稍後仔細介紹,先參考以下代碼:

var viewModel = {
acceptedNumericValue: ko.observable(123),
lastInputWasValid: ko.observable(true)
};

viewModel.attemptedValue = ko.dependentObservable({
read: viewModel.acceptedNumericValue,
write: function (value) {
if (isNaN(value))
this.lastInputWasValid(false);
else {
this.lastInputWasValid(true);
this.acceptedNumericValue(value); // Write to underlying storage
}
},
owner: viewModel
});

… 按照以下格式聲明綁定元素:

<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很難使用的話,你就能夠用它。

 

依賴跟蹤如何工做的

新手不必知道太清楚,可是高級開發人員能夠須要知道爲何依賴監控屬性可以自動跟蹤而且自動更新UI…

事實上,很是簡單,甚至說可愛。跟蹤的邏輯是這樣的:

  1. 當你聲明一個依賴監控屬性的時候,KO會當即調用執行函數而且獲取初始化值。
  2. 當你的執行函數運行的時候,KO會把全部須要依賴的依賴屬性(或者監控依賴屬性)都記錄到一個Log列表裏。
  3. 執行函數結束之後,KO會向全部Log裏須要依賴到的對象進行訂閱。訂閱的callback函數是從新運行你的執行函數。而後回頭從新執行上面的第一步操做(而且註銷再也不使用的訂閱)。
  4. 最後KO會通知上游全部訂閱它的訂閱者,告訴它們我已經設置了新值。

全部說,KO不只僅是在第一次執行函數執行時候探測你的依賴項,每次它都會探測。舉例來講,你的依賴屬性能夠是動態的:依賴屬性A表明你是否依賴於依賴屬性B或者C,這時候只有當A或者你當前的選擇B或者C改變的時候執行函數才從新執行。你不須要再聲明其它的依賴:運行時會自動探測到的。

另一個技巧是:一個模板輸出的綁定是依賴監控屬性的簡單實現,若是模板讀取一個監控屬性的值,那模板綁定就會自動變成依賴監控屬性依賴於那個監控屬性,監控屬性一旦改變,模板綁定的依賴監控屬性就會自動執行。嵌套的模板也是自動的:若是模板X render模板 Y,而且Y須要顯示監控屬性Z的值,當Z改變的時候,因爲只有Y依賴它,因此只有Y這部分進行了從新繪製(render)。

3     使用observable數組

若是你要探測和響應一個對象的變化,你應該用observables。若是你須要探測和響應一個集合對象的變化,你應該用observableArray 。在不少場景下,它都很是有用,好比你要在UI上須要顯示/編輯的一個列表數據集合,而後對集合進行添加和刪除。

var myObservableArray = ko.observableArray();    // Initially an empty array
myObservableArray.push('Some value'); // Adds the value and notifies observers

 

關鍵點:監控數組跟蹤的是數組裏的對象,而不是這些對象自身的狀態。

簡單說,將一對象放在observableArray 裏不會使這個對象自己的屬性變化可監控的。固然你本身也能夠聲明這個對象的屬性爲observable的,但它就成了一個依賴監控對象了。一個observableArray 僅僅監控他擁有的對象,並在這些對象添加或者刪除的時候發出通知。

 

預加載一個監控數組observableArray

若是你想讓你的監控數組在開始的時候就有一些初始值,那麼在聲明的時候,你能夠在構造器里加入這些初始對象。例如:

// This observable array initially contains three objects
var anotherObservableArray = ko.observableArray([
{ name: "Bungle", type: "Bear" },
{ name: "George", type: "Hippo" },
{ name: "Zippy", type: "Unknown" }
]);

 

從observableArray裏讀取信息

一個observableArray其實就是一個observable的監控對象,只不過他的值是一個數組(observableArray還加了不少其餘特性,稍後介紹)。因此你能夠像獲取普通的observable的值同樣,只須要調用無參函數就能夠獲取自身的值了。 例如,你能夠像下面這樣獲取它的值:

alert('The length of the array is ' + myObservableArray().length);
alert('The first element is ' + myObservableArray()[0]);

理論上你可使用任何原生的JavaScript數組函數來操做這些數組,可是KO提供了更好的功能等價函數,他們很是有用是由於:

  1. 兼容全部瀏覽器。(例如indexOf不能在IE8和早期版本上使用,但KO本身的indexOf 能夠在全部瀏覽器上使用)
  2. 在數組操做函數方面(例如push和splice),KO本身的方式能夠自動觸發依賴跟蹤,而且通知全部的訂閱者它的變化,而後讓UI界面也相應的自動更新。
  3. 語法更方便,調用KO的push方法,只須要這樣寫:myObservableArray.push(...)。 好比原生數組的myObservableArray().push(...)好用多了。

 

下面講解的均是observableArray的讀取和寫入的相關函數。

indexOf

indexOf 函數返回的是第一個等於你參數數組項的索引。例如:myObservableArray.indexOf('Blah')將返回以0爲第一個索引的第一個等於Blah的數組項的索引。若是沒有找到相等的,將返回-1。

slice

slice函數是observableArray相對於JavaScript 原生函數slice的等價函數(返回給定的從開始索引到結束索引之間全部的對象集合)。 調用myObservableArray.slice(...)等價於調用JavaScript原生函數(例如:myObservableArray().slice(...))。

 

操做observableArray

observableArray 展示的是數組對象類似的函數並通知訂閱者的功能。

pop, push, shift, unshift, reverse, sort, splice

全部這些函數都是和JavaScript數組原生函數等價的,惟一不一樣的數組改變能夠通知訂閱者:

    myObservableArray.push('Some new value') 在數組末尾添加一個新項

    myObservableArray.pop() 刪除數組最後一個項並返回該項

    myObservableArray.unshift('Some new value') 在數組頭部添加一個項

    myObservableArray.shift() 刪除數組頭部第一項並返回該項

    myObservableArray.reverse() 翻轉整個數組的順序

    myObservableArray.sort() 給數組排序

        默認狀況下,是按照字符排序(若是是字符)或者數字排序(若是是數字)。

        你能夠排序傳入一個排序函數進行排序,該排序函數須要接受2個參數(表明該數組裏須要比較的項),若是第一個項小於第二個項,返回-1,大於則返回1,等於返回0。例如:用lastname給person排序,你能夠這樣寫:myObservableArray.sort (function (left, right) {return left.lastName == right.lastName? 0: (left.lastName < right.lastName? -1: 1) })

     myObservableArray.splice() 刪除指定開始索引和指定數目的數組對象元素。例如myObservableArray.splice(1, 3) 從索引1開始刪除3個元素(第2,3,4個元素)而後將這些元素做爲一個數組對象返回。

更多observableArray 函數的信息,請參考等價的JavaScript數組標準函數

 

remove和removeAll

observableArray 添加了一些JavaScript數組默認沒有但很是有用的函數:

    myObservableArray.remove(someItem) 刪除全部等於someItem的元素並將被刪除元素做爲一個數組返回

    myObservableArray.remove(function(item) { return item.age < 18 }) 刪除全部age屬性小於18的元素並將被刪除元素做爲一個數組返回

    myObservableArray.removeAll(['Chad', 132, undefined]) 刪除全部等於'Chad', 123, or undefined的元素並將被刪除元素做爲一個數組返回

 

destroy和destroyAll(注:一般只和和Ruby on Rails開發者有關)

destroy和destroyAll函數是爲Ruby on Rails開發者方便使用爲開發的:

    myObservableArray.destroy(someItem) 找出全部等於someItem的元素並給他們添加一個屬性_destroy,並賦值爲true

    myObservableArray.destroy(function(someItem) { return someItem.age < 18 }) 找出全部age屬性小於18的元素並給他們添加一個屬性_destroy,並賦值爲true

    myObservableArray.destroyAll(['Chad', 132, undefined]) 找出全部等於'Chad', 123, 或undefined 的元素並給他們添加一個屬性_destroy,並賦值爲true

 

那麼,_destroy是作什麼用的?正如我提到的,這只是爲Rails 開發者準備的。在Rails 開發過程當中,若是你傳入一個JSON對象,Rails 框架會自動轉換成ActiveRecord對象而且保存到數據庫。Rails 框架知道哪些對象以及在數據庫中存在,哪些須要添加或更新, 標記_destroy爲true就是告訴框架刪除這條記錄。

 

注意的是:在KO render一個foreach模板的時候,會自動隱藏帶有_destroy屬性而且值爲true的元素。因此若是你的「delete」按鈕調用destroy(someItem) 方法的話,UI界面上的相對應的元素將自動隱藏,而後等你提交這個JSON對象到Rails上的時候,這個元素項將從數據庫刪除(同時其它的元素項將正常的插入或者更新)。

相關文章
相關標籤/搜索