天天看點

JavaScript設計模式

設計模式的意義

  • 模式為常見問題提供了行之有效的解決方案:模式提供了解決特定問題的優化模闆;
  • 模式旨在重用:它們具備通用性,适合于各種問題。

《設計模式:可複用的面向對象軟體基礎》的作者『四人幫』将設計模式寬泛地劃分為以下幾類:

  • 建立型設計模式:該模式處理的是用于建立對象的各種機制,這種模式着眼于優化的或更可控的對象建立機制;
  • 結構型設計模式:該模式考慮的是對象的組成以及對象彼此之間的關系,其意圖在于将系統變化對整個對象關系所造成的影響降低到最小;
  • 行為型設計模式:該模式關注的是對象之間的依賴關系以及通信。

這幾類設計模式又有更詳細的分類:

建立型模式:

  • 工廠方法
  • 抽象工廠
  • 建造者
  • 原型
  • 單例

結構型模式:

  • 擴充卡
  • 橋接
  • 組合
  • 裝飾器
  • 外觀
  • 享元
  • 代理

行為型模式:

  • 解釋器
  • 模闆方法
  • 責任鍊
  • 指令
  • 疊代器
  • 中介者
  • 備忘錄
  • 觀察者
  • 狀态
  • 政策
  • 通路者

而針對于 JavaScript,則有更為特殊的設計模式。

https://juejin.im/entry/5b2929b351882574bd7edddd#%E5%91%BD%E5%90%8D%E7%A9%BA%E9%97%B4%E6%A8%A1%E5%BC%8F 命名空間模式

命名空間能夠減少程式建立的全局變量的數量,有助于避免命名沖突或過多的名稱字首。

命名空間的思路是為應用程式或庫建立一個全局對象,将所有的其他對象和函數全部添加到該對象中去,而不是去污染全局作用域。

我們可以建立單個全局對象,然後将所有的函數和對象作為該全局對象的一部分:

var FACTORY = FACTORY || {};
FACTORY.Car = function () {};
FACTORY.Bike = function () {};
FACTORY.engines = 1;           

全局命名空間對象名習慣上全部都是大寫。

命名空間模式既可以限制全局變量,又能夠為代碼添加命名空間,看起來似乎是一個不錯的方法,但多少有點繁瑣,你得在每個變量和函數前面加上命名空間字首,需要輸入更多的内容,代碼也變得啰嗦。另外,單個全局執行個體意味着任何代碼都能夠修改該全局執行個體,進而影響到其他功能。

https://juejin.im/entry/5b2929b351882574bd7edddd#%E6%A8%A1%E5%9D%97%E6%A8%A1%E5%BC%8F 子產品模式

子產品模式有助于維持代碼清晰的獨立性以及條理性。

子產品可以将較大的程式分割成較小的部分,賦予其各自的命名空間。這一點非常重要,因為一旦将代碼劃分成子產品,這些子產品就能夠在其他地方重新使用。精心設計的子產品接口能夠使代碼易于重用和擴充。

JavaScript 提供了靈活的函數和對象,可以輕松地建立出健壯的子產品系統。函數作用域有助于建立子產品内部使用的命名空間,對象可以用來儲存導出的值。

子產品可以模拟類的概念,它使得我們能夠在對象中加入公共/私有方法和變量,但最重要的是,子產品能夠将它們同全局作用域隔離開。變量和函數都被限制在了子產品作用域中,因而也就自動避免了與使用相同名稱的其他腳本産生命名沖突。

子產品模式的另一個有點在于它隻暴露了公共 API,其他所有與内部實作相關的細節都以私有狀态保留在子產品的閉包中。

以下是一種子產品模式的實作方法:

var revealingExample = function() {
    var privateOne = 1;
    function privateFn() {
        console.log('privateFn called');
    }
    var publicTwo = 2;
    function publicFn() {
        publicFnTwo();
    }
    function publicFnTwo() {
        privateFn();
    }
    function getCurrentState() {
        return 2;
    }
    // 通過配置設定共有指針來暴露私有變量
    return {
        setup: publicFn,
        count: publicTwo,
        increaseCount: publicFnTwo,
        current: getCurrentState()
    };
}();
console.log(revealingExample.current); //2
revealingExample.setup(); //調用 privateFn           

