JavaScript设计模式

本文深入探讨了JavaScript中的设计模式,包括原型机制、this的指向、单例模式、策略模式、代理模式、迭代器模式、发布-订阅模式、命令模式、组合模式、模板方法模式、享元模式、职责链模式和装饰者模式。特别讨论了发布-订阅模式在异步编程和架构中的重要角色,以及装饰者模式与代理模式的区别。
摘要由CSDN通过智能技术生成

JavaScript的原型机制

JavaScript 本身是一门基于原型的面向对象语言,它的对象系统就是使用原型模式来搭建的,在这里称之为原型编程范型也许更合适。所有的 JavaScript 对象都是从某个对象上克隆而来的。
JavaScript 是通过克隆 Object.prototype 来得到新的对象,但实际上并不是每次都真正地克隆了一个新的对象。从内存方面的考虑出发,JavaScript 还做了一些额外的处理。
“对象的原型”,就 JavaScript 的真正实现来说,其实并不能说对象有 原型,而只能说对象的构造器有原型。对于“对象把请求委托给它自己的原型”这句话,更好 的说法是对象把请求委托给它的构造器的原型。
JavaScript 给对象提供了一个名为 __proto__ 的隐藏属性,某个对象的 __proto__ 属性默认会指向它的构造器的原型对象,即{Constructor}.prototype。__proto__ 就是对象跟“对象构造器的原型”联系起来的纽带。
虽然 JavaScript 的对象最初都是由 Object.prototype 对象克隆而来的,但对象构造器的原型并不仅限于 Object.prototype 上,而是可以动态指向其他对象。这样一来,当对象 a 需要借用对象 b 的能力时,可以有选择性地把对象 a 的构造器的原型指向对象 b ,从而达到继承的效果。
p21
var obj = { name: 'sven' };
var A = function(){};
A.prototype = obj;
var a = new A();
console.log( a.name ); // 输出: sven
执行这段代码的时候,引擎做了哪些事情:
首先,尝试遍历对象 a 中的所有属性,但没有找到 name 这个属性。
查找 name 属性的这个请求被委托给对象 a 的构造器的原型,它被 a. __proto__ 记录着并且
指向 A.prototype ,而 A.prototype 被设置为对象 obj
在对象 obj 中找到了 name 属性,并返回它的值。
通过 Object.create 来创建对象的效率并不高,通常比通过构造函数创建对象要慢。
通过设置构造器的prototype 来实现原型继承的时候,除了根对象 Object.prototype 本身之外,任何对象都会有一个原型。而通过 Object.create( null ) 可以创建出没有原型的对象。
小例子:

关于this

this 的指向大致可以分为:

作为对象的方法调用。
作为普通函数调用。
构造器调用。
Function.prototype.call Function.prototype.apply 调用。
当函数不作为对象的属性被调用时,此时的 this 总是指向全局对象。
①window.name = 'globalName';
var myObject = {
name: 'sven',
getName: function(){
return this.name;
}
};
var getName = myObject.getName;// 使用另一个变量来引用 myObject.getName是普通函数调用
console.log( getName() ); // globalName 
console.log( myObject.getName() ); //sven
②<html>
<body>
<div id="div1"> 我是一个 div</div>
</body>
<script>
window.id = 'window';
document.getElementById( 'div1' ).onclick = function(){
alert ( this.id ); // 输出: 'div1'
var callback = function(){//函数中的方法
alert ( this.id ); // 输出:'window'
}
callback();//该方法不作为对象的属性被调用
};
</script>
</html>
想让它指向该 div 节点,法一
document.getElementById( 'div1' ).onclick = function(){
var that = this; // 保存 div 的引用
var callback = function(){
alert ( that.id ); // 输出: 'div1'
}
callback();
};
法二
 <div id="div1">我是一个 div</div>
    <script>
        document.getElementById = (function (func) {
            return function () {
                console.log(arguments);
                return func.apply(document, arguments);
            }
        })(document.getElementById);
        var getId = document.getElementById;
        var div = getId('div1');
        console.log(div.id); // 输出: div1
    </script>
