Javascript-设计模式.md

单体模式

说明
    单体(Singleton)模式的思想在于保证一个特性的类仅有一个实例。
    实现的方式有多种方案,主要思路为判断实例是否存在,下面是其中两种常见的方式。
    第一种是通过类的静态属性保存实例,缺点是静态属性可改写。
    第二种是通过闭包内的局部变量存储实例,缺点是会带来闭包的格外开销。
范例
    类属性方式
        function Person(){
            // 判断是否类的静态属性instance是否存在
            if(typeof Person.instance === "object"){
                return Person.instance;
            }
            this.name = "Jack";
            this.age = 18;
            // 通过构造函数创建实例时,把实例的引用保存在Person的静态属性中
            Person.instance = this;
        }
        var p1 = new Person;
        var p2 = new Person;
        p1===p2;  //true
    闭包方式
        var Person = (function () {
            var instance;
            var Person = function Person() {
                if(instance){
                    return instance;
                }
                this.name = "Jack";
                this.age = 18;
                instance = this;
            }
            return Person
        }())
        Person.prototype.nothing = true;
        var p1 = new Person;
        Person.prototype.everything = true;
        var p2 = new Person;
        p1===p2;  //true
        p1.nothing && p2.nothing && p1.everything && p2.everything //true
        p1.name //"Jack"
        p1.constructor === Person //true

工厂模式

说明
    工厂模式的目的主要为了创建对象,通常在类的静态方法中实现。
    许多类型对象的创造需要一系列的步骤,比如设置属性,位置,选择为哪个子类的实例等。 
    这一系列的重复操作类似类似工厂一样。
    可以给类添加一个工厂方法,只需要一个参数,便能完成这些不同类型的实例的设置。
    全局Object构造函数就是一个工厂模式的例子,可以通过传递不同的参数生成不同类型的实例。
    工厂模式又区分简单工厂模式和抽象工厂模式,下面范例为抽象工厂模式。

范例
    // Vehicle为父类用来派生子类
    var Vehicle = function(){};
    Vehicle.prototype.drive = function () {
        console.log("I have " + this.doors + " doors");
    }
    // 定义工厂方法
    Vehicle.factory = function(type){
        var newcar;
        // 如果不存在对应的之类则抛出错误
        if(typeof Vehicle[type] !== "function"){
            throw{
                name:"Error",
                message:type + "doesn's exist"
            }
        };
        // 用于给子类设置原型为父类的实例,并只设置一次
        if(typeof Vehicle[type].prototype !== "function"){
            Vehicle[type].prototype = new Vehicle();
        };
        // 创建一个新的实例
        newcar = new Vehicle[type]();
        // 可以做一些其他操作并返回...
        return newcar;
    }
    // 下面是特定类型的实例的构造函数
    Vehicle.Micro = function(){
        this.doors = 2;
    };
    Vehicle.Compact = function(){
        this.doors = 4;
    };
    Vehicle.SUV = function(){
        this.doors = 8;
    };
    // 通过工厂方法创建实例
    var micro = Vehicle.factory("Micro");
    micro.drive();    //I have 2 doors
    var compact = Vehicle.factory("Compact");
    compact.drive();  //I have 4 doors
    var suv = Vehicle.factory("SUV");
    suv.drive();      //I have 8 doors

迭代器模式

说明
    迭代器模式中,通常有一个包含某种数据集合的对象。
    该数据可在存储在一个复杂数据结构的内部,需要提供一个简单的方法来访问数据中的每个元素。
    对象的使用者不需要知道对象内部如何组织数据,仅需要使用对象提供的方法取出数据使用。
    通常对象需要提供一个next方法,依次调用next()必须返回下一个连续的元素。
    在特定的数据结构中,next方法所返回的"下一个"元素可由您来使用。

