在javascript的世界里尾序,屬性和數(shù)組是構(gòu)成對(duì)象的基本元素,也是數(shù)據(jù)結(jié)構(gòu)的基本元素躯砰,因此knockout針對(duì)屬性和數(shù)組進(jìn)行了動(dòng)態(tài)的監(jiān)控每币,也是靠著這個(gè)特性完成了MVVM的流程。
創(chuàng)建一個(gè)View Models
只需要聲明任意的JavaScript object字面量形式或者構(gòu)造函數(shù)形式均可琢歇。例如:
var myViewModel = {
personName: 'Bob',
personAge: 123
};
你可以為view model創(chuàng)建一個(gè)聲明式綁定的簡(jiǎn)單view兰怠。例如:下面的代碼顯示personName 值:
The name is <span data-bind="text: personName"></span>
完成以上兩部并不能完成MVVM梦鉴,需要將html的內(nèi)容與viewmode綁定起來(lái),代碼如下:
ko.applyBindings(myViewModel);
你可能奇怪ko.applyBindings使用的是什么樣的參數(shù),
- 第一個(gè)參數(shù)是你想用于聲明式綁定
- 第二個(gè)參數(shù)(可選)揭保,可以聲明成使用data-bind的HTML元素或者容器肥橙。例如, ko.applyBindings(myViewModel, document.getElementById('someElementId'))秸侣。它的現(xiàn)在是只有作為someElementId 的元素和子元素才能激活KO功能存筏。 好處是你可以在同一個(gè)頁(yè)面聲明多個(gè)view model,用來(lái)區(qū)分區(qū)域味榛。
激活綁定需要注意一下幾點(diǎn):
- 每個(gè)dom節(jié)點(diǎn)只能綁定一個(gè)viewmodel椭坚,該節(jié)點(diǎn)的內(nèi)部也不能再次綁定任何viewmodel.
- 上述給出的例子沒(méi)有第二個(gè)參數(shù),那么這個(gè)viewmode就綁定了整個(gè)body上搏色,這個(gè)是默認(rèn)的善茎。那么這種情況在這個(gè)頁(yè)面中就只能存在一個(gè)viewmodel。
- 雖然頁(yè)面只能存在一個(gè)viewmodel频轿,但是viewmodel里面可以綁定組件巾表。
監(jiān)控屬性(Observables)
現(xiàn)在已經(jīng)知道如何創(chuàng)建一個(gè)簡(jiǎn)單的view model并且通過(guò)binding顯示它的屬性了。但是KO一個(gè)重要的功能是當(dāng)你的view model改變的時(shí)候能自動(dòng)更新你的界面略吨。當(dāng)你的view model部分改變的時(shí)候KO是如何知道的呢?答案是:你需要將你的model屬性聲明成observable的, 因?yàn)樗欠浅L厥獾腏avaScript objects考阱,能夠通知訂閱者它的改變以及自動(dòng)探測(cè)到相關(guān)的依賴翠忠。
例如:將上述例子的view model改成如下代碼:
var myViewModel = {
personName: ko.observable('Bob'),
personAge: ko.observable(123)
};
你根本不需要修改view – 所有的data-bind語(yǔ)法依然工作,不同的是他能監(jiān)控到變化乞榨,當(dāng)值改變時(shí)秽之,view會(huì)自動(dòng)更新。
監(jiān)控屬性(observables)的讀和寫(xiě)
不是所有的瀏覽器都支持JavaScript的 getters and setters (比如IE),吃既,所以為了兼容性考榨,使用ko.observable監(jiān)控的對(duì)象都是真實(shí)的function函數(shù)。
- 讀取監(jiān)控屬性(observable)的值鹦倚,只需要直接調(diào)用監(jiān)控屬性(observable)(不需要參數(shù))河质,例如myViewModel.personName() 將返回'Bob', myViewModel.personAge() 將返回 123。
- 寫(xiě)一個(gè)新值到監(jiān)控屬性(observable)上震叙,調(diào)用這個(gè)observable屬性并當(dāng)新值作為參數(shù)掀鹅。例如:調(diào)用 myViewModel.personName('Mary') 將更新name值為'Mary'。
- 給一個(gè)model對(duì)象的多個(gè)屬性寫(xiě)入新值媒楼,你可以使用鏈?zhǔn)秸Z(yǔ)法乐尊。例如: myViewModel.personName('Mary').personAge(50) 將會(huì)將name更新為 'Mary' 并且 將age更新為 50.
監(jiān)控屬性(observables)的特征就是監(jiān)控(observed),例如其它代碼可以說(shuō)我需要得到對(duì)象變化的通知划址,所以KO內(nèi)部有很多內(nèi)置的綁定語(yǔ)法扔嵌。所以如果你的代碼寫(xiě)成data-bind="text: personName"限府, text綁定注冊(cè)到自身,一旦personName的值改變痢缎,它就能得到通知胁勺。
當(dāng)然調(diào)用myViewModel.personName('Mary')改變name的值,text綁定將自動(dòng)更新這個(gè)新值到相應(yīng)的DOM元素上牺弄。這就是如何將view model的改變傳播到view上的姻几。
監(jiān)控屬性(Observables)的顯式訂閱
通常情況下,你不用手工訂閱势告,所以新手可以忽略此小節(jié)蛇捌。高級(jí)用戶,如果你要注冊(cè)自己的訂閱到監(jiān)控屬性(observables)咱台,你可以調(diào)用它的subscribe 函數(shù)络拌。例如:
myViewModel.personName.subscribe(function (newValue) {
alert("The person's new name is " + newValue);
});
這個(gè)subscribe 函數(shù)在內(nèi)部很多地方都用到的。你也可以終止自己的訂閱:首先得到你的訂閱回溺,然后調(diào)用這個(gè)對(duì)象的dispose函數(shù)春贸,例如:
var subscription = myViewModel.personName.subscribe(function (newValue) { /* do stuff */ });
// ...then later...
subscription.dispose(); // I no longer want notifications
大多數(shù)情況下,你不需要做這些遗遵,因?yàn)閮?nèi)置的綁定和模板系統(tǒng)已經(jīng)幫你做好很多事情了萍恕,可以直接使用它們。
如果要在可更改即將更改之前通知其值车要,則可以訂閱beforeChange事件允粤。 例如:
myViewModel.personName.subscribe(function(oldValue) {
alert("The person's previous name is " + oldValue);
}, null, "beforeChange");
注意:Knockout不保證beforeChange和change事件成對(duì)出現(xiàn),因?yàn)榇a的其他部分可能會(huì)單獨(dú)引發(fā)任一事件翼岁。 如果您需要跟蹤observable的先前值类垫,則由您使用訂閱來(lái)捕獲和跟蹤它。
強(qiáng)制觀察者總是通知訂閱者
當(dāng)寫(xiě)入包含原始值(數(shù)字琅坡,字符串悉患,布爾值或null)的observable時(shí),通常只有在值實(shí)際改變時(shí)才通知observable的依賴關(guān)系榆俺。 但是售躁,可以使用內(nèi)置的notify擴(kuò)展器來(lái)確保observable的訂閱者總是在寫(xiě)入時(shí)通知,即使值是相同的茴晋。 您可以將擴(kuò)展器應(yīng)用于可觀察者迂求,如下所示:
myViewModel.personName.extend({ notify: 'always' });
延遲和/或抑制更改通知
通常,可觀察者立即通知其訂戶晃跺,只要它改變揩局。 但是如果一個(gè)observable重復(fù)更改或觸發(fā)昂貴的更新,您可以通過(guò)限制或延遲observable的更改通知獲得更好的性能掀虎。 這是使用rateLimit擴(kuò)展器實(shí)現(xiàn)這樣凌盯,其中rateLimit為延遲的參數(shù)付枫,單位毫秒:
// Ensure it notifies about changes no more than once per 50-millisecond period
myViewModel.personName.extend({ rateLimit: 50 });
監(jiān)控?cái)?shù)組
如果你要探測(cè)和響應(yīng)一個(gè)對(duì)象的變化,你應(yīng)該用observables驰怎。如果你需要探測(cè)和響應(yīng)一個(gè)集合對(duì)象的變化阐滩,你應(yīng)該用observableArray 。在很多場(chǎng)景下县忌,它都非常有用掂榔,比如你要在UI上需要顯示/編輯的一個(gè)列表數(shù)據(jù)集合,然后對(duì)集合進(jìn)行添加和刪除症杏。
var myObservableArray = ko.observableArray(); // Initially an empty array
myObservableArray.push('Some value'); // Adds the value and notifies observers
關(guān)鍵點(diǎn):監(jiān)控?cái)?shù)組跟蹤的是數(shù)組里的對(duì)象装获,而不是這些對(duì)象自身的狀態(tài)。
簡(jiǎn)單說(shuō)厉颤,將一對(duì)象放在observableArray 里不會(huì)使這個(gè)對(duì)象本身的屬性變化可監(jiān)控的穴豫。當(dāng)然你自己也可以聲明這個(gè)對(duì)象的屬性為observable的,但它就成了一個(gè)依賴監(jiān)控對(duì)象了逼友。一個(gè)observableArray 僅僅監(jiān)控他擁有的對(duì)象精肃,并在這些對(duì)象添加或者刪除的時(shí)候發(fā)出通知。
預(yù)加載一個(gè)監(jiān)控?cái)?shù)組observableArray
如果你想讓你的監(jiān)控?cái)?shù)組在開(kāi)始的時(shí)候就有一些初始值帜乞,那么在聲明的時(shí)候司抱,你可以在構(gòu)造器里加入這些初始對(duì)象。例如:
// This observable array initially contains three objects
var anotherObservableArray = ko.observableArray([
{ name: "Bungle", type: "Bear" },
{ name: "George", type: "Hippo" },
{ name: "Zippy", type: "Unknown" }
]);
從observableArray里讀取信息
一個(gè)observableArray其實(shí)就是一個(gè)observable的監(jiān)控對(duì)象黎烈,只不過(guò)他的值是一個(gè)數(shù)組(observableArray還加了很多其他特性习柠,稍后介紹)。所以你可以像獲取普通的observable的值一樣怨喘,只需要調(diào)用無(wú)參函數(shù)就可以獲取自身的值了。 例如振定,你可以像下面這樣獲取它的值:
alert('The length of the array is ' + myObservableArray().length);
alert('The first element is ' + myObservableArray()[0]);
理論上你可以使用任何原生的JavaScript數(shù)組函數(shù)來(lái)操作這些數(shù)組必怜,但是KO提供了更好的功能等價(jià)函數(shù),他們非常有用是因?yàn)椋?/p>
- 兼容所有瀏覽器后频。(例如indexOf不能在IE8和早期版本上使用梳庆,但KO自己的indexOf 可以在所有瀏覽器上使用)
- 在數(shù)組操作函數(shù)方面(例如push和splice),KO自己的方式可以自動(dòng)觸發(fā)依賴跟蹤卑惜,并且通知所有的訂閱者它的變化膏执,然后讓UI界面也相應(yīng)的自動(dòng)更新。
- 語(yǔ)法更方便露久,調(diào)用KO的push方法更米,只需要這樣寫(xiě):myObservableArray.push(...)。 比如原生數(shù)組的myObservableArray().push(...)好用多了毫痕。
下面講解的均是observableArray的讀取和寫(xiě)入的相關(guān)函數(shù)征峦。
indexOf
indexOf 函數(shù)返回的是第一個(gè)等于你參數(shù)數(shù)組項(xiàng)的索引迟几。例如:myObservableArray.indexOf('Blah')將返回以0為第一個(gè)索引的第一個(gè)等于Blah的數(shù)組項(xiàng)的索引。如果沒(méi)有找到相等的栏笆,將返回-1类腮。
slice
slice函數(shù)是observableArray相對(duì)于JavaScript 原生函數(shù)slice的等價(jià)函數(shù)(返回給定的從開(kāi)始索引到結(jié)束索引之間所有的對(duì)象集合)。 調(diào)用myObservableArray.slice(...)等價(jià)于調(diào)用JavaScript原生函數(shù)(例如:myObservableArray().slice(...))蛉加。
操作observableArray
observableArray 展現(xiàn)的是數(shù)組對(duì)象相似的函數(shù)并通知訂閱者的功能蚜枢。
pop, push, shift, unshift, reverse, sort, splice
所有這些函數(shù)都是和JavaScript數(shù)組原生函數(shù)等價(jià)的,唯一不同的數(shù)組改變可以通知訂閱者:
myObservableArray.push('Some new value') 在數(shù)組末尾添加一個(gè)新項(xiàng)
myObservableArray.pop() 刪除數(shù)組最后一個(gè)項(xiàng)并返回該項(xiàng)
myObservableArray.unshift('Some new value') 在數(shù)組頭部添加一個(gè)項(xiàng)
myObservableArray.shift() 刪除數(shù)組頭部第一項(xiàng)并返回該項(xiàng)
myObservableArray.reverse() 翻轉(zhuǎn)整個(gè)數(shù)組的順序
myObservableArray.sort() 給數(shù)組排序
默認(rèn)情況下针饥,是按照字符排序(如果是字符)或者數(shù)字排序(如果是數(shù)字)厂抽。
你可以排序傳入一個(gè)排序函數(shù)進(jìn)行排序,該排序函數(shù)需要接受2個(gè)參數(shù)(代表該數(shù)組里需要比較的項(xiàng))打厘,如果第一個(gè)項(xiàng)小于第二個(gè)項(xiàng)修肠,返回-1,大于則返回1户盯,等于返回0嵌施。例如:用lastname給person排序,你可以這樣寫(xiě):
myObservableArray.sort (function (left, right) {
return left.lastName == right.lastName? 0: (left.lastName < right.lastName? -1: 1)
});
myObservableArray.splice() 刪除指定開(kāi)始索引和指定數(shù)目的數(shù)組對(duì)象元素莽鸭。
例如myObservableArray.splice(1, 3) 從索引1開(kāi)始刪除3個(gè)元素(第2,3,4個(gè)元素)然后將這些元素作為一個(gè)數(shù)組對(duì)象返回吗伤。
更多observableArray 函數(shù)的信息,請(qǐng)參考等價(jià)的JavaScript數(shù)組標(biāo)準(zhǔn)函數(shù)硫眨。
remove和removeAll
observableArray 添加了一些JavaScript數(shù)組默認(rèn)沒(méi)有但非常有用的函數(shù):
- myObservableArray.remove(someItem) 刪除所有等于someItem的元素并將被刪除元素作為一個(gè)數(shù)組返回
- myObservableArray.remove(function(item) { return item.age < 18 }) 刪除所有age屬性小于18的元素并將被刪除元素作為一個(gè)數(shù)組返回
- myObservableArray.removeAll(['Chad', 132, undefined]) 刪除所有等于'Chad', 123, or undefined的元素并將被刪除元素作為一個(gè)數(shù)組返回