在複雜的應用程序中,因爲具備多個交織的依賴關係,更新單個observable可能會觸發計算的observable,手動訂閱和UI綁定更新的級聯。 若是將沒必要要的中間值推送到視圖或產生額外的計算的可觀察評估,則這些更新多是昂貴的和低效的。 即便在簡單的應用程序中,更新相關的可觀察量或單個可觀察的屢次(例如填充可觀察的數組)也能夠具備相似的效果。 數組
使用延遲更新可確保僅在計算的可觀察量和綁定的依賴性穩定後才更新。 即便可觀察者可能經歷多箇中間值,也只使用最新的值來更新其依賴性。 爲了方便這一點,全部通知變爲異步,使用Knockout微任務隊列進行調度。 這聽起來很是相似於速率限制,這也有助於防止額外的通知,但延遲更新能夠在整個應用程序中提供這些好處,而不會增長延遲。 如下是標準,延遲和速率限制模式之間通知調度的不一樣之處: 瀏覽器
延遲更新默認關閉,以提供與現有應用程序的兼容性。 要爲應用程序使用延遲更新,必須在經過設置如下選項初始化視圖模型以前啓用它:服務器
ko.options.deferUpdates = true;
當deferUpdates選項打開時,全部observable,computed observable和綁定都將設置爲使用延遲更新和通知。 在開始建立基於Knockout的應用程序時啓用此功能意味着您不須要擔憂解決中間值問題,所以能夠促進更清潔,純粹的反應性設計。 可是,當啓用現有應用程序的延遲更新時,應該注意,由於它會破壞依賴於同步更新或中間值通知的代碼(儘管您可能能夠解決這些問題)。app
如下是一個有用的示例來演示延遲更新消除中間值的UI更新的能力,以及如何提升性能。異步
UI怨罵:函數
<!--ko foreach: $root--> <div class="example"> <table> <tbody data-bind='foreach: data'> <tr> <td data-bind="text: name"></td> <td data-bind="text: position"></td> <td data-bind="text: location"></td> </tr> </tbody> </table> <button data-bind="click: flipData, text: 'Flip ' + type"></button> <div class="time" data-bind="text: (data(), timing() + ' ms')"></div> </div> <!--/ko—>
視圖模型源碼:性能
function AppViewModel(type) { this.type = type; this.data = ko.observableArray([ { name: 'Alfred', position: 'Butler', location: 'London' }, { name: 'Bruce', position: 'Chairman', location: 'New York' } ]); this.flipData = function () { this.starttime = new Date().getTime(); var data = this.data(); for (var i = 0; i < 999; i++) { this.data([]); this.data(data.reverse()); } } this.timing = function () { return this.starttime ? new Date().getTime() - this.starttime : 0; }; } ko.options.deferUpdates = true; var vmDeferred = new AppViewModel('deferred'); ko.options.deferUpdates = false; var vmStandard = new AppViewModel('standard'); ko.applyBindings([vmStandard, vmDeferred]);
即便您不爲整個應用程序啓用延遲更新,您仍然能夠經過專門使某些可觀察項延遲,從中受益。 使用延遲擴展器:this
this.data = ko.observableArray().extend({ deferred: true });
如今咱們能夠將一堆項目推入數據數組,而沒必要擔憂引發過多的UI或計算更新。 延遲擴展器能夠應用於任何類型的可觀察量,包括可觀察數組和計算可觀察量。lua
如下模型表示能夠做爲分頁網格呈現的數據:spa
function GridViewModel() { this.pageSize = ko.observable(20); this.pageIndex = ko.observable(1); this.currentPageData = ko.observableArray(); // Query /Some/Json/Service whenever pageIndex or pageSize changes, // and use the results to update currentPageData ko.computed(function() { var params = { page: this.pageIndex(), size: this.pageSize() }; $.getJSON('/Some/Json/Service', params, this.currentPageData); }, this); }
由於計算的observable計算了pageIndex和pageSize,它變得依賴於它們。 因此,這個代碼將使用jQuery的$ .getJSON函數從新加載currentPageData當一個GridViewModel首次實例化時,每當pageIndex或pageSize屬性後來改變。
這是很是簡單和優雅(和添加更多可觀察的查詢參數,也觸發刷新自動每當他們改變)的,但有一個潛在的效率問題。 假設您向GridViewModel添加了如下函數,該函數同時更改pageIndex和pageSize:
this.setPageSize = function(newPageSize) { // Whenever you change the page size, we always reset the page index to 1 this.pageSize(newPageSize); this.pageIndex(1); }
問題是,這將致使兩個Ajax請求:第一個將在您更新pageSize時啓動,第二個將在您更新pageIndex時當即啓動。 這是浪費帶寬和服務器資源,以及不可預測的競爭條件的來源。
當應用於計算的可觀察量時,延遲的延長器也將避免對計算函數的過分評價。 使用延遲更新可確保對當前任務中的依賴性的任何更改順序只觸發對計算的observable的一次從新評估。 例如:
ko.computed(function() { // This evaluation logic is exactly the same as before var params = { page: this.pageIndex(), size: this.pageSize() }; $.getJSON('/Some/Json/Service', params, this.currentPageData); }, this).extend({ deferred: true });
如今,您能夠根據須要更改pageIndex和pageSize,而且Ajax調用只會在您將線程釋放回JavaScript運行時後發生一次。
雖然延遲,異步通知一般更好,由於更少的UI更新,若是你須要當即更新UI多是一個問題。 有時,爲了正確的功能,您須要一個推送到UI的中間值。 您可使用ko.tasks.runEarly方法完成此操做。 例如:
// remove an item from an array var items = myArray.splice(sourceIndex, 1); // force updates so the UI will see a delete/add rather than a move ko.tasks.runEarly(); // add the item in a new location myArray.splice(targetIndex, 0, items[0]);
當any observable的值是原始值(數字,字符串,布爾值或null)時,只有當observable的依賴項設置爲實際上與以前不一樣的值時,纔會通知它的依賴項。 所以,原始值延遲可觀測量只有在當前任務結束時它們的值實際上不一樣時才通知。 換句話說,若是一個原始值的延遲的observable被改變爲一個新的值,而後改回原來的值,則不會發生通知。
要確保始終通知訂閱者更新,即便該值相同,也可使用notify擴展器:
ko.options.deferUpdates = true; myViewModel.fullName = ko.pureComputed(function() { return myViewModel.firstName() + " " + myViewModel.lastName(); }).extend({ notify: 'always' });