将不变的部分和变化的部分隔开是每个设计模式的主题。

单例模式

单例模式的核心是 确保只有一个实例,并提供全局访问 。比如重复点击按钮只弹出一个弹出框,判断该弹出框是否已存在,就需要创建一个唯一对象,并判断它是否已赋值。
代理实现单例模式:把控制创建唯一对象的代码段在函数中移除出来交给代理类,这样和构造函数结合起来的单例类,使得当该类从单例类要变成一个普通的可产生多个实例的类时,不需要去修改到构造函数。
惰性单例:指在需要的时候才创建对象实例(调用时才创建)

策略模式

*策略模式的定义是: 定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
*一个基于策略模式的程序至少由两部分组成。第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程。第二个部分是环境类 Context Context 接受客户的请求,随后把请求委托给某一个策略类。要做到这点,说明 Context 中要维持对某个策略对象的引用。
 <script>
        //不使用策略模式:
        var calculateBonus = function (performanceLevel, salary) {
            if (performanceLevel === 'S') {
                return salary * 4;
            }
            if (performanceLevel === 'A') {
                return salary * 3;
            }
            if (performanceLevel === 'B') {
                return salary * 2;
            }
        };
        calculateBonus('B', 20000); // 输出:40000 
        calculateBonus('S', 6000); // 输出:24000

        //使用策略模式:消除了原程序中大片的条件分支语句。
        //策略类:封装了具体的算法,并负责具体的计算过程。
        var strategies = {
            "S": function (salary) {
                return salary * 4;
            },
            "A": function (salary) {
                return salary * 3;
            },
            "B": function (salary) {
                return salary * 2;
            }
        };
        //环境类Context,Context 接受客户的请求,随后把请求委托给策略类。
        var calculateBonus = function (level, salary) {
            return strategies[level](salary);
        };
        console.log(calculateBonus('S', 20000)); // 输出:80000 
        console.log(calculateBonus('A', 10000)); // 输出:30000
    </script>

代理模式

JavaScript 开发中最常用的是虚拟代理和缓存代理。
*代理 B 可以帮助 A 过滤掉一些请求,比如送花的人中年龄太大的或者没有宝马的,这种请求就可以直接在代理 B处被拒绝掉。这种代理叫作保护代理 A B 一个充当白脸,一个充当黑脸。白脸 A 继续保持良好的女神形象,不希望直接拒绝任何人,于是找了黑脸 B 来控制对 A 的访问。
*虚拟代理:把一些开销很大的对象,延迟到 真正需要它的时候才去创建。比如在监听函数中,满足条件之后才创建需要的变量。运用:
实现图片预加载(等待图片加载完成之前先用其他图片代替)
合并 HTTP 请求 (需要连续发送很多请求时先等待几秒再一起发送) 。:
        <input type="checkbox" id="1"></input>1 
        <input type="checkbox" id="2"></input>2 
        <input type="checkbox" id="3"></input>3 
        <input type="checkbox" id="4"></input>4 
        <input type="checkbox" id="5"></input>5 
        <input type="checkbox" id="6"></input>6 
        <input type="checkbox" id="7"></input>7 
        <input type="checkbox" id="8"></input>8 
        <input type="checkbox" id="9"></input>9 
    <script>
        var synchronousFile = function (id) {
            console.log('开始同步文件,id 为: ' + id);
        };
        //代理函数:收集一段时间之内发的请求,再一次性发给服务器
        var proxySynchronousFile = (function () {
            var cache = [], // 保存一段时间内需要同步的 ID 
                timer; // 定时器
            return function (id) {
                cache.push(id);
                if (timer) { // 保证不会覆盖已经启动的定时器
                    return;
                }
                timer = setTimeout(function () {
                    synchronousFile(cache.join(',')); // 2 秒后向本体发送需要同步的 ID 集合
                    clearTimeout(timer); // 清空定时器
                    timer = null;
                    cache.length = 0; // 清空 ID 集合
                }, 2000);
            }
        })();
        var checkbox = document.getElementsByTagName('input');
        for (var i = 0, c; c = checkbox[i++];) {
            c.onclick = function () {
                if (this.checked === true) {
                    proxySynchronousFile(this.id);
                }
            }
        };
    </script>