https://juejin.im/entry/5b2929b351882574bd7edddd#ES6-%E6%A8%A1%E5%9D%97 ES6 子產品

ES6 子產品的文法類似于 CommonJS,另外還支援異步裝載以及可配置的子產品裝載:

// json_processor.js
function processJSON(url) {
    ...
}
export function getSiteContent(url) {
    return processJSON(url);
}
// main.js
import { getSiteContent } from "json_processor.js";
content = getSiteContent("http://google.com/")           

ES6 的導出功能可以讓你使用類似于 CommonJS 的方式導出函數或變量。

https://juejin.im/entry/5b2929b351882574bd7edddd#%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F 工廠模式

工廠模式是另一種流行的對象建立模式。該模式提供了一個用于建立對象的接口。根據傳入工廠的類型,可以建立出特定類型的對象。這種模式常見的實作通常是利用類或類的靜态方法。這樣的類或方法的目的如下:

  • 在建立相似對象時,抽象出重複出現的操作;
  • 允許工廠的使用者在無需了解對象建立的内部細節的情況下建立對象;

讓我們用一個常見的例子來了解工廠模式的用法。假設我們有:

  • 一個構造函數 

    CarFactory()

  • CarFactory 中一個叫做 

    make()

     的靜态方法,該方法知道如何建立 car 類型的對象
  • 特定的 car 類型,例如 

    CarFactory.SUV

    CarFactory.Sedan

     等

我們希望像下面這樣使用 CarFactory:

var golf = CarFactory.make('Compact');
var vento = CarFactory.make('Sedan');
var touareg = CarFactory.make('SUV');           

這裡給出了這種工廠的實作方法。下面的實作非常标準。我們用程式設計的方式調用了構造函數,建立指定類型的對象——

CarFactory[const].prototype = new CarFactory();

我們将對象類型映射為構造函數。該模式的實作方法不止一種:

// 工廠構造函數
function CarFactory() {}
CarFactory.prototype.info = function() {
    console.log("This car has " + this.doors + " doors and a " + this.engine_capacity + " liter engine");
};
// 靜态工廠方法
CarFactory.make = function (type) {
    var constr = type;
    var car;
    CarFactory[constr].prototype = new CarFactory();
    // 建立新的執行個體
    car = new CarFactory[constr]();
    return car;
};
CarFactory.Compact = function () {
    this.doors = 4;
    this.engine_capacity = 2;
};
CarFactory.Sedan = function () {
    this.door = 2;
    this.engine_capacity = 2;
}
CarFactory.SUV = function () {
    this.door = 4;
    this.engine_capacity = 6;
}
var golf = CarFactory.make('Compact');
var vento = CarFactory.make('Sedan');
var touareg = CarFactory.make('SUV');
golf.info(); // "This car has 4 door and a 2 liter engine"           

https://juejin.im/entry/5b2929b351882574bd7edddd#mixin-%E6%A8%A1%E5%BC%8F mixin 模式

mixin 模式能夠顯著減少代碼中重複出現的功能,有助于功能重用。我們可以将能夠共享的功能放到 mixin 中,以此降低共享行為的重複數量。

考慮下面的例子,我們想建立一個定制的日志記錄器(logger),任何對象執行個體都可以使用,這個日志記錄器會在希望使用/擴充 mixin 對象之間共享:

var _ = require('underscore');
// 将共享的功能封裝進 CustomLogger
var logger = (function () {
    var CustomLogger = {
        log: function (message) {
            console.log(message);
        }
    };
    return CustomLogger
}())
// 需要定制的日志記錄器來記錄系統特定日志的對象
var Server = (function (Logger) {
    var CustomServer = function () {
        this.init = function () {
            this.log("Initializing Server...");
        };
    };
    
    // 将 CustomLogger 的成員複制/擴充為 CustomServer
    _.extend(CustomServer.prototype, Logger);
    return CustomServer;
}(logger));
(new Server()).init(); //初始化伺服器           

