翻譯:Knockout 快速上手 - 4: 你需要知道的頂級特性


Knockout 最棒的一個特點就是它的可擴展性。Knockout 存在大量的擴展點,包含大量的工具來創建我們的應用程序。許多開發者除了 Knockout 核心庫之外沒有使用任何其他的腳本庫 ( 甚至包括 jQuery ) 就創建了非常棒的站點。

Subscribables

在創建我們的庫存管理程序的時候,很容易發現在 Knockout 中 Observable 是一個核心對象。在 Observable,ObservableArray 和 Computed Observables 的底層是 Subscribable,Subscribable 是包含三個方法和一個 Subscriptions 數組的對象,這三個方法是:

  • subscribe:這個方法添加一個訂閱到主題對象上,當訂閱的主題發出提醒的時候,訂閱就會被調用,默認的提醒類型是 change。
  • notifySubscribers:這個方法調用所有的訂閱,並且會傳遞一個參數到訂閱的回調方法中。
  • extend:這個方法為主題對象添加一個擴展

下面的代碼演示了前兩個方法的使用,實現了發布和訂閱。

var test = ko.observable();

// create a subscription for the "test-event"

test.subscribe(function (val) {

    console.log(val);

}, test, "test-event");

test.notifySubscribers("Hello World", "test-event");

Knockout 中訂閱的一個很酷的特性是可以混合到任何的 Javascript 對象中,下面的代碼演示了在普通代碼中使用的方式。

// Dummy Subscribable
function PubSub() {
    // inherit Subscribable
    ko.subscribable.call(this);
}

// create an instance of our Subscribable
var pubsub = new PubSub();
// make a subscription
var subscription = pubsub.subscribe(function (val) {
    console.log(val);
}, pubsub, 'test-topic');

pubsub.notifySubscribers("hello world", "test-topic");
// console: "hello world"
// clean up things
subscription.dispose();

在調用可訂閱對象的subscribe 方法之后,我們得到了一個 Subscription 對象,通常開發者可以忽略這個 subscribe 方法返回的訂閱對象,但是需要注意的是通過這個訂閱對象 Subscription ,我們可以釋放訂閱的回調方法和應用中對於回調的引用,直接調用 dispose 方法就可以確信你的程序不會造成內存的泄露。

現在,你已經理解了 Knockout 的一個核心處理機制,Subscribable,我們將學習 Knockout 如何擴展它的用法在其它的模式。

Observables

Knockout 的 Observable 對象是實現訂閱機制的頭等對象,也是 Knockout 庫中最為簡單,也最為強大的部分。下面的代碼演示了實現 Observable 的基本思想。

// Very Simple Knockout Observable Implementation
// ko.observable is actually a function factory

ko.observable = function (initialValue) {

    // private variable to hold the Observable's value
    var _latestValue = initialValue;

    // the actual "Observable" function
    function observable() {
        // one or more args, so it's a Write
        if (arguments.length > 0) {
            // set the private variable
            _latestValue = arguments[0];
            // tell any subscribers that things have changed
            observable["notifySubscribers"](_latestValue);
            return this; // Permits chained assignments
        }

        else { // no args, so it's a Read
            // just hand back the private variable's value
            return _latestValue;
        }
    }

    // inherit from Subscribable
    ko.subscribable.call(observable);

    // return the freshly created Observable function
    return observable;
};

這是 Knockout 中 observable 的一個簡單實現 ( 真正的實現代碼中包含了大量的強壯性處理代碼,驗證,依賴檢測等等 ),從這個實現中可以看到實際上 observable 是一個函數的工廠,當調用這個工廠方法的時候,它生成了一個新的函數。這里實現了一個簡單的 set/get API。如果你在不提供參數的情況下調用返回的函數,就會返回 _initialValue ,如果調用的時候提供了一個參數,就會設置 _initialValue ,並且通知所有的訂閱。

當你創建事件驅動的程序的時候,Observable 可以得到大量的應用。我們可以創建事件驅動的程序架構,僅僅依賴事件 ( 比如按鈕的點擊,或者輸入元素的變化等等 )而不是過程化的方法。

Observable Array

我在前面已經提到過可觀察的數組,而且提高還會深入討論這個特性。前面的示例中已經演示了 Observable 比較簡單,而可觀察的數組也一樣簡單。下面的代碼演示了實現的機制。

// Very Simple Knockout Observable Array Implementation
// function factory for observable arrays
ko.observableArray = function (initialValues) {

    // make sure we have an array
    initialValues = initialValues || [];

    // create a Knockout Observable around our Array
    var result = ko.observable(initialValues);

    // add our Observable Array member functions
    // like "push", "pop", and so forth
    ko.utils.extend(result, ko.observableArray['fn']);

    // hand back the Observable we've created
    return result;

};

