(一)Knockout 計算屬性

Computed Observables

若是您有一個用於firstName的 observable 對象,和一個用於lastName的可觀察對象,而且您想顯示全名,該怎麼辦?這就是計算可觀察性的用武之地——這些函數依賴於一個或多個其餘可觀察性,而且當這些依賴關係中的任何一個發生變化時,都會自動更新。
例如,給定如下視圖模型類,javascript

function AppViewModel() {
    this.firstName = ko.observable('Bob');
    this.lastName = ko.observable('Smith');
}

您能夠添加一個 computed observable 來返回全名:html

function AppViewModel() {
    // ... 讓 firstName 和 lastName 不變 ...
 
    this.fullName = ko.computed(function() {
        return this.firstName() + " " + this.lastName();
    }, this);
}

如今您能夠將UI元素綁定到它,例如:java

The name is <span data-bind="text: fullName"></span>

1.1 依賴鏈是有效的

固然,若是您願意,您能夠建立整個 computed observables 鏈。例如,你可能有:程序員

  • 表示一組 itemsobservable 的稱爲項的 items
  • 另外一個 observable 稱爲selectedIndexes,它存儲用戶選擇的項索引
  • 一個名爲selectedItemscomputed observable,返回與所選索引對應的項對象數組
  • 另外一個computed observable,返回truefalse,這取決於selectedItems是否具備某些屬性(如new或unsaved)。一些UI元素,好比按鈕,可能會基於這個值啓用或禁用。

itemsselectedIndexes的更改將波及computed observables鏈,而computed observables鏈又將更新綁定到它們的任何UI元素。c#

1.2 管理 ‘this’

