速率限制可觀察通知
原文:Rate-limiting observable notifications
中英文名詞對照:
- 可觀察--observable
- 計算可觀察--computed observable
- 速率限制--rate-limit
注意:這個速率限制API在Knockout 3.1.0版本時被加進來,對先前的版本,使用節流擴充器(throttle extender)提供類似的功能。
一般地,一個observable被改變後立即通知它的訂閱者,以緻任何計算可觀察(computed observables)或依賴該可觀察的綁定被同步更新。但是速率限制(rateLimit)擴充器促使一個可觀察壓制和延遲改變通知一段指定的時間。一個速率限制可觀察是以異步更新依賴。
速率限制擴充器能被應用到任意類型的可觀察,包括可觀察數組(observable arrays)和計算可觀察(computed observables)。速率限制主要的用例是:
- 在某個延遲後響應
- 組合多個改變到一個單一的更新
應用速率限制擴充器
速率限制支援兩個參數格式:
// Shorthand: Specify just a timeout in milliseconds
someObservableOrComputed.extend({ rateLimit: 500 });
// Longhand: Specify timeout and/or method
someObservableOrComputed.extend({ rateLimit: { timeout: 500, method: "notifyWhenChangesStop" } });
當通知觸發時,上述方法可選控制如下,接收下面的值:
1、 notifyAtFixedRate-- 假如沒有其他限定的預設值。這時通知在從第一次改變到可觀察期間(或者初始時或者從先前通知開始)發生。
2、 notifyWhenChangesStop--在指定時期後 可觀察沒有改變時發生。每次 可觀察改變,計時器被重置,是以,假如該 可觀察持續不斷地改變,比逾時期還頻繁時,通知不發生。
例1:基礎
考慮下面代碼中的可觀察:
var name = ko.observable('Bert');
var upperCaseName = ko.computed(function() {
return name().toUpperCase();
});
正常地,假如你象下面這樣改變名字:
name('The New Bert');
那麼upperCaseName将被立即重計算,在你的下一行代碼運作之前。但是假如你使用 rateLimit來代替 name定義,見下面的代碼:
var name = ko.observable('Bert').extend({ rateLimit: 500 });
那麼當 name改變時, upperCaseName将不被立即重新計算,而是在通知它的新值到 upperCaseName之前, name将等500毫秒(半秒鐘),而後重新計算它的值。不管 name在500ms期間改變多少次, upperCaseName将僅僅使用最近的值更新一次。
例子2:當使用者停止打字時做一些事情
在這個線上例子中,有一個 instantaneousValue可觀察,當你按下一個鍵時會立即反應。這個被包裹在一個 delayedValue 計算可觀察配置裡面,僅僅當停止400毫秒後通知改變,使用 notifyWhenChangesStop速率限制方法。
試試這個( 譯者注:這裡該例子不可運作,僅做展示):
Type stuff here:
Current delayed value: sdfssdsdfssdfdsffffff
Stuff you have typed:
- sdfssd
- sdfssdsdfs
- sdfssdsdfssdfdsffffff
源代碼:View
<p>Type stuff here: <input data-bind='value: instantaneousValue,
valueUpdate: ["input", "afterkeydown"]' /></p>
<p>Current delayed value: <b data-bind='text: delayedValue'> </b></p>
<div data-bind="visible: loggedValues().length > 0">
<h3>Stuff you have typed:</h3>
<ul data-bind="foreach: loggedValues">
<li data-bind="text: $data"></li>
</ul>
</div>
源代碼:View model
function AppViewModel() {
this.instantaneousValue = ko.observable();
this.delayedValue = ko.pureComputed(this.instantaneousValue)
.extend({ rateLimit: { method: "notifyWhenChangesStop", timeout: 400 } });
// Keep a log of the throttled values
this.loggedValues = ko.observableArray([]);
this.delayedValue.subscribe(function (val) {
if (val !== '')
this.loggedValues.push(val);
}, this);
}
ko.applyBindings(new AppViewModel());
說明(譯者注):
假設timeout設定時間為t,則:
- method: "notifyWhenChangesStop"的作用是,當定時器還未到期時,鍵入事件将定時器重置清零,如果經過t時刻仍然沒有鍵入,則觸發通知事件;
- 若不使用 method: "notifyWhenChangesStop",則定時器不清零,每到t時刻,就觸發通知事件,是以不超過t時刻,必然觸發一次通知。
例子3:避免多重Ajax請求
下面的模型展示了你能作為一個頁表(paged grid)繪制的資料:
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);
}
因為該 計算可觀察求 pageIndex和 pageSize值, 它變得依賴它們兩個。是以,當一個 GridViewModel首先被執行個體化,并且無論什麼時候 pageIndex或 pageSize屬性在後來被改變時,這個代碼将使用 jQuery's $.getJSON function 來重新加載 currentPageData。
這是非常簡單和簡潔的(增加更多可觀察查詢參數無論什麼時候改變也觸發一個自動重新整理也沒有問題),但是有一個潛在的效率問題,假定你增加下面的函數到 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時立即開始。這是一個帶寬和伺服器資源的浪費,一個不可預測的競争條件的根源。
當你應用到 計算可觀察時, rateLimit擴充器也将避免額外的計算函數求值。使用一個短的速率限制期(例如0毫秒)確定任何對依賴同步改變的序列将觸發僅僅一次你的計算可觀察的求值。例如:
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({ rateLimit: 0 });
現在你能改變pageIndex和pageSize無論多少次,這個Ajax都将僅僅調用一次,在你釋出你的線程到JavaScript背景運作時。
計算可觀察的特别條件
對一個計算可觀察來說,當一個計算可觀察的依賴改變而不是值改變時,速率限制被觸發。計算可觀察直到它的值被真正改變才重新求值——在這個改變通知發生期後,或者當這個計算可觀察值被直接通路。假如你需要通路這個計算的最近的求值,你可以使用peek方法這樣做。
強迫速率限制可觀察總是通知訂閱者
當任何 可觀察值是原生類型(number,string,boolean 或null)時,僅僅當它被設定為一個真正的不同于以前的值時, 可觀察的依賴才被預設通知。是以,在這個時期結束時,原生值 速率限制 可觀察通知僅僅當它們的值是真正不同,換句話說,假如一個原生值速率限制可觀察被改變到一個新值,然後在這個時期結束又改回原始值,通知不發生。
假如你想確定訂閱者總是被通知更新,即使它的值是一樣的,你應該使用除了 rateLimit之外的 notifiy擴充器:
myViewModel.fullName = ko.computed(function() {
return myViewModel.firstName() + " " + myViewModel.lastName();
}).extend({ notify: 'always', rateLimit: 500 });
與throttle(節流)擴充器的比較
假如你想從已經廢棄的 throttle擴充器移植代碼,你應該注意接下的方法, rateLimit擴充器是不同于 throttle擴充器的。
當使用 rateLimit:
1、寫到可觀察是不延遲的;可觀察的值能被正确更新。對可寫的計算可觀察,這意味着寫函數總是被正确運作。
2、所有 change通知被延遲,包括當手工調用 valueHasMuted。這意味着你不能使用 valueHasMutated來強迫一個速率限制可觀察去通知一個沒有改變的值。
3、預設速率限制方法是不同于 throttle算法的。使用notifyWhenchangesStop方法比對throttle行為。
4、速率限制計算可觀察求值不是速率限制;假如你讀它的值,它将重新求值。