Knockout.js隨手記(2)

Halower發表於2013-07-13

計算屬性

   konckout.js的API文件,寫的極為詳細和生動,透過MVVM的運作原理,開發時只需專注於定義ViewModel邏輯,不需耗費心力處理TextBox、Select的onchange、onclick等互動細節,就能達到UI元素與資料天人合一的境界。這一系列的konckout學習筆記,將逐一探討knockout.js在常見網頁情境上的應用。

  Knockout.js隨手記(1)開篇已經示範過最簡單的應用,為<input>與<span>加上data-bind宣告,透過ko.applyBindings()繫結到定義好的ViewModel上,當input改變輸入內容,會立即反應在span。然而,有些場合資料需經加工才能使用,例如: 指定日期格式,將數字相加... 等等,此時ko.computed()便派上用場。

  使用ko.computed()時,最簡單的做法是直接傳入function,在其中引用其他的ViewModel屬性處理運算後傳回結果;因knockout具備強大的依賴關係追蹤能力,能記下你引用了哪些屬性,一旦被引用的屬性來源改變,便會自動呼叫ko.computed()計算新值。 

  範例1

  首先我們建立個ViewModel,然後將使用計算屬性將結果返回給fullName.

 function AppViewModel() {
            this.firstName = ko.observable('Bob');
            this.lastName = ko.observable('Smith');
            this.fullName = ko.computed(function () { return this.firstName() + " " + this.lastName(); }, this);
        }
        ko.applyBindings(new AppViewModel());

現在我們做的事情就是繫結這些值

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

我們來檢視下執行效果:

注意:由於this在不同的範圍內又不同的含義,往往為了避免混淆,我們採用一項流行的約定,就是把這個this指向另一個習慣性的變數(self),我們的程式碼可以修改為如下:

<body>
     <p>First name: <input data-bind="value: firstName"/></p>
     <p>Last name: <input data-bind="value: lastName"/></p>
     <h2>Hello, <span data-bind="text: fullName"/>!</h2>
    <script  type="text/javascript">
        function AppViewModel() {
            var self = this;
            self.firstName = ko.observable('Bob');
            self.lastName = ko.observable('Smith');
            self.fullName = ko.computed(function () { return self.firstName() + " " + self.lastName(); }, this);
        }
        ko.applyBindings(new AppViewModel());
    </script>
</body>
使用self

範例2

    可能你和我一樣在想,既然knockout支援依賴性追蹤特性,那麼,我可以通過更改fullName的值去動態修改first Name和 Last Name嗎?

