KnockoutJS 3.X API 第七章 其他技術(shù)(3) 延遲更新
注意:本文檔適用于Knockout 3.4.0及更高版本。 對于先前版本,“延遲更新”插件提供類似的支持。
在復(fù)雜的應(yīng)用程序中,由于具有多個交織的依賴關(guān)系,更新單個observable可能會觸發(fā)計算的observable,手動訂閱和UI綁定更新的級聯(lián)。 如果將不必要的中間值推送到視圖或產(chǎn)生額外的計算的可觀察評估,則這些更新可能是昂貴的和低效的。 即使在簡單的應(yīng)用程序中,更新相關(guān)的可觀察量或單個可觀察的多次(例如填充可觀察的數(shù)組)也可以具有類似的效果。
使用延遲更新可確保僅在計算的可觀察量和綁定的依賴性穩(wěn)定后才更新。 即使可觀察者可能經(jīng)歷多個中間值,也只使用最新的值來更新其依賴性。 為了方便這一點,所有通知變?yōu)楫惒剑褂肒nockout微任務(wù)隊列進(jìn)行調(diào)度。 這聽起來非常類似于速率限制,這也有助于防止額外的通知,但延遲更新可以在整個應(yīng)用程序中提供這些好處,而不會增加延遲。 以下是標(biāo)準(zhǔn),延遲和速率限制模式之間通知調(diào)度的不同之處:
- Standard (標(biāo)準(zhǔn))– 通知立即并同步。 通常通知中間值的依賴關(guān)系。
- Deferred (延遲)– 通知異步發(fā)生,緊接在當(dāng)前任務(wù)之后,通常在任何UI重繪之前發(fā)生。
- Rate-limited (速率限制)– 通知發(fā)生在指定的時間段之后(根據(jù)瀏覽器的不同,最小值為2-10 ms)。
啟用延遲更新
延遲更新默認(rèn)關(guān)閉,以提供與現(xiàn)有應(yīng)用程序的兼容性。 要為應(yīng)用程序使用延遲更新,必須在通過設(shè)置以下選項初始化視圖模型之前啟用它:
ko.options.deferUpdates = true;當(dāng)deferUpdates選項打開時,所有observable,computed observable和綁定都將設(shè)置為使用延遲更新和通知。 在開始創(chuàng)建基于Knockout的應(yīng)用程序時啟用此功能意味著您不需要擔(dān)心解決中間值問題,因此可以促進(jìn)更清潔,純粹的反應(yīng)性設(shè)計。 但是,當(dāng)啟用現(xiàn)有應(yīng)用程序的延遲更新時,應(yīng)該注意,因為它會破壞依賴于同步更新或中間值通知的代碼(盡管您可能可以解決這些問題)。
示例:避免多個UI更新
以下是一個有用的示例來演示延遲更新消除中間值的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]);
對特定observable使用延遲更新
即使您不為整個應(yīng)用程序啟用延遲更新,您仍然可以通過專門使某些可觀察項延遲,從中受益。 使用延遲擴展器:
this.data = ko.observableArray().extend({ deferred: true });
現(xiàn)在我們可以將一堆項目推入數(shù)據(jù)數(shù)組,而不必?fù)?dān)心引起過多的UI或計算更新。 延遲擴展器可以應(yīng)用于任何類型的可觀察量,包括可觀察數(shù)組和計算可觀察量。
示例:避免多個Ajax請求
以下模型表示可以作為分頁網(wǎng)格呈現(xiàn)的數(shù)據(jù):
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函數(shù)重新加載currentPageData當(dāng)一個GridViewModel首次實例化時,每當(dāng)pageIndex或pageSize屬性后來改變。
這是非常簡單和優(yōu)雅(和添加更多可觀察的查詢參數(shù),也觸發(fā)刷新自動每當(dāng)他們改變)的,但有一個潛在的效率問題。 假設(shè)您向GridViewModel添加了以下函數(shù),該函數(shù)同時更改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); }
問題是,這將導(dǎo)致兩個Ajax請求:第一個將在您更新pageSize時啟動,第二個將在您更新pageIndex時立即啟動。 這是浪費帶寬和服務(wù)器資源,以及不可預(yù)測的競爭條件的來源。
當(dāng)應(yīng)用于計算的可觀察量時,延遲的延長器也將避免對計算函數(shù)的過度評價。 使用延遲更新可確保對當(dāng)前任務(wù)中的依賴性的任何更改順序只觸發(fā)對計算的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 });
現(xiàn)在,您可以根據(jù)需要更改pageIndex和pageSize,并且Ajax調(diào)用只會在您將線程釋放回JavaScript運行時后發(fā)生一次。
強制延遲通知
雖然延遲,異步通知通常更好,因為更少的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]);
強制延遲可觀察者總是通知訂閱者
當(dāng)any observable的值是原始值(數(shù)字,字符串,布爾值或null)時,只有當(dāng)observable的依賴項設(shè)置為實際上與之前不同的值時,才會通知它的依賴項。 因此,原始值延遲可觀測量只有在當(dāng)前任務(wù)結(jié)束時它們的值實際上不同時才通知。 換句話說,如果一個原始值的延遲的observable被改變?yōu)橐粋€新的值,然后改回原來的值,則不會發(fā)生通知。
要確保始終通知訂閱者更新,即使該值相同,也可以使用notify擴展器:
ko.options.deferUpdates = true; myViewModel.fullName = ko.pureComputed(function() { return myViewModel.firstName() + " " + myViewModel.lastName(); }).extend({ notify: 'always' });

浙公網(wǎng)安備 33010602011771號