**保护代理用于控制不同权限的对象对目标对象的访问,但在 JavaScript 并不容易实现保护代理,因为我们无法判断谁访问了某个对象。而虚拟代理是最常用的一种代理模式
缓存代理缓存代理可以为一些开销大的运算结果提供暂时的存储,在下次运算时,如果传递进来的参数跟之前一致,则可以直接返回前面存储的运算结果。
用于ajax异步请求数据在项目中遇到分页的需求,同一页的数据理论上只需要去后台拉取一次,这些已经拉取到的数据在某个地方被缓存之后,下次再请求同一页的时候,便可以直接使用之前的数据。
高阶函数动态创建缓存代理:
/**************** 计算乘积 *****************/
        var mult = function () {
            var a = 1;
            for (var i = 0, l = arguments.length; i < l; i++) {
                a = a * arguments[i];
            }
            return a;
        };
        /**************** 计算加和 *****************/
        var plus = function () {
            var a = 0;
            for (var i = 0, l = arguments.length; i < l; i++) {
                a = a + arguments[i];
            }
            return a;
        };
        /**************** 创建缓存代理的工厂 *****************/
        var createProxyFactory = function (fn) {
            var cache = {};
            return function () {
                var args = Array.prototype.join.call(arguments, ',');
                if (args in cache) {
                    return cache[args];
                }
                return cache[args] = fn.apply(this, arguments);
            }
        };
        var proxyMult = createProxyFactory(mult),
            proxyPlus = createProxyFactory(plus);
        alert(proxyMult(1, 2, 3, 4)); // 输出:24 
        alert(proxyMult(1, 2, 3, 4)); // 输出:24 
        alert(proxyPlus(1, 2, 3, 4)); // 输出:10 
        alert(proxyPlus(1, 2, 3, 4)); // 输出:10

迭代器模式

发布-订阅模式(观察者模式)

DOM事件

