? ? ? ?最近馬上要開始一個新項目的研發,作為第一次mvvm應用的嘗試,我決定使用knockoutjs框架。作為學習的開始就從官網的Document翻譯開始吧,這樣會增加印象并加入自己的思考,說是翻譯也并不是純粹的翻譯,會加入自己對知識點的思考以及自己的嘗試,在系列最后也希望用一個應用案例作為結尾。希望自己能堅持下來并有所收獲,理解不對的地方大家也指出來避免我”誤入歧途“。也并不會翻譯所有的內容,我會根據自己的經驗選擇最能反映它使用和精髓的部分。當前版本為3.4
? ? ? 好了,閑言少敘,正篇開始。
===================================華麗的分割線============================================
? ? ? knocket主要圍繞由以下三個核心特征組成:
- ?Observables(觀察器)與dependency tracking(依賴追蹤)
- ?Declarative bindings(聲明式綁定)
- ?Templating(模板)
? ? ??
? ? ?MVVM and View Models
? ? ??Model-View-View Model (MVVM)?是一種創建用戶界面或用戶接口的設計模式。你可以通過將UI拆分成下面三個部分從而將復雜的UI簡潔化,清晰化:
- Model(數據模型): 應用程序存儲數據。模型包括了你應用程序域中與業務相關的數據以及這些數據對應的操作(例如 一個銀行賬戶模型具有轉賬的功能)并且它跟UI是相互獨立 的。 當時用KO的時候,通常這類模型是通過ajax從服務器端進行獲取的。
- ViewModel(視圖模型): 服務于數據模型,為數據模型在UI上的展現及UI上的操作進行包裝服務。 例如, 如果你要在UI上做一個列表展現, 你的視圖模型將會是包含一個數據集合的對象, 并提供一些添加和刪除數據的相關方法。需要注意的是視圖模型并不是UI本身,它并不包括任何UI元素,比如按鈕啊,標簽啊樣式之類。它也不是持久化的數據對象,它只是為數據模型臨時保存一些用戶正在處理的數據。當我們在使用KO的時候,它無非就是一些純粹的javascript對象而已。
- A?view(視圖): 一個可視,可交互的真正的UI展現,他展現著視圖模型的當前狀態。它所展現的信息來自于視圖模型,并且向視圖模型發送命令執行動作 (例如: 當用戶點擊按鈕的時候,視圖向視圖模型發送命令,視圖模型進行真正的操作),并且當視圖模型屬性發生變化的時候,視圖會自動進行展現的更新。當時用KO的時候,你的視圖中的Html元素內容可以通過聲明式綁定與視圖模型進行連接從而決定UI如何展現。另外,你也可以通過使用模板的方式來使用視圖模型中的數據生成UI中的Html內容。(模板后面會提到)
? ? ? 好了,讓我們來舉個很小的例子來看看上面說的視圖與視圖模型在KO中是如何協作的。創建一個視圖模型對象非常容易,隨意聲明一個javascript對象我們就可以將它作為視圖模型對象。例如:
//創建一個視圖模型對象
var myViewModel = {personName: 'Bob',personAge: 123};
ko.applyBindings(myViewModel);//ko是knockoutjs中的全局對象,這句話的意思是將數據模型對象與UI中所有有data-bind的屬性進行聲明綁定的元素進行連接
然后我們就可以創建一個非常簡單的視圖來展現上面的視圖對象。還記得嗎?他們使用聲明式綁定來進行連接。下面的視圖用來展現視圖模型對象中的personName數據。
The name is <span data-bind="text: personName"></span>
?
好了現在如果運行頁面的話將會顯示如下運行結果:
通過上面的例子我們可以看到,在UI上賦值我們并沒有像jquery一樣通過js來控制,而是使用聲明綁定的方式在UI元素和js數據對象上建立聯系,自動展現。在上面的代碼標簽中data-bind
?并不是Html中的原生標記,它在Html5中得到瀏覽器的支持,是KO框架用來進行聲明式綁定的工具,所以在KO中進行聲明式綁定,都通過data-bind屬性進行。有了視圖模型,有了相應視圖,最后要進行兩者的連接了,所以下面這行代碼必不可少:
ko.applyBindings(myViewModel); //將myViewModel對象與UI中所有進行了聲明式綁定的元素進行連接,注意:是所有。
完整測試代碼:
The name is <span data-bind="text: personName"></span> @section scripts {<script src="~/Scripts/knockout-3.4.0.js"></script><script type="text/javascript">$(function () {var myViewModel = {personName: 'Bob',personAge: 123};ko.applyBindings(myViewModel);});</script> }
?
? ? ? 關于ko.appyBindings(myViewModel)中參數的作用說明一下:第一個參數說明在整個UI中你希望使用哪個視圖模型對象與視圖中的聲明綁定進行連接。你也可以傳遞第二個參數來決定這個視圖模型與UI中的哪個特定的聲明式綁定(data-bind)進行連接,而不是與所有的進行連接。舉個例子,?ko.applyBindings(myViewModel, document.getElementById('someElementId'))。
這就限制了這個視圖模型對象只能與ID為someElementId
?的Html元素對象以及它的后代元素對象進行連接,這樣的話當你想要定義多個視圖模型對象并且與頁面中不同的元素進行綁定的時候就會特別有用。到目前為止真的是相當簡單吧。
? ? ?Observables
? ? ?好了,你已經看到了如何創建一個基本的視圖模型以及如何通過綁定進行它的屬性數據的展現。但是使用KO一個核心的好處是當視圖模型內容改變的時候它還會自動更新你的UI,反之亦然。這有時會大大簡化你的代碼(我們稍后展示這個效果)。那么KO如何知道你的視圖模型什么時候發生了改變進而更新你的UI呢?答案是:你需要將你的數圖模型中的屬性聲明為observable類型對象。 observables類型對象非常特殊,當視圖模型發生改變的時候,他們可以向訂閱者發出通知,并自動建立與訂閱者的關系,訂閱者也就是具有聲明式綁定的元素。舉個例子, 重寫一下上面的代碼如下:
? ??
$(function () {var myViewModel = {personName: ko.observable('Bob'),personAge: ko.observable(123)};ko.applyBindings(myViewModel);});
? ? ?現在,你完全不需要視圖 data-bind
?聲明部分保持不變. 與之前代碼不同的是,現在ko可以自動監測變化了, 一旦視圖模型有數據發生變化,它就會自動更新視圖。
? ? ?Observables屬性的讀取與寫入
? ?? 要讀取observable的當前值,只需要像調用方法一樣以無參數的方式調用它。以上面的代碼為例,?myViewModel.personName()
?將會返回?'Bob'
,而myViewModel.personAge()
?將返回123。
? ? ?要向observable屬性中寫入一個值的話也跟上面一樣進行調用,只不過傳入一個你想要寫入的新值就可以了。舉個例子:調用myViewModel.personName('Mary')
?將會為personName賦一個新的名字。另外KO還提供了一個非常方便的代碼鏈寫法。?像這樣:myViewModel.personName('Mary').personAge(50)
?會修改personName為?'Mary'
?,personAge修改為?50。
? ? ?observables的核心作用就是"觀察" ,也就是說, 被聲明為observable的屬性將來是會被雙向通知的,它通知其它UI元素它已經被修改了,并且觀察相關UI元素內容,并將變化值更新到ViewModel對象上。KO框架中的很多內置綁定就是用來干這事兒的。所以,當你在UI元素上(例如span標簽)寫上data-bind="text: personName"的時候
?text
?綁定類型將把這個span元素進行注冊并做好被通知的準備只要視圖對象上的personName發生了該表,span就會被通知修改內部的文本內容
(假設personName是一個observable值,另外除了text綁定還有許多其它類型綁定,我們后面提到)。
? ? ?當你通過調用myViewModel.personName('Mary')來修改personName的值的時候
, text綁定將會自動更新相關DOM元素的text內容
?。
? ?observables的顯示訂閱處理??
? ? 通常你無需干預訂閱的過程,所以初學者可以暫時跳過這一小節。
? ? 當observable類型數據發生改變后如果你希望在這個過程中做一些處理,你可以調用observable屬性上的subscribe方法來將自己的處理代碼注冊進來。舉個例子:
myViewModel.personName.subscribe(function(newValue) {alert("The person's new name is " + newValue); });
? ? 上面這段代碼執行后,如果修改了personName的值后,那么將會彈出一個警告框,并且通過newValue參數可以獲取當前正在更新的值。這個過程叫訂閱注冊。
? ??subscribe方法接受三個傳入參數
:?callback
?是一個function當通知到來時會自動執行,?target
?(可選) 定義了在callback方法中?this代表了哪個對象(默認的話this就是當前視圖模型對象)
, event
(可選; 默認值是"change"
) 事件名稱,是指當什么類型的事件發生的時候會有通知到來,默認情況下就是當值發生改變的時候。(其它類型我們后面談到)。
? ? ?當然了,一旦你注冊了一個自己的訂閱,你也可以根據需要在未來的某個時候取消這個訂閱,你需要先定義一個變量來接收subscribe當前的返回值,然后調用dispose方法。代碼如下
var subscription = myViewModel.personName.subscribe(function (newValue) { /* do stuff */ }); // ...then later... subscription.dispose();
? ? ?如果你希望在observable類型值在發生改變,但被賦值之前做一些處理的話,你也可以在beforeChange
?事件上注冊自己的處理,代碼如下:
myViewModel.personName.subscribe(function(oldValue) {alert("The person's previous name is " + oldValue); }, null, "beforeChange");
? ? 注意: Knockout 是否觸發上面的訂閱還有一個默認條件就是新的值必須與老的值不相同,如果賦值時是相同值的話那么將不會觸發這兩個訂閱。如果需要更改這種默認動作可以使用訂閱器上的extend方法來修改。代碼如下:
myViewModel.personName.extend({ notify: 'always' });
? ? 最后,如果你的observable屬性在更新時的動作比較耗時或者會更新的很頻繁,你可以通過限制通知的時間間隔,畢竟訂閱通知會有性能影響。做法如下:
myViewModel.personName.extend({ rateLimit: 50 });
? ? 這樣的話就算是頻繁更新屬性值,每次通知的事件間隔也會控制在50毫秒。測試代碼如下:
<button type="button" id="btnStart">點擊測試</button> <span id="clickcontent"></span> @section scripts {<script src="~/Scripts/knockout-3.4.0.js"></script><script type="text/javascript">$(function () {var myViewModel = {//personName: ko.observable("ZhouBo") personName: ko.observable("ZhouBo")};myViewModel.personName.extend({ notify: 'always' });myViewModel.personName.extend({ rateLimit: 5000 });ko.applyBindings(myViewModel);var index = 0;$('#btnStart').click(function () {$('#clickcontent').text(++index);myViewModel.personName(++index);});});
說明:如果我快速點擊按鈕 btnStart,則5秒鐘之后第一個span的內容才會發生變化,也就是5廟后才發送了一次通知。
?