范例
    var generator = (function(){
        var index = 0,
            data = [1,2,3,4,5],
            length = data.length;
        return {
            // 取出下一个元素
            next: function(){
                var element;
                if(!this.hasNext()){
                    return null;
                }
                element = data[index];
                index ++;
                return element;
            },
            // 检测是否到最后一个元素
            hasNext: function(){
                return index < length
            },
            // 重置指针到初始位置
            rewind: function(){
                index = 0;
            },
            // 返回当前元素
            current: function(){
                return data[index < length ? index : length - 1];
            }
        }
    }());
    var arr = [];
    while(generator.hasNext()){
        arr.push(generator.next());
    }
    arr  // [1,2,3,4,5]
    generator.current()) // 5
    generator.rewind()
    generator.current()) // 1

装饰者模式

说明
    在装饰者模式中,可以在运行时动态添加附件功能到对象中。
    因JavaScript的对象是可边的,添加功能到对象的过程非常容易。

范例
    // 货币的构造函数
    function Money(amount){
        this.amount = amount || 100;
        this.decoratorsList = [];
    }
    // 装饰方法
    Money.prototype.decorate = function(decorater){
        this.decoratorsList.push(decorater)
    };
    // 获取处理后的金额
    Money.prototype.getAmount = function(){
        var amount = this.amount,
            name;
        for(var i = 0; i < this.decoratorsList.length; i++){
            var key = this.decoratorsList[i];
            amount = Money.decorators[key].getAmount(amount) ;
        }
        return amount;
    };
    // 装饰者列表
    Money.decorators = {}
    // 货币转换
    Money.decorators.toUSD = {
        getAmount: function(amount){
            return amount * 0.8;
        }
    }
    // 加上额外的货币转换手续费
    Money.decorators.addTax = {
        getAmount: function(amount){
            return amount * 1.01;
        }
    }
    // 使用
    var m = new Money(100);
    m.decorate("toUSD");
    m.decorate("addTax");
    m.getAmount(); // 80.8

策略模式

说明
    策略模式支持在运行时选择算法。目的是把程序的接口和算法解耦。
    常见的例子是表单验证的方法,可根据数据类型的不同选择不同的验证规则。

范例
    var validator = {
        // 对不同数据的验证配置
        config: {
            name: "isNonEmpty",
            age: "isNumber",
            email: "isEmail"
        },
        // 验证后的错误信息列表
        messages: [],
        // 验证方法
        types: {
            isNonEmpty: {
                validate: function(value){
                    return value !== "";
                },
                errMsg:"the value cannot be empty"
            },
            isNumber: {
                validate: function(value){
                    return !isNaN(value);
                },
                errMsg:"the value can only be a number"
            },
            isEmail: {
                validate: function(value){
                    var reg = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/;
                    return reg.test(value)
                },
                errMsg:"the value can only be a email"
            }
        },
        // 验证方法
        validate: function(data){
            var i, msg, type, checker, resultOk;
            // 重置所有消息
            this.messages = [];
            // 对所有字段进行校验
            for(i in data){
                type = this.config[i];
                checker = this.types[type];
                resultOk = checker.validate(data[i]);
                if(!resultOk){
                    msg = checker.errMsg;
                    this.messages.push(msg)
                }
            }
            return this.hasError();
        },
        // 数据是否有错误
        hasError: function(){
            return this.messages.length !== 0;
        }
    };
    var data = {
        name: "",
        age: "err",
        email: "name@gamil.com"
    }
    var hasError = validator.validate(data);
    console.log(hasError) //true
    console.log(validator.messages.join(","))
    //"the value cannot be empty,the value can only be a number"

外观模式

说明
    通过把常用方法包装到一个新的方法中,从而提供一个更为便利的API。

范例
    var event = {
        // ...
        stop: function (e) {
            e.preventDefault();
            e.stopPropagation();
        }
        // ...
    };

代理模式

说明
    在代理模式中,一个对象充当另外一个对象的接口。
    代理介于对象的客户端和对象本身之间,并且对该对象的访问提供保护。
    代理模式可用于服务器请求的合并,降低服务器压力。
    下面的例子中taskProxy用来作为doTask的代理,积累1S内的任务统一执行。