自定义事件:可以往回调函数里填入一些参数,订阅者可以接收这些参数。

    /*11自定义事件基础版*/
        var salesOffices = {}; // 定义售楼处
        salesOffices.clientList = []; // 缓存列表,存放订阅者的回调函数
        salesOffices.listen = function (fn) { // 增加订阅者
            this.clientList.push(fn); // 订阅的消息添加进缓存列表
        };
        salesOffices.trigger = function () { // 发布消息
            for (var i = 0, fn; fn = this.clientList[i++];) {
                fn.apply(this, arguments); // (2) // arguments 是发布消息时带上的参数
            }
        };
        // 下面我们来进行一些简单的测试:
        salesOffices.listen(function (price, squareMeter) { // 小明订阅消息
            console.log('价格= ' + price);
            console.log('squareMeter= ' + squareMeter);
        });
        salesOffices.listen(function (price, squareMeter) { // 小红订阅消息
            console.log('价格= ' + price);
             console.log('squareMeter= ' + squareMeter);
        });
        salesOffices.trigger(2000000, 88); // 输出:200 万,88 平方米
        salesOffices.trigger(3000000, 110); // 输出:300 万,110 平方米

  /*22增加一个标示 key,让订阅者只订阅自己感兴趣的消息*/
        var salesOffices = {}; // 定义售楼处
        salesOffices.clientList = {}; // 缓存列表,存放订阅者的回调函数
        salesOffices.listen = function (key, fn) {
            if (!this.clientList[key]) { // 如果还没有订阅过此类消息,给该类消息创建一个缓存列表
                this.clientList[key] = [];
            }
            this.clientList[key].push(fn); // 订阅的消息添加进消息缓存列表
        };
        salesOffices.trigger = function () { // 发布消息
            var key = Array.prototype.shift.call(arguments), // 取出消息类型
                fns = this.clientList[key]; // 取出该消息对应的回调函数集合
            if (!fns || fns.length === 0) { // 如果没有订阅该消息,则返回
                return false;
            }
            for (var i = 0, fn; fn = fns[i++];) {
                fn.apply(this, arguments); // (2) // arguments 是发布消息时附送的参数
            }
        };
        salesOffices.listen('squareMeter88', function (price) { // 小明订阅 88 平方米房子的消息
            console.log('价格= ' + price); // 输出: 2000000 
        });
        salesOffices.listen('squareMeter110', function (price) { // 小红订阅 110 平方米房子的消息
            console.log('价格= ' + price); // 输出: 3000000 
        });
        salesOffices.trigger('squareMeter88', 2000000); // 发布 88 平方米房子的价格
        salesOffices.trigger('squareMeter110', 3000000); // 发布 110 平方米房子的价格

        /*33把发布—订阅的功能提取出来,放在一个单独的对象内*/
        var event = {
            clientList: [],
            //订阅者
            listen: function (key, fn) {
                if (!this.clientList[key]) {
                    this.clientList[key] = [];
                }
                this.clientList[key].push(fn); // 订阅的消息添加进缓存列表
            },
            //发布者
            trigger: function () {
                var key = Array.prototype.shift.call(arguments), // (1); 
                    fns = this.clientList[key];
                if (!fns || fns.length === 0) { // 如果没有绑定对应的消息
                    return false;
                }
                for (var i = 0, fn; fn = fns[i++];) {
                    fn.apply(this, arguments); // (2) // arguments 是 trigger 时带上的参数
                }
            }
        };

        //取消订阅事件
        event.remove = function (key, fn) {
            var fns = this.clientList[key];
            if (!fns) { // 如果 key 对应的消息没有被人订阅,则直接返回
                return false;
            }
            if (!fn) { // 如果没有传入具体的回调函数,表示需要取消 key 对应消息的所有订阅
                fns && (fns.length = 0);
            } else {
                for (var l = fns.length - 1; l >= 0; l--) { // 反向遍历订阅的回调函数列表
                    var _fn = fns[l];
                    if (_fn === fn) {
                        fns.splice(l, 1); // 删除订阅者的回调函数
                    }
                }
            }
        };
        var installEvent = function (obj) {
            for (var i in event) {
                obj[i] = event[i];
            }
        };
        var salesOffices = {};
        installEvent(salesOffices);//这样可以一直赋值给多个对象(让多个售楼处都拥有该功能)
        salesOffices.listen('squareMeter88', fn1 = function (price) { // 小明订阅消息
            console.log('价格= ' + price);
        });
        salesOffices.listen('squareMeter100', function (price) { // 小红订阅消息
            console.log('价格= ' + price);
        });
        salesOffices.remove( 'squareMeter88', fn1 ); // 删除小明的订阅
        salesOffices.trigger('squareMeter88', 2000000); // 输出:2000000 (删除之后无法输出)
        salesOffices.trigger('squareMeter100', 3000000); // 输出:3000000
 /*44全局的发布-订阅对象(不需要一直创建多个对象,如salesOffices对象,而是使用中介的方式(全局)处理不同楼盘和买房者)*/
        var Event = (function () {
            var clientList = {},
                listen,
                trigger,
                remove;
            listen = function (key, fn) {
                if (!clientList[key]) {
                    clientList[key] = [];
                }
                clientList[key].push(fn);
            };
            trigger = function () {
                var key = Array.prototype.shift.call(arguments),
                    fns = clientList[key];
                if (!fns || fns.length === 0) {
                    return false;
                }
                for (var i = 0, fn; fn = fns[i++];) {
                    fn.apply(this, arguments);
                }
            };
            remove = function (key, fn) {
                var fns = clientList[key];
                if (!fns) {
                    return false;
                }
                if (!fn) {
                    fns && (fns.length = 0);
                } else {
                    for (var l = fns.length - 1; l >= 0; l--) {
                        var _fn = fns[l];
                        if (_fn === fn) {
                            fns.splice(l, 1);
                        }
                    }
                }
            };
            return {
                listen: listen,
                trigger: trigger,
                remove: remove
            }
        })();
        Event.listen('squareMeter88', function (price) { // 小红订阅消息
            console.log('价格= ' + price); // 输出:'价格=2000000' 
        });
        Event.trigger('squareMeter88', 2000000); // 售楼处发布消息