我們動态地将 mixin 的功能加入到了對象中,重要的是了解 mixin 和繼承之間的差別。如果有可以在多個對象和類層次之間共享的功能,可以使用 mixin;如果要共享的功能是在單個層次中,可以使用繼承。在原型繼承中,如果繼承來自原型,那麼對原型做出的修改會影響到從原型繼承的一切内容。如果不希望出現這種情況,可以使用 mixin。

https://juejin.im/entry/5b2929b351882574bd7edddd#%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F 觀察者模式

在《設計模式》一書中,是這樣定義觀察者模式的:

對目标狀态感興趣的一個或多個觀察者,通過将自身與該目标關聯在一起的形式進行注冊。當目标出現觀察者可能感興趣的變化時,發出提醒消息,進而調用每個觀察者的更新方法。如果觀察者對目标狀态不再感興趣,隻需要解除關聯即可。

在觀察者模式中,目标儲存了一個對其依賴的對象清單(稱為觀察者),并在自身狀态發生變化時通知這些觀察者。目标所采用的通知方式是廣播。觀察者如果不想再被提醒,可以把自己從清單中移除。我們可以對該模式中的參與者做出如下定義:

  • 目标:儲存觀察者清單,擁有可以添加、删除和更新觀察者的方法;
  • 觀察者:為那些需要在目标狀态發生變化時得到提醒的對象提供接口。

讓我們來建立一個能夠添加、删除和提醒觀察者的目标:

var Subject = (function () {
    function Subject () {
        this.observer_list = [];
    }
    // 該方法用于向内部清單中添加觀察者
    Subject.prototype.add_observer = function (obj) {
        console.log('Added observer');
        this.observer_list.push(obj);
    };
    
    Subject.prototype.remove_observer = function (obj) {
        for(var i = 0; i < this.observer_list.length; i++) {
            if(this.observer_list[i] === obj) {
                this.observer_list.splice(i, 1);
                console.log('Removed Observer');
            }
        }
    };
    
    Subject.prototype.notify = function () {
        var args = Array.prototype.slice.call(arguments, 0);
        for(var i = 0; i < this.observer_list.length; i++) {
            this.observer_list[i].update(args)
        }
    };
    
    return Subject
})();           

Subject 的實作方法非常直覺,

notify()

 方法的重要之處在于調用所有觀察者對象的 

update()

方法來廣播更新。

現在來定義一個能夠建立随機推文的簡單對象,該對象提供了一個接口,可以使用 

addObserver()

和 

removeObserver()

 方法來添加和删除觀察者,它也可以使用新擷取的推文來調用 Subject 的 

notify()

 方法。如果出現這種情況,所有的觀察者都會将新釋出的推文作為參數,發出有推文更新的廣播:

function Tweeter () {
    var subject = new Subject();
    this.addObserver = function (observer) {
        subject.add_observer(observer);
    };
    this.removeObserver = function (observer) {
        subject.remove_observer(observer);
    };
    this.fetchTweets = function fetchTweets () {
        // tweet
        var tweet = {
            tweet: "This is one nice Observer"
        };
        // 提示觀察者發生的變化
        subject.notify(tweet);
    };
}           

添加兩名觀察者:

var TweetUpdater = {
    update: function () {
        console.log('Update Tweet - ', arguments);
    }
};
var TweetFollower = {
    update: function () {
        console.log('Following this tweet - ', arguments);
    }
};           

兩名觀察者都有 

update()

 方法,該方法将由 

Subject.notify()

 調用。現在我們就可以通過 Tweeter 的接口将觀察者添加到 Subject 中了:

var tweetApp = new Tweeter();
tweetApp.addObserver(TweeterUpdater);
tweetApp.addObserver(TweeterFollower);
tweetApp.fetchTweets();
tweetApp.removeObserver(TweetUpdater);
tweetApp.removeObserver(TweetFollower);           

https://juejin.im/entry/5b2929b351882574bd7edddd#%E5%B0%8F%E7%BB%93 小結

在大型應用的建構中,我們發現某些問題模式會反複地出現,這類問題都有明确的應對方法,可以拿來重用以建構一套健壯的解決方案,這便是應用設計模式的意義所在。

原文釋出時間為:2018年6月9日

原文作者:alloween.top

本文來源:

掘金

如需轉載請聯系原作者

繼續閱讀