那麼我們做的工作就是分解FullName的輸入值。

  <p>First name: <input data-bind="value: firstName"/></p>
     <p>Last name: <input data-bind="value: lastName"/></p>
     <h2>Hello, <input data-bind="value: fullName"/>!</h2>
    <script  type="text/javascript">
        function MyViewModel() {
            var self = this;
            self.firstName = ko.observable('Planet');
            self.lastName = ko.observable('Earth');

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

        ko.applyBindings(new MyViewModel());
    </script>

程式碼很簡單,可以說也是最常見的js擷取字串的應用,我們只要注意其中的三個代理功能:read,write,owner就可以,實現的效果如下:

範例3

  這例子其實算是對範例2的一個複習,主要功能是提供金額格式的自動轉換(包括精度和格式)已經垃圾字元的過濾

<p>Enter bid price: <input data-bind="value: formattedPrice"/></p><br/>
    <script  type="text/javascript">
        function MyViewModel() {
            this.price = ko.observable(25.99);

            this.formattedPrice = ko.computed({
                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());
    </script>

    不管使用者什麼時候輸入新價格,輸入什麼格式,text box裡會自動更新為帶有2位小數點和貨幣符號的數值。這樣使用者可以看到你的程式有多聰明,來告訴使用者只能輸入2位小數,否則的話自動刪除多餘的位數,當然也不能輸入負數,因為write的callback函式會自動刪除負號。

我們來檢視下執行效果:  

範例4 過濾並驗證使用者輸入

 <p>Enter a numeric value: <input data-bind="value: attemptedValue"/>
      <span data-bind="visible:lastInputWasValid()">驗證通過!</span>
  </p>
  <div data-bind="visible: !lastInputWasValid()">這不是一個合法的數字!</div>

    <script  type="text/javascript">
        function MyViewModel() {
            this.acceptedNumericValue = ko.observable(123);
            this.lastInputWasValid = ko.observable(true);

            this.attemptedValue = ko.computed({
                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());
    </script>

執行效果:

依賴跟蹤是如何工作的

新手沒必要知道太清楚,但是高階開發人員可以需要知道為什麼依賴監控屬效能夠自動跟蹤並且自動更新UI…

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

 1. 當你宣告一個依賴監控屬性的時候,KO會立即呼叫執行函式並且獲取初始化值。

 2. 當你的執行函式執行的時候,KO會把所有需要依賴的依賴屬性(或者監控依賴屬性)都記錄到一個Log列表裡。

執行函式結束以後,KO會向所有Log裡需要依賴到的物件進行訂閱。訂閱的callback函式重新執行你的執行函式。然後回頭重新執行上面的第一步操作(並且登出不再使用的訂閱)。

3.最後KO會通知上游所有訂閱它的訂閱者,告訴它們我已經設定了新值。

4.所有說,KO不僅僅是在第一次執行函式執行時候探測你的依賴項,每次它都會探測。舉例來說,你的依賴屬性可以是動態的:依賴屬性A代表你是否依賴於依賴屬性B或者C,這時候只有當A或者你當前的選擇B或者C改變的時候執行函式才重新執行。你不需要再宣告其它的依賴:執行時會自動探測到的。

另外一個技巧是:一個模板輸出的繫結是依賴監控屬性的簡單實現,如果模板讀取一個監控屬性的值,那模板繫結就會自動變成依賴監控屬性依賴於那個監控屬性,監控屬性一旦改變,模板繫結的依賴監控屬性就會自動執行。巢狀的模板也是自動的:如果模板X render模板 Y,並且Y需要顯示監控屬性Z的值,當Z改變的時候,由於只有Y依賴它,所以只有Y這部分進行了重新繪製(render)。

範例5

  knocut自動依賴性跟蹤通常不正是您想要。但你有時可能需要控制觀測值將更新計算觀察到的,尤其是如果計算可觀察執行某種操作,如一個Ajax請求。peek函式,可以讓你訪問到監控屬性或計算屬性,而無需建立一個依賴。

ko.computed(function() {
    var params = {
        page: this.pageIndex(),
        selected: this.selectedItem.peek()
    };
    $.getJSON('/Some/Json/Service', params, this.currentPageData);
}, this);

其中selectItem屬性使用了Peek函式,所以計算屬性會隨時監控和更新PageIndex的值,但它忽略更改selectItem。

Computed Observable Reference
A computed observable can be constructed using one of the following forms:

ko.computed( evaluator [, targetObject, options] ) — This form supports the most common case of creating a computed observable.

evaluator — A function that is used to evaluate the computed observable’s current value.
targetObject — If given, defines the value of this whenever KO invokes your callback functions. See the section on managing this for more information.
options — An object with further properties for the computed observable. See the full list below.
ko.computed( options ) — This single parameter form for creating a computed observable accepts a JavaScript object with any of the following properties.

read — Required. A function that is used to evaluate the computed observable’s current value.
write — Optional. If given, makes the computed observable writeable. This is a function that receives values that other code is trying to write to your computed observable. It’s up to you to supply custom logic to handle the incoming values, typically by writing the values to some underlying observable(s).
owner — Optional. If given, defines the value of this whenever KO invokes your read or write callbacks.
deferEvaluation — Optional. If this option is true, then the value of the computed observable will not be evaluated until something actually attempts to access it. By default, a computed observable has its value determined immediately during creation.
disposeWhen — Optional. If given, this function is executed on each re-evaluation to determine if the computed observable should be disposed. A true-ish result will trigger disposal of the computed observable.
disposeWhenNodeIsRemoved — Optional. If given, disposal of the computed observable will be triggered when the specified DOM node is removed by KO. This feature is used to dispose computed observables used in bindings when nodes are removed by the template and control-flow bindings.
A computed observable provides the following functions:

dispose() — Manually disposes the computed observable, clearing all subscriptions to dependencies. This function is useful if you want to stop a computed observable from being updated or want to clean up memory for a computed observable that has dependencies on observables that won’t be cleaned.
extend(extenders) — Applies the given extenders to the computed observable.
getDependenciesCount() — Returns the current number of dependencies of the computed observable.
getSubscriptionsCount() — Returns the current number of subscriptions (either from other computed observables or manual subscriptions) of the computed observable.
isActive() — Returns whether the computed observable may be updated in the future. A computed observable is inactive if it has no dependencies.
peek() — Returns the current value of the computed observable without creating a dependency (see the section above on peek).
subscribe( callback [,callbackTarget, event] ) — Registers a manual subscription to be notified of changes to the computed observable.
Computed Observable相關的函式功能

 

備註:

    本文版權歸大家共用,不歸本人所有,所有知識都來自於官網支援,書本,國內外論壇,大牛分享等等......後續將學習knockout.js的常用功能。

相關文章