**使用该模式的案例:

网站登录:每次登录需要ajax异步请求获取用户的登录信息,但其他模块中需要用到登录后返回的信息,可以直接用发布-订阅模式,在登录成功时发布(trigger)一个消息,其他模块中订阅(listen)监听该消息并操作。

**未订阅先发布的情况:

有时候还没有人订阅,但是已经发布了消息,可以先建立一个存放离线事件的堆栈,暂时把发布事件的动作包裹在一个函数里,等有对象来订阅事件时再遍历堆栈并依次执行该包装函数,即重新发布事件。当然离线事件的生命周期只有一次,就像 QQ 的未读消息只会被重 新阅读一次,所以刚才的操作我们只能进行一次。

**推模型和拉模型

推模型是指在事件发生时,
发布者一次性把所有更改的状态和数据都推送给订阅者。拉模型不同的地方是,发布者仅仅通知
订阅者事件已经发生了,此外发布者要提供一些公开的接口供订阅者来主动拉取数据。拉模型的
好处是可以让订阅者“按需获取”,但同时增加了代码量和复杂度。
JavaScript中一般都会选择推模型, 使用 Function.prototype.apply 方法把所有参数都推送给订阅者。
**发布订阅模式优缺点:
优点:
  1. 一为时间上的解耦,二为对象之间的解耦。(楼盘有新动向时再发消息给买房者,而不需要买房者存储销售的电话,每天都询问楼盘动向【时间】。并且销售辞职或买房者是谁等对象信息也不会影响该消息传送【对象】)
  2. 三可以用在异步编程中,也可帮助实现一些别的设计模式,比如中介者模式。
  3. 四架构上来看,无论是 MVC 还是 MVVM, 都少不了发布—订阅模式的参与。
缺点:
1.创建订阅者本身要消耗一定的时间和内存,而 且当你订阅一个消息后,也许此消息最后都未发
生,但这个订阅者会始终存在于内存中。
2.发布 订阅模式虽然可以弱化对象之间的联系,但如果过度使用的话,对象和对象之间的必要联
系也将被深埋在背后,会导致程序难以跟踪维护和理解。特别是有多个发布者和订阅者嵌套到一 起的时候,要跟踪一个 bug 不是件轻松的事情。

命令模式

有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么,此时希望用一种松耦合的方式来设计软件,使得请求发送者和请求接收者能够消除彼此之间的耦合关系。(比如按钮数量很多,则点击按钮和按钮的功能之间,可以通过一个统一的函数来连接所有不同的按钮以及对应的功能,以此降低耦合度)
<button id="button1">点击按钮 1</button>
    <button id="button2">点击按钮 2</button>
    <button id="button3">点击按钮 3</button>
    <script>
        var button1 = document.getElementById('button1');
        var button2 = document.getElementById('button2')
        var button3 = document.getElementById('button3');
 //使用闭包的方式:
/*关键点:只需要预留好安装命令的接口(setCommand),不需要关心其他,于是把请求发送者和请求接收者解耦开。*/
        var setCommand = function (button, func) {
            button.onclick = function () {
                func();
            }
        };
        var MenuBar = {
            refresh: function () {
                console.log('刷新菜单目录');
            }
        };
        var SubMenu = {
            add: function () {
                console.log('增加子菜单');
            },
            del: function () {
                console.log('删除子菜单');
            }
        };
 var RefreshMenuBarCommand = function (receiver) {
            return function () {
                receiver.refresh();
            }
        };
 var refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar);
       setCommand(button1, refreshMenuBarCommand);