如你所見,實際上還是一個可觀察對象,除了這個可觀察對象的值是一個數組而已。另外還添加了匹配數組本地方法的擴展方法。Knockout 的作者已經幫我們完成的這些工作,使得開發者可以像使用普通的數組一樣使用可觀察的數組。

可觀察數組的一個擴展點是它的 fn 屬性,你可以添加自己的函數到這個屬性中,那么就可以使得應用中所有的可觀察數組都提供這個方法,下面的代碼演示了如何添加一個過濾方法來過濾數組中的元素。

ko.observableArray.fn['filter'] = function (filterFunc) {

    // get the array
    var underlyingArray = this();
    var result = [];

    for (var i = 0; i < underlyingArray.length; i++) {
        var value = underlyingArray[i];

        // execute filter logic
        if (filterFunc(value)) {
            result.push(value);
        }
    }
    return result;
};
var list = ko.observableArray([1, 2, 3]);

// filter down the list to only odd numbers
var odds = list.filter(function (item) {
    return (item % 2 === 1);
});

console.log(odds); // [1, 3]

 

使用 fn 函數在許多腳本庫中是常見的模式,包括 jQuery 腳本庫,Knockout 也不例外。fn 也存在於 Observable 中,所以你也可以通過同樣的方式進行擴展。

Computed Observable

計算出的可觀察對象可以說是 Knockout 中最強大的特性。在你學會使用 Observable 和 Observable Array 而不是用其他技術來創建視圖模型之后,已經是一個巨大的進步,但是,更棒的是你可以創建僅僅依賴於其他 Observable 和 Observalbe Array 的屬性,而且在底層屬性發生變化的時候,計算的結果會同時發生變化。

計算出的可觀察對象類似於普通的可觀察對象,除了沒有保存自己的值之外,它提供了一個方法來計算應該返回的值,這個函數僅僅在所依賴的底層對象發生變化的時候重新計算返回值,下面的代碼演示了實現的機制。

var a = ko.observable(1);

var b = ko.observable(2);
var sum = ko.computed(function () {
    var total = a() + b();
    // let's log every time this runs
    console.log(total);
    return total;
});

// console: 3
a(2); // console: 4
b(3); // console: 5
b(3); // (nothing logged)

 

計算出的可觀察對象有着非常有趣和巧妙的實現,你可以自己挖掘一下,初次之外,還有一些需要注意的要點。

首先,計算出的可觀察對象創建了一個依賴列表 ( 或者訂閱 ),當你的計算函數執行的時候,如果你希望依賴於某個對象,就需要保證在計算函數中調用這個 Observable ,下面的代碼演示了如何設置依賴和動態改變計算對象的依賴對象。

var dep1 = ko.observable(1);
var dep2 = ko.observable(2);
var skipDep1 = false;
var comp = ko.computed(function () {

    dep2(); // register dep2 as dependency
    if (!skipDep1) {
        dep1(); // register dep1 as dependency
    }
    console.log('evaluated');
});

// console: evaluated
dep1(99); // console: evaluated
skipDep1 = true;
dep2(98); // console: evaluated
dep1(97); // (nothing logged)

 

一般來說,上面的代碼不是什么好注意,這使得代碼更難調試,使其他的開發人員更加難以直觀地意識到發生的狀況。相反,建議的處理方式是首先從依賴對象中獲取所有的值,然后,使用私有的變量來評估處理邏輯問題。

第二點,這一點使用所有的可觀察對象,訂閱的回調函數僅僅在屬性的值發生變化的時候,每個可觀察對象都擁有一個名為equalityComparer 的屬性,用來檢測屬性之前和之后的值是否不同,如果你注意前面的例子,最后一個調用什么都沒有做,這是因為第二次設置的值還是 3。默認的equalityComparer 實現僅僅比較 javascript 的基礎值,如果可計算對象依賴所有的對象,包括復雜的或者不復雜的,那么,這個函數就會執行多次,下面的代碼演示了這個重要的概念。

var depPrimitive = ko.observable(1);
var depObj = ko.observable({ val: 1 });
var comp = ko.computed(function () {

    // register dependencies
    var prim = depPrimitive();
    var obj = depObj();
    console.log("evaluated");
});

// console: evaluated
depPrimitive(1); // (nothing logged)

var previous = depObj();
depObj(previous); // console: evaluated

重要的是理解這一點,很多 Knockout 的新手創建了復雜的可計算對象,導致應用的性能降低。

最后一點,計算的可觀察對象也可以擁有自己的 set/get ,這一點很有用,因為這允許我們在視圖模型上擁有私有的可觀察對象,可以通過公共的 set/get 類似於普通的可觀察對象一樣使用,下面的代碼演示了這個技術。

var _val = ko.observable(1);

var vm = {
    val: ko.computed({

        read: function () {
            return _val();
        },

        write: function (newVal) {
            _val(newVal);
        }
    })
};

vm.val(2);
console.log(_val()); // console: 2
_val(3);
console.log(vm.val()); // console: 3


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM