鏈式調用&工廠

鏈式調用

精髓在於重用一個初始操做.

能夠把方法的鏈式調用技術寫到本身所寫的整個 js 庫中,把本身喜歡的方法串起來調用.兩個部分:一個建立表明 HTML 元素的對象的工廠,還有一批對這個 HTML 元素執行某些操做的方法.每個這種方法均可以在方法名前附加一個圓點後加入調用鏈中.方法的鏈式調用能夠被視爲選擇網頁上的一個元素並對其進行多個操做的過程.
addEvent($('.example'), 'click', function () {
    // Without chaining:
    $(this).hide();
    setStyle(this, 'color', 'green');
    $(this).show();

    // With chaining;
    $(this).hide().setStyle('color', 'green').show();
});

調用鏈結構

$函數一般會返回一個HTML 元素(的集合):ajax

function $() {
    var eles = [];
    for (var i = 0, len = arguments.length; i < len; ++i) {
        var ele = arguments[i];
        if (typeof ele === 'string') {
            ele = document.getElementById(ele);
        }
        if (arguments.length === 1) {
            return ele;
        }
        eles.push(ele);
    }
    return eles;
}

若是把這個函數改形成一個構造器,把那些元素做爲數組保存在一個實例屬性中,並讓全部定義在構造器函數的 prototype 屬性所指對象中的方法都返回用來調用方法的那個實例的引用,那麼它就具備鏈式調用的能力.
作一下改進:首先把$函數改爲一個工廠方法,負責建立支持鏈式調用的對象,這個函數應該能接受元素數組形式的參數,因此咱們可以使用和原來同樣的公有接口:數組

(function () {
    // Use a private class.
    function _$(els) {
        this.eles = [];
        for (var i = 0, len = els.length; i < len; ++i) {
            var ele = els[i];
            if (typeod ele === 'string') {
                ele = document.getElementById(ele);
            }
            this.eles.push(ele);
        }
    }

    // The public interface remains the same.
    window.$ = function () {
        return new _$(arguments);
    };
})();

因爲全部對象都會繼承其原型對象的屬性和方法,因此咱們可讓定義在原型對象中的那幾個方法都返回用以調用方法的實例對象的引用,這樣就能夠對哪些方法進行鏈式調用.如今在_$這個私有構造函數的 prototype 對象中添加方法:瀏覽器

(function () {
    function _$(eles) {
        // ...
    }
    _$.prototype = {
        each: function (fn) {
            for (var i = 0, len = this.eles.length; i < len; ++i) {
                fn.call(this, this.eles[i]);
            }
            return this;
        },
        hide: function (0 {
            var that = this;
            this.setStyle('display', 'none');
        });
        setStyle: function (prop, val) {
            this.each(function (ele) {
                ele.style[prop] = val;
            });
            return this;
        },
        show: function (0 {
            var that = this;
            this.setStyle('display', 'block');
        });
        return this;
        addEvent: function(type, fn) {
            var add = function (ele) {
                if (window.addEventListener) {
                    ele.addEventList(type, fn, false);
                }
                else if (window.attachEvent) {
                    ele.attachEvnet('on' + type, fn);
                }
            };
            this.each(function (el) {
                add(el);
            });
            return this;
        }
    };
    window.$ = function () {
        return new _$(arguments);
    };
})();

每一個方法的最後一行return this;會講調用方法對象傳給調用鏈上的下一個方法.
jQuery 即是這樣,window 對象或者某個 HTML 元素是調用鏈的錨點,多有操做都掛系在上面.異步

使用回調從支持鏈式調用的方法獲取數據

鏈式調用很適合於賦值器方法,可是對於取值器方法,並不但願方法返回 this.不過使用回調技術能夠返回你想要的數據而不是 this.ide

// Accessor without function callbacks: returning requested data in accessors.
window.API = window.API || function () {

    var name = 'Hello world';
    // Privilleged mutator
    this.setName = function(newName) {
        name = newName;
        return this;
    };
    // Privileged accessor method.
    this.getName = function () {
        return name;
    };

};

// Implementation code
var o = new API;
console.log(o.getName()); // Displays 'Hello world'.
console.log(o.setName('nanci').getName()); // Display 'nanci'

// Accessor with function callbacks.
window.API2 = window.API2 || function () {

    var name = 'Hello world';
    // Privilleged mutator
    this.setName = function(newName) {
        name = newName;
        return this;
    };
    // Privileged accessor method.
    this.getName = function (callback) {
        callback.call(this, name);
        return this;
    };

}

// Implementation code
var o2 = new API2;
o2.getName(console.log).setName('nanci').getName(console.log); // Displays 'Hello world' and then display 'nanci'

小結

使用鏈式調用能夠避免屢次重複使用一個對象變量,減小代碼量.
若是想讓類的接口保持一致,讓取值器像賦值器那樣也支持鏈式調用,那麼可使用回調.模塊化

----------another part----------函數

工廠

一個類或者對象中可能包含別的對象.在建立這種成員對象時,可使用 new 關鍵字或者類構造函數.可是會致使兩個類產生依賴.工廠模式就是用來消除這種依賴.他使用一個方法來決定究竟要實例化哪一個具體的類.簡單的工廠使用一個類(一般是單體)來生成實例,複雜的工廠模式使用子類來決定一個成員變量應該是哪一個具體的類的實例.

簡單工廠

若是你想開幾個自行車商店,每一個店都有幾種型號的自行車出售,用一個類表示:post

// BicycleShop class.

var BicycleShop = function () {};
BicycleShop.prototype = {
    sellBicycle: function(model) {
        var bicycle;

        switch(model) {
            case 'The Speedter':
                bicycle = new Speedter();
                break;
            case 'The Lowrider':
                bicycle = new Lowrider();
                break;
            case 'The Comfort Cruiser':
            default:
                bicycle = new ComfortCruiser();
        }

        Interface.ensureImplements(bicycle, Bicycle);
        bicycle.assemble();
        bicycle.wash();

        return bicycle;
    }
};

sellBicycle 方法根據所要求的自行車型號用 switch 語句建立一個自行車的實例.各類型號的自行車實例能夠互換使用,由於他們都實現了 Bicycle 接口(接口在工廠中很重要,若是不對對象進行某種類型檢查以其確保其實現了必須的方法,那麼工廠模式並不能帶來什麼好處).ui

// The Bicycle interface.

var Bicycle = new Interface('Bicycle', ['assemble', 'wash', 'ride', 'repair']);

// Speedster class.
var Speedster = function () { // implement Bicycle
    ...
};
Speedster.prototype = {
    assemble: function () {
        ...
    },
    wash: function () {
        ...
    },
    ride: function () {
        ...
    },
    repair: function () {
        ...
    }
};

要出售某種型號自行車,只須要調用 sellBicycle 方法便可:this

var californiaCruisers = new BicycleShop();
var yourNewBike = californiaCruisers.sellBicycle('The Speedster');

若是你想在供貨目錄中加入一款新車型,更好的解決辦法是把 sellBicycle 方法中"建立新實例"這部分工做轉交給一個簡單工廠對象.

// BicycleFactory namespace.

var BicycleFactory = {
    createBicycle: function (model) {
        var bicycle;

        switch(model) {
            case 'The Speedter':
                bicycle = new Speedter();
                break;
            case 'The Lowrider':
                bicycle = new Lowrider();
                break;
            case 'The Comfort Cruiser':
            default:
                bicycle = new ComfortCruiser();
        }

        Interface.ensureImplements(bicycle, Bicycle);
        return bicycle;
    }
};

BicycleFactory 是一個單體,用來把 createBicycle 方法封裝在一個命名空間中,這個方法返回一個實現了 Bicycle接口的對象,而後能夠對其進行組裝和清洗:

// BicycleShop class, improved.

var BicycleShop = function () {};
BicycleShop.prototype = {
    sellBicycle: function (model) {
        var bicycle = BicycleFactory.createBicycle(model);

        bicycle.assemble();
        bicycle.wash();

        return bicycle;
    }
};

這個 BicycleFactory 對象能夠供各類類用來建立新的自行車實例.有關可供車型的全部信息都集中在一個地方管理,因此添加更多車型很容易:

// BicycleFactory namespace, with more models.

var Bicycle: function (model) {

    var bicycle;

    switch (model) {
        case 'The Speedster':
            bicycle = new Speedster();
            break;
        case 'The Lowrider':
            bicycle = new Lowrider();
            break;
        case 'The Flatlander':
            bicycle = new Flatlander();
            break;
        case 'The ComfortCruiser':
            bicycle = new ComfortCruiser();
    }

    Interface.ensureImplements(bicycle, Bicycle);
    return bicycle;
}

這是一個簡單工廠的例子,他把成員對象的建立工做交給一個外部對象,這個外部對象能夠是一個簡單的命名空間,也能夠是一個類的實例.

示例: XHR 工廠

用 Ajax 技術發起異步請求是如今 Web 開發的一個常見任務.用於發起請求的對象是某種類的實例,具體是哪一種類取決於用戶的瀏覽器.若是代碼中須要屢次執行 ajax 請求,那麼能夠把建立這種對象的代碼提取到一個類中,並建立一個包裝器來包裝在實際發起請求時所要經歷的一系列步驟,簡單工廠很是適合該場合,根據瀏覽器特性生成一個 XMLHttpRequest 或者 ActiveXObject 實例.

// AjaxHandler interface.
var AjaxHandler = new Interface('AjaxHandler', ['request', 'createXhrObject']);

// SimpleHandler class.

var SimleHandler = function () {}; // implements AjaxHandler
SimpleHandler.prototype = {
    request: function (method, url, callback, postVars) {
        var xhr = this.createXhrObject();
        xhr.onreadystatechange = function () {
            if (xhr.readyState !== 4) return;
            (xhr.status === 200) ? callback.success(xhr.responseText) : callback.failure(xhr.status);
        };
        xhr.open(method, url, true);
        if (method !== 'POST') {
            postVars = null;
        }
        xhr.send(postVars);
    },
    createXhrObject: function () { // Factory method.
        var methods = [
            function () {
                return newXMLHttpRequest();
            },
            function () {
                return new ActiveXObject('Msxml2.XMLHTTP');
            },
            function () {
                return new ActiveXObject('Microsoft.XMLHTTP');
            }
        ];

        for (var i =0, len = methods.length; i < len; i++) {
            try {
                methods[i]();
            }
            catch(e) {
                continue;
            }

            //If we reach this point, method[i] worked.
            this.createXhrObject = methods[i]; //Memoize the method.
            return methods[i];
        }

        // If we reach this point, none of the methods worked.
        throw new Error('SimpleHandler: Could not create an XHR object.');
    }
}

主要好處在於消除對象間的耦合,經過使用工廠方法而不是 new 關鍵字及具體類,你能夠把全部實例化代碼集中在一個位置.能夠大大簡化更換所用的類或者在運行期間動態選擇全部的類的工做.在派生子類時也更靈活.能夠先建立一個抽象的超類,而後在子類中建立工廠方法,從而把成員對象的實例化推遲到更專門化的子類中進行.
全部這些好處都和麪向對象設計的兩條原則相關: 弱化對象間的耦合:防止代碼的重複.在一個方法中進行類的實例化,能夠消除重複性的代碼.這是在用一個對接口的調用取代一個具體的實現.這些都有助於模塊化代碼.

不能把工廠方法當萬金油,而把普通函數扔在以便.若是根本不可能另外換用一個類或者不須要在運行期間在一系列類的選擇,那麼就不該該使用工廠方法.大多數最好使用 new 關鍵字和構造函數公開進行實例化,這樣代碼會更簡單易讀..一眼就看到調用的構造函數,沒必要去查看某個工廠方法去知道實例化的是什麼類.

小結

相關文章
相關標籤/搜索