组合模式

不同功能用不同的对象(子命令)来表示,最终再创建一个对象(宏命令),将所有子命令都放进去,调用时只需要调用宏命令就可以实现所有子命令的功能。

模板方法模式

原型链上设置好对象父类,后面需要用到的时候再继承并重写,和其他语言的抽象类相似。

钩子方法:

在父类中容易变化的地方放置钩子,钩子可以有一个默认的实现,究竟要不要“挂钩”,这由子类自 行决定。允许底层组件将自己挂钩到高层组件中,而高层组件会决定什么时候、以何种方式去使用这些底层组件,高层组件对待底层组件的方式,跟演艺公司对待新人演员一样,都是“别调用我们,我们会调用你”。
模板方法模式是基于继承的一种设计模式,父类封装了子类的算法框架和方法的执行顺序,子类继承父类之后,父类通知子类执行这些方法,好莱坞原则很好地诠释了这种设计技巧,即高层组件调用底层组件。

享元模式

(减少创建的对象数量)

在一个存在大量相似对象的系统中,享元模式可以很好地解决大量对象带来的性能问题。
用享元模式的关键是如何区别内部状态和外部状态。可以被对象共享的属性通常被划分为内部状态,如同不管什么样式的衣服,都可以按照性别不同,穿在同一个男模特或者女模特身上,模特的性别就可以作为内部状态储存在共享对象的内部。而外部状态取决于具体的场景,并根据场景而变化,就像例子中每件衣服都是不同的,它们不能被一些对象共享,因此只能被划分为外部状态。
性别是内部状态,内衣是外部状态(不需要在遍历的时候创建对象),通过区分这两种状态,大大减少了系统中的对象数量。

职责链模式