范例
    // 执行任务的方法
    var doTask = function(msg){
        console.log("doTask: " + msg);
    }
    // 中间代理
    var taskProxy = (function(){
        // 代发送的消息列表
        var msgList = [];
        // 任务队列是否已经开始积累任务
        var taskQueueStart = false;
        var task = function(msg){
            msgList.push(msg);
            if(!taskQueueStart){
                setTimeout(function(){
                    doTask(msgList.join(", "));
                    msgList = [];
                    taskQueueStart = false;
                }, 1000);
            }
            taskQueueStart = true;
        }
        return task
    }());
    // 同时执行三个任务
    taskProxy("msg-1");
    taskProxy("msg-2");
    taskProxy("msg-3");
    // doTask: msg-1, msg-2, msg-3

中介者模式

说明
    多个对象间相互并不直接通信,而是仅通过中介者对象进行通信,从而促进形成松散耦合。

范例
    // 创建数据模型
    function Model(){
        this.data = [];
    }
    Model.prototype.getNumber = function(){
        console.log(this.data.join(","));
    }
    // 构造两个实例,分别用来收集单数和偶数
    var oddModel = new Model(),
        evenModel = new Model();
    // 中介者
    var mediator = function(){
        arr = [].slice.call(arguments);
        for (var i = 0; i < arr.length; i++) {
            if(arr[i] % 2 === 1){
                oddModel.data.push(arr[i])
            }else{
                evenModel.data.push(arr[i])
            }
        }
    }
    mediator(1,2,3,4,5,6,7,8,9,0)
    oddModel.getNumber();   // 1,3,5,7,9
    evenModel.getNumber();  // 2,4,6,8,0

观察者模式

说明
    观察者模式广泛应用于客户端JavaScript编程中,所有的浏览器事件都是该模式的例子。
    它的另外一个名字称为自定义事件,也可称为订阅/发布模式。
    该模式的实现是通过创建可观察的对象,发生事件时将事件通知给所有观察者,从而形成松耦合。

范例
    // 通用的发布者对象
    var publisher = {
        // 存放不同事件类型订阅者数组的对象
        subscribers: {
            any: []  //any事件类型
        },
        // 订阅
        subscribe: function(fn, type) {
            // 默认为any事件
            type = type || "any";
            // 当事件不存在时在subscribers里面添加
            if(typeof this.subscribers[type] === "undefined"){
                this.subscribers[type] = [];
            }
            this.subscribers[type].push(fn)
        },
        // 取消订阅
        unsubscribe: function(fn, type){
            this.visitSubscribers("unsubscribe",fn,type);
        },
        // 发布事件
        publish: function(publication, type){
            this.visitSubscribers("publish",publication,type);
        },
        // 这个方法的内部用来发布或取消订阅
        visitSubscribers: function(action, arg, type){
            var pubtype = type || "any",
                subscribers = this.subscribers[pubtype];
            for (var i = 0; i < subscribers.length; i++) {
                if(action === "publish"){
                    subscribers[i](arg);
                }else{
                    if(subscribers[i] === arg){
                        subscribers.splice(i,1);
                    }
                }
            }

        }
    }
    // 可将通用发布者对象的方法拷贝到指定对象中
    function makePublisher(o){
        var i;
        for (i in publisher) {
            if(publisher.hasOwnProperty(i) && typeof publisher[i] === "function"){
                o[i] = publisher[i];
            }
        }
        o.subscribers = {any:[]};
    }
    // 实现paper对象,它可以发布日报和月刊
    var paper = {
        daily: function(){
            this.publish("big news today");
        },
        monthly: function(){
            this.publish("interesting analysis","monthly");
        }
    }
    makePublisher(paper);
    // 实现订阅者
    var joe = {
        drinkCoffee: function(paper){
            console.log("Just read " + paper);
        },
        sundayPreNap: function(monthly){
            console.log("About to fall asleep reading this " + monthly);
        }
    }
    // paper注册joe
    paper.subscribe(joe.drinkCoffee);
    paper.subscribe(joe.sundayPreNap,"monthly");
    // 发布
    paper.daily();    // Just read big news today
    paper.daily();    // Just read big news today
    paper.monthly();  // About to fall asleep reading this interesting analysis