ko.computed的第二個參數(咱們在上面的例子中經過this在哪裏)在計算 computed observable 時定義了this值。若是不傳遞它,就不可能引用this.firstName()this.lastName()。經驗豐富的JavaScript程序員會認爲這是顯而易見的,但若是您仍然在學習JavaScript,這可能看起來很奇怪。(像c#和Java這樣的語言從不指望程序員爲此設置值,可是JavaScript會,由於它的函數自己在默認狀況下不屬於任何對象。)數組

一種簡化事物的流行慣例
也有一種通用的簡化方式,即將this在構造函數一開始就賦給另外一個變量,這樣在以後須要用到this的部分便可以經過調用另外一個變量來實現:閉包

function MyViewModel() {
  var self = this;

  self.firstName = ko.observable("Chiaki");
  self.lastName = ko.observable("Izumi");
  self.fullName = ko.computed(function() {
    return self.firstName()+ " " + self.lastName();
  });
}

我的分析,之因此可以經過這種方式來簡化,跟javascript中的this機制有關,可能在javascript中每當遇到一個this的時候就分析當前的object究竟是哪一個,進而對this進行替代,可是進入到ko.computed函數裏面以後,因爲函數並不算是object的一部分,this的值也就再也不是當前的object(MyViewModel),而變成了window,使用self之後就涉及到閉包的問題了,使得self的值並不會更改,這個能夠留做之後研究。app

1.3 Pure computed observables

若是computed observable知識基於一些observable的簡單計算的話(其求值器不直接修改其餘對象或狀態),使用pureComputed會比computed更好,以下:less

self.fullName = ko.pureComputed(function() {
   return self.firstName()+ " " + self.lastName();
 })

1.4 強制計算的監控對象始終通知訂閱者

咱們也能夠對computed或是pureComputed進行強制訂閱,以下:
當 computed observable 返回一個基本值(數字、字符串、布爾值或null)時,一般只有當值實際更改時纔會通知可觀察對象的依賴關係。函數

this.fullName = ko.pureComputed(function() {
        return this.firstName() + " " + this.lastName();
      }, this).extend({notify: "always"});

1.5 延遲和/或禁止更改通知 rateLimit

一樣的,能夠經過調用extend方法中的rateLimit屬性來指定響應的延時。

// Ensure updates no more than once per 50-millisecond period
myViewModel.fullName.extend({ rateLimit: 50 });

1.6 肯定一個屬性是不是計算監控的 isComputed

for (var prop in myObject) {
    if (myObject.hasOwnProperty(prop) && !ko.isComputed(myObject[prop])) {
        result[prop] = myObject[prop];
    }
}

在某些時候,咱們可能須要斷定某個變量究竟是不是computed observable,這時能夠用到ko.isComputed來進行判斷,相似的方法還包括isObservablem,isWritableObservable等,其中

  • isObservable對於observables、observable arrays、computed observables均會返回true;
  • isWritableObservable對於observables、observable arrays、writable computed observables均會返回true,這部分能夠留做之後研究。

1.7 當計算監控屬性(computed observables)僅在UI中使用時

參考:

官網,computedObservables
CharlieYuki,KnockoutJs學習筆記(三),Using computed observables

2 可寫計算監控屬性 Writable computed observables

  初學者可能但願跳過這一節——可寫的計算監控屬性是至關高級的,在大多數狀況下是沒必要要的。

  一般,計算監控屬性的值是從其餘監控屬性計算出來的,所以是隻讀的。那麼,可能看起來使人驚訝的是,使計算出的監控屬性數據可寫是可能的。您只須要提供您本身的回調函數,它對寫入的值執行一些合理的操做。

  您可使用與常規可寫可計算可觀察對象徹底同樣的可寫可計算可觀察對象,並使用您本身的自定義邏輯攔截全部的讀和寫。與observables同樣,您可使用連接語法將值寫入模型對象上的多個可觀察或計算的可觀察屬性。例如:

myViewModel.fullName('Joe Smith').age(50).

例1:分解用戶的輸入

返回到經典的「first name + last name = full name」 例子上,你可讓事情調回來看: 讓依賴監控屬性fullName可寫,讓用戶直接輸入姓名全稱,而後輸入的值將被解析並映射寫入到基本的監控屬性firstName和lastName上:

var viewModel = {
    firstName: ko.observable("Planet"),
    lastName: ko.observable("Earth")
};

viewModel.fullName = ko.dependentObservable({

    read: function () {
        return this.firstName() + " " + this.lastName();
    },

    write: function (value) {
        var lastSpacePos = value.lastIndexOf(" ");
        if (lastSpacePos > 0) { // Ignore values with no space character
            this.firstName(value.substring(0, lastSpacePos)); // Update "firstName"
            this.lastName(value.substring(lastSpacePos + 1)); // Update "lastName"
        }
    },
    owner: viewModel
});

這個例子裏,寫操做的callback接受寫入的值,把值分離出來,分別寫入到「firstName」和「lastName」上。 你能夠像普通狀況同樣將這個view model綁定到DOM元素上,以下:

<p>First name: <span data-bind="text: firstName"></span></p>
<p>Last name: <span data-bind="text: lastName"></span></p>
<h2>Hello, <input data-bind="value: fullName"/>!</h2>


這是一個Hello World 例子的反例子,姓和名都不可編輯,相反姓和名組成的姓名全稱倒是可編輯的。

上面的view model演示的是經過一個簡單的參數來初始化依賴監控屬性。你能夠給下面的屬性傳入任何JavaScript對象:

  • read — 必選,一個用來執行取得依賴監控屬性當前值的函數。
  • write — 可選,若是聲明將使你的依賴監控屬性可寫,別的代碼若是這個可寫功能寫入新值,經過自定義邏輯將值再寫入各個基礎的監控屬性上。
  • owner — 可選,若是聲明,它就是KO調用read或write的callback時用到的this。查看「管理this」獲取更新信息。

例2:全選/反選

當向用戶顯示一個可選擇項列表時,包含一個選擇或取消選擇全部項的方法一般頗有用。這能夠很是直觀地用一個布爾值表示,該值表示是否選擇了全部項。當設置爲true,它將選擇全部項目,當設置爲false,它將取消選擇他們。

Source code: View

<div class="heading">
    <input type="checkbox" data-bind="checked: selectedAllProduce" title="Select all/none"/> Produce
</div>
<div data-bind="foreach: produce">
    <label>
        <input type="checkbox" data-bind="checkedValue: $data, checked: $parent.selectedProduce"/>
        <span data-bind="text: $data"></span>
    </label>
</div>

Source code: View model

function MyViewModel() {
    this.produce = [ 'Apple', 'Banana', 'Celery', 'Corn', 'Orange', 'Spinach' ];
    this.selectedProduce = ko.observableArray([ 'Corn', 'Orange' ]);
    this.selectedAllProduce = ko.pureComputed({
        read: function () {
            // Comparing length is quick and is accurate if only items from the
            // main array are added to the selected array.
            return this.selectedProduce().length === this.produce.length;
        },
        write: function (value) {
            this.selectedProduce(value ? this.produce.slice(0) : []);
        },
        owner: this
    });
}
ko.applyBindings(new MyViewModel());

例3:Value轉換器

有時候你可能須要顯示一些不一樣格式的數據,從基礎的數據轉化成顯示格式。好比,你存儲價格爲float類型,可是容許用戶編輯的字段須要支持貨幣單位和小數點。你能夠用可寫的依賴監控屬性來實現,而後解析傳入的數據到基本 float類型裏:

Source code: View

<div>Enter bid price: <input data-bind="value: formattedPrice"/></div>
<div>(Raw value: <span data-bind="text: price"></span>)</div>

Source code: View model

function MyViewModel() {
    this.price = ko.observable(25.99);
 
    this.formattedPrice = ko.pureComputed({
        read: function () {
            return '$' + this.price().toFixed(2);
        },
        write: function (value) {
            // Strip out unwanted characters, parse as float, then write the 
            // raw data back to the underlying "price" observable
            value = parseFloat(value.replace(/[^\.\d]/g, ""));
            this.price(isNaN(value) ? 0 : value); // Write to underlying storage
        },
        owner: this
    });
}
 
ko.applyBindings(new MyViewModel());

如今,每當用戶輸入一個新價格時,文本框都會更新,以顯示它的格式是貨幣符號和兩位小數,不管輸入的格式是什麼。這給了一個很好的用戶體驗,由於用戶看到了軟件如何將他們的數據輸入理解爲一個價格。他們知道他們不能輸入超過兩位小數,由於若是他們嘗試輸入,額外的小數位就會被刪除。相似地,它們不能輸入負值,由於write回調去掉了任何負號。

例4:過濾並驗證用戶輸入

例1展現的是寫操做過濾的功能,若是你寫的值不符合條件的話將不會被寫入,忽略全部不包括空格的值。

再多走一步,你能夠聲明一個監控屬性 isValid 來表示最後一次寫入是否合法,而後根據真假值顯示相應的提示信息。稍後仔細介紹,先參考以下代碼:

Source code: View

<div>Enter a numeric value: <input data-bind="textInput: attemptedValue"/></div>
<div class="error" data-bind="visible: !lastInputWasValid()">That's not a number!</div>
<div>(Accepted value: <span data-bind="text: acceptedNumericValue"></span>)</div>

Source code: View model

function MyViewModel() {
    this.acceptedNumericValue = ko.observable(123);
    this.lastInputWasValid = ko.observable(true);
 
    this.attemptedValue = ko.pureComputed({
        read: this.acceptedNumericValue,
        write: function (value) {
            if (isNaN(value))
                this.lastInputWasValid(false);
            else {
                this.lastInputWasValid(true);
                this.acceptedNumericValue(value); // Write to underlying storage
            }
        },
        owner: this
    });
}
 
ko.applyBindings(new MyViewModel());

如今,acceptedNumericValue 將只接受數字,其它任何輸入的值都會觸發顯示驗證信息,而會更新acceptedNumericValue

備註:上面的例子顯得殺傷力太強了,更簡單的方式是在<input>上使用jQuery Validationnumber class。Knockout能夠和jQuery Validation一塊兒很好的使用,參考例子:grid editor 。固然,上面的例子依然展現了一個如何使用自定義邏輯進行過濾和驗證數據,若是驗證很複雜而jQuery Validation很難使用的話,你就能夠用它。

3 依賴跟蹤如何工做的

新手不必知道太清楚,可是高級開發人員能夠須要知道爲何依賴監控屬性可以自動跟蹤而且自動更新UI…

事實上,很是簡單,甚至說可愛。跟蹤的邏輯是這樣的:

  1. 當你聲明一個依賴監控屬性的時候,KO會當即調用執行函數而且獲取初始化值
  2. 當你的執行函數運行的時候,KO會把全部須要依賴的依賴屬性(或者監控依賴屬性)都記錄到一個Log列表裏。
  3. 執行函數結束之後,KO會向全部Log裏須要依賴到的對象進行訂閱。訂閱的callback函數是從新運行你的執行函數。而後回頭從新執行上面的第一步操做(而且註銷再也不使用的訂閱)。
  4. 最後KO會通知上游全部訂閱它的訂閱者,告訴它們我已經設置了新值。

因此,KO不只僅是在第一次執行函數執行時候探測你的依賴項,每次它都會探測。舉例來講,你的依賴屬性能夠是動態的:依賴屬性A表明你是否依賴於依賴屬性B或者C,這時候只有當A或者你當前的選擇B或者C改變的時候執行函數才從新執行。你不須要再聲明其它的依賴:運行時會自動探測到的。

另一個技巧是:一個模板輸出的綁定是依賴監控屬性的簡單實現,若是模板讀取一個監控屬性的值,那模板綁定就會自動變成依賴監控屬性依賴於那個監控屬性,監控屬性一旦改變,模板綁定的依賴監控屬性就會自動執行。嵌套的模板也是自動的:若是模板X render模板 Y,而且Y須要顯示監控屬性Z的值,當Z改變的時候,因爲只有Y依賴它,因此只有Y這部分進行了從新繪製(render)。

4 PureComputed

Pure computed observables相對於通常的computed observables,在性能和存儲上有優點,這是由於pure computed observables在不存在訂閱者的時候是不會保持訂閱關係的。這也使得pure computed observables有以下兩點特色:

  • 能夠防止沒有被訂閱的computed observables的存儲泄露。
  • 能夠下降因重複計算未被訂閱的computed observables而形成的運算過載。

一個pure computed observable可以依據它是否擁有訂閱者而自動地在兩種狀態下切換:
1.當不存在訂閱者的時候,pure computed observable會進入休眠狀態,此時的它,會關閉全部依賴於它的訂閱關係,同時也不會再追蹤它所關聯的observables。一旦處於休眠狀態的computed observable的值被讀取的話,它就須要從新計算以便以確保值得正確性。
2.當它擁有訂閱者的時候,pure computed observable會進入監聽狀態。一旦進入監聽狀態,它會當即調用它的function和訂閱程序來追蹤它所關聯的observables。在這種狀態下,pure computed observables和普通的computed observables無異。更爲詳細的內容需參考How dependency tracking works部分。

按照文檔的說明,選擇pure computed observables有兩條原則。一是computed observable在運算的時候不能產生反作用(不能對其餘的observables產生影響);二是computed observable的值應該僅僅依賴於它所關聯的observables的值,而不是其餘隱含的信息。

Pure computed observables有兩種定義方式:

this.fullName = ko.pureComputed(function() {
    return this.firstName() + " " + this.lastName();
}, this);

或者

this.fullName = ko.computed(function() {
    return this.firstName() + " " + this.lastName();
}, this, { pure: true });

4.1 參考:

官網,Pure computed observables

Computed Observable 參考

下面的文檔描述瞭如何構造和使用 Computed Observable。

構建一個 Computed Observable

  1. ko.computed( evaluator [, targetObject, options] ) — 此表單支持建立 computed observable 的最多見狀況。
    • evaluator — 用於computed observable 值的當前值的函數
    • targetObject — 若是給定,則在KO調用回調函數時定義this的值。有關更多信息,請參見管理this功能的部分。
    • options — An object with further properties for the computed observable. See the full list below.
  2. ko.computed( options ) — This single parameter form for creating a computed observable accepts a JavaScript object with any of the following properties.
相關文章
相關標籤/搜索