职责链模式的最大优点就是解耦了请求发送者和 N 个接收者之间的复杂关系,由于不知道链中的哪个节点可以处理你发出的请求,所以你只需把请求传递给第一个节点即可。当第一个节点不符合要求,会自动传给职责链的下一个节点
**使用AOP实现职责链????p187
职责链模式可以很好地帮助我们管理代码,降低发起请求的对象和处理请求的对象之间的耦合性。
 /*无使用职责链*/
        var order = function (orderType, pay, stock) {
            if (orderType === 1) { // 500 元定金购买模式
                if (pay === true) { // 已支付定金
                    console.log('500 元定金预购, 得到 100 优惠券');
                } else { // 未支付定金,降级到普通购买模式
                    if (stock > 0) { // 用于普通购买的手机还有库存
                        console.log('普通购买, 无优惠券');
                    } else {
                        console.log('手机库存不足');
                    }
                }
            }
            else if (orderType === 2) { // 200 元定金购买模式
                if (pay === true) {
                    console.log('200 元定金预购, 得到 50 优惠券');
                } else {
                    if (stock > 0) {
                        console.log('普通购买, 无优惠券');
                    } else {
                        console.log('手机库存不足');
                    }
                }
            }
            else if (orderType === 3) {
                if (stock > 0) {
                    console.log('普通购买, 无优惠券');
                } else {
                    console.log('手机库存不足');
                }
            }
        };
        order(1, true, 500); // 输出: 500 元定金预购, 得到 100 优惠券
 /*用职责链模式重构代码*/
        // 500 元订单
        //缺点:请求在链条传递中的顺序非常僵硬,传递请求的代码被耦合在了业务函数之中,违反开放-封闭原则
        var order500 = function (orderType, pay, stock) {
            if (orderType === 1 && pay === true) {
                console.log('500 元定金预购, 得到 100 优惠券');
            } else {
                order200(orderType, pay, stock); // 将请求传递给 200 元订单
            }
        };
        // 200 元订单
        var order200 = function (orderType, pay, stock) {
            if (orderType === 2 && pay === true) {
                console.log('200 元定金预购, 得到 50 优惠券');
            } else {
                orderNormal(orderType, pay, stock); // 将请求传递给普通订单
            }
        };
        // 普通购买订单
        var orderNormal = function (orderType, pay, stock) {
            if (stock > 0) {
                console.log('普通购买, 无优惠券');
            } else {
                console.log('手机库存不足');
            }
        };
        // 测试结果:
        order500(1, true, 500); // 输出:500 元定金预购, 得到 100 优惠券
        order500(1, false, 500); // 输出:普通购买, 无优惠券
        order500(2, true, 500); // 输出:200 元定金预购, 得到 500 优惠券
        order500(3, false, 500); // 输出:普通购买, 无优惠券
        order500(3, false, 0); // 输出:手机库存不足
 /*灵活可拆分的职责链节点*/
        var order500 = function (orderType, pay, stock) {
            if (orderType === 1 && pay === true) {
                console.log('500 元定金预购,得到 100 优惠券');
            } else {
                return 'nextSuccessor'; // 我不知道下一个节点是谁,反正把请求往后面传递
            }
        };
        var order200 = function (orderType, pay, stock) {
            if (orderType === 2 && pay === true) {
                console.log('200 元定金预购,得到 50 优惠券');
            } else {
                return 'nextSuccessor'; // 我不知道下一个节点是谁,反正把请求往后面传递
            }
        };
        var orderNormal = function (orderType, pay, stock) {
            if (stock > 0) {
                console.log('普通购买,无优惠券');
            } else {
                console.log('手机库存不足');
            }
        };
        var Chain = function (fn) {
            this.fn = fn;
            this.successor = null;
        };
        //异步的职责链p185:数同步返回"nextSuccessor"已经没有意义了。需要添加一个原型方法,在异步触发时可以用
        // Chain.prototype.next = function () {
        //     return this.successor && this.successor.passRequest.apply(this.successor, arguments);
        // };

        Chain.prototype.setNextSuccessor = function (successor) {
            return this.successor = successor;
        };
        Chain.prototype.passRequest = function () {
            var ret = this.fn.apply(this, arguments);
            if (ret === 'nextSuccessor') {
                return this.successor && this.successor.passRequest.apply(this.successor, arguments);
            }
            return ret;
        };
        var chainOrder500 = new Chain(order500);
        var chainOrder200 = new Chain(order200);
        var chainOrderNormal = new Chain(orderNormal);
        chainOrder500.setNextSuccessor(chainOrder200);//职责链的连接:函数是封闭的,暴露出接口是开放的(符合封闭-开放原则)
        chainOrder200.setNextSuccessor(chainOrderNormal);
        // chainOrder500.passRequest(1, true, 500); // 输出:500 元定金预购,得到 100 优惠券
        chainOrder500.passRequest(2, true, 500); // 输出:200 元定金预购,得到 50 优惠券
        // chainOrder500.passRequest(3, true, 500); // 输出:普通购买,无优惠券
        // chainOrder500.passRequest(1, false, 0); // 输出:手机库存不足

装饰者模式

装饰者模式将一个对象嵌入另一个对象之中,实际上相当于这个对象被另一个对象包装起来,形成一条包装链。请求随着这条链依次传递到所有的对象,每个对象都有处理这条请求的机会。
 /*给对象动态增加职责的方式,并没有真正地改动对象自身,而是将对象放入另一个对象
之中,这些对象以一条链的方式进行引用,形成一个聚合对象。这些对象都拥有相同的接口(fire
方法),当请求达到链中的某个对象时,这个对象会执行自身的操作,随后把请求转发给链中的
下一个对象。*/
        let Plane = function() {}
        Plane.prototype.fire = function() {
            console.log('plane');
        }
        let PlaneOne = function(plane) {
            this.plane=plane
        }
        PlaneOne.prototype.fire=function() {
            this.plane.fire()
            console.log('planeOne');
        }
        let plane = new Plane()
        let planeOne = new PlaneOne(plane)
        planeOne.fire()

/*但可以直接改写对象或者对象的某个方法,并不需要使用“类”来实现装饰者模式*/
var plane = {
            fire: function () {
                console.log('发射普通子弹');
            }
        }
        var missileDecorator = function () {
            console.log('发射导弹');
        }
        var atomDecorator = function () {
            console.log('发射原子弹');
        }
        var fire1 = plane.fire;
        plane.fire = function () {
            fire1();
            missileDecorator();
        }
        var fire2 = plane.fire;
        plane.fire = function () {
            fire2();
            atomDecorator();
        }
        plane.fire();
        fire1()
// 分别输出: 发射普通子弹、发射导弹、发射原子弹
装饰者模式中使用AOP把行为依照职责分成粒度更细的函数,随后通过装饰把它们合并到一起。(就是将不同功能分离在不同函数中,再使用AOP方式按顺序调用这些功能,并且其this指向明确),解析代码:
 /*11*/
        //报错,this指向不对(被劫持)
        var _getElementById = document.getElementById;
        document.getElementById = function (id) {
            alert(1);
            return _getElementById(id); // (1) 
        }
        var button = document.getElementById('button');

        //改变this指向,但该做法不方便
        var _getElementById = document.getElementById;
        document.getElementById = function () {
            alert(1);
            return _getElementById.apply(document,arguments); // (1) 
        }
        var button = document.getElementById('button');


        /*22*/
        //不使用AOP时新增新的onload事件,必须维护中间变量_onload(可能不止一个)
        window.onload = function () {
            alert(1);
        }
        var _onload = window.onload || function () { };
        window.onload = function () {
            _onload();
            alert(2);
        }

        /*上面两者的解决方式:AOP*/
        Function.prototype.before = function (beforefn) {
            var __self = this;
            return function () {
                beforefn.apply(this, arguments);//先执行参数
                return __self.apply(this, arguments);
            }
        }
        Function.prototype.after = function (afterfn) {
            var __self = this;
            return function () {
                var ret = __self.apply(this, arguments);
                afterfn.apply(this, arguments);//后执行参数
                return ret;
            }
        };

        // var button = document.getElementById('button');

        // document.getElementById = document.getElementById.before(function () {
        //     console.log(3);
        // }).after(function () {
        //     console.log(1);
        // });
        // console.log(button);


        window.onload = function () {
            alert(1);
        }
        window.onload = (window.onload || function () { }).after(function () {
            alert(2);
        }).after(function () {
            alert(3);
        }).after(function () {
            alert(4);
        });

        /*担心上面的方法会污染原型的话:*/
        var before = function (fn, beforefn) {
            return function () {
                beforefn.apply(this, arguments);
                return fn.apply(this, arguments);
            }
        }
        var a = before(
            function () { alert(3) },
            function () { alert(2) }
        );
        a = before(a, function () { alert(1); });
        a();

装饰者模式和代理模式的区别:

代理模式 强调一种一开始就可以被确定的关系,而装饰者模式用于一开始并不能确定对象的全部功能,在后期时通过参数再传入另一个功能函数,它可以形成一条长长的装饰链。

状态模式

状态模式的关键是把事物的每种状态都封装成单独的类,跟此种状态有关的行为都被封装在这个类的内部。
单一职责原则:
就一个类(通常也包括对象和函数等)而言,应该仅有一个引起它变 化的原因。如果一个对象承担了多项职责,就意味着这个对象将变得巨大,引起它变化的原因可能会有多个。面向对象设计鼓励将行为分布到细粒度的对象之中,如果一个对象承担的职责过多, 等于把这些职责耦合到了一起,这种耦合会导致脆弱和低内聚的设计。当变化发生时,设计可能会遭到意外的破坏。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值