设计模式——JavaScript

 // // DP1. 简单的工厂模式
    // // 1. 为解决多个相似的问题
    // function CarFactory(brand, price) {
    //     var car = new Object();
    //     car.brand = brand;
    //     car.price = price;
    //     car.getPrice = function () {
    //         return this.price;
    //     }
    //     return car;
    // }
    // var p1 = CarFactory("牌子A", 10000);
    // var p2 = CarFactory("牌子B", 20000);
    // console.log(typeof p1, typeof p2);  // 返回都是object,无法识别它们是哪个对象的实例
    // // 2. 优点:能解决多个相似问腿,减少代码冗余。专业术语叫做耦合度降低
    // // 3. 缺点:对象的类型未知

    // // DP2. 复杂工厂模式
    // // 1. 定义:
    // // 将其成员对象的实例化推迟到子类中,子类可以重写父类接口方法以便创建的时候制定自己的对象类型
    // // 即父类为抽象类,处理子类共同业务,具体业务子类实现
    // function ComplexCarFactory(brand, price) {
    //     this.brand = brand;
    //     this.price = price;
    // }
    // ComplexCarFactory.prototype = {
    //     constructor: ComplexCarFactory,
    //     sellCar: function () {
    //         var speed = this.getSpeed(this.brand);
    //         console.log(this.brand + '的车子售价:' + this.price + '元人民币,限速' + speed + '公里每小时');
    //     },
    //     getSpeed: function (brand) {
    //         throw new Error("父类是抽象类不能直接调用,需要子类重写该方法");
    //     }
    // };
    // var CarChild = function (brand, price) {
    //     this.brand = brand;
    //     this.price = price;
    //     // 继承构造函数父类中的属性和方法
    //     ComplexCarFactory.call(this, brand, price);
    // };
    // // 子类继承父类原型方法
    // CarChild.prototype = Object.create(ComplexCarFactory.prototype);
    // // CarChild 子类重写父类的方法
    // CarChild.prototype.getSpeed = function (brand) {
    //     var speed = 0;
    //     if (brand === '牌子C') {
    //         speend = 100;
    //     }
    //     if (brand === '牌子A') {
    //         speend = 2000;
    //     }
    //     return speed;
    // }
    // var p3 = new CarChild("牌子C", 3000);
    // console.log(p3); // CarChild {brand: "牌子C", price: 3000}  // 可以看到实例对象类型
    // console.log(p3 instanceof CarChild);  // true
    // console.log(p3.sellCar()); // 牌子C的车子售价:3000元人民币,限速100公里每小时
    // // 解析:ComplexCarFactory为父类,CarChild为子类,CarChild继承自ComplexCarFactory
    // // ComplexCarFactory不在进行对象实例化,只对创建过程中的一般性问题进行处理,ComplexCarFactory就像是Java中的抽象类,必须被子类重写,否则调用ComplexCarFactory的sellCall方法时就会抛出异常
    // // CarChild继承自ComplexCarFactory,同时重写了父类的方法,CarChild类实例后的对象之间是相互独立的,具体的业务逻辑会放在子类中进行编写
    // // 2. 优点:(面试官想听到的)
    // // 弱化对象间耦合,防止代码重复
    // // 重复性代码可以放到父类去编写,子类继承与父类的成员属性和方法。子类只专注于实现自己的业务逻辑

    // // DP3. [创建型] 单例模式
    // // 1. 单例模式限制某个类只能被创建一次,并且需要提供一个全局访问点
    // // 2. 关键:如果一个类的实例不存在,单例模式就会创建一个新的类实例
    // // 如果实例存在,它只返回对该对象的引用
    // // 对构造函数的任何重复调用都会获取相同的对象
    // // 3. 实例
    // let instance = null;
    // function User() {
    //     if (instance) {
    //         return instance;
    //     }
    //     instance = this;  // instance指向this
    //     this.name = 'Matthew';
    //     this.age = 23;
    //     return instance;
    // }

    // const user1 = new User();
    // const user2 = new User();
    // console.log(user1 === user2);  // true;
    // // 4. 实例(使用揭示模块模式)
    // const instance = (function () {
    //     let instance;
    //     function init() {
    //         return { name: 'Matthew', age: 23 };
    //     }
    //     function getInstance() {
    //         if (!instance) {
    //             instance = init();
    //         }
    //         return instance;
    //     }
    //     return {
    //         getInstance,
    //     }
    // })();
    // const instanceA = instance.getInstance();
    // const instanceB = instance.getInstance();
    // console.log(instanceA === instanceB);  // true
    // // 5. 应用场景:
    // // 应用中需要一个全局且唯一的的对象
    // // 6. 优点:
    // // 适用于单一对象,只生成一个实例,避免频繁创建和销毁实例,减少内存占用
    // // 7. 缺点:
    // // 不适用动态创建对象,或者需要创建多个类似对象的场景

    // var Singleton = function (name, age) {
    //     this.name = name;
    //     this.age = age;
    //     this.instance = null;
    // };
    // Singleton.prototype.getInfo = function () {
    //     console.log(`I am ${this.name}, i am ${this.age} years old.`);
    // }

    // function getInstance(name, age) {
    //     if (!this.instance) {
    //         this.instance = new Singleton(name, age);
    //     }
    //     return this.instance;
    // }

    // var a = getInstance('小王', 34);
    // var b = getInstance('小红', 26);
    // console.log(a === b);  // true  只会被创建一次
    // a.getInfo();  // I am 小王, i am 34 years old.
    // b.getInfo();  // I am 小王, i am 34 years old.

    // // 应用场景
    // // 创建遮罩层
    // var createMask = (function () {
    //     var div;
    //     return function () {
    //         if (!div) {
    //             div = document.createElement('div');
    //             div.className = "mask";
    //             div.innerHTML = "遮罩层";
    //             div.style.display = 'none';
    //             div.style.width = 800;
    //             div.style.height = 2400;
    //             div.style.background = 'green';
    //             document.body.appendChild(div);
    //         }
    //         return div;
    //     }
    // })();

    // document.addEventListener('click', function () {
    //     // debugger
    //     var win = createMask();
    //     win.style.display = 'block';
    // });

    // // 上述代码简单封装一下:
    // var getInstance = function (func) {
    //     var result;
    //     return function () {
    //         return result || (result = func.call(this, arguments));
    //     }
    // };
    // // 现在,不管我们需要实例化多少个对象,都使用getInstance来实现
    // // 创建遮罩层
    // var createMask = function () {
    //     var div = document.createElement('div');
    //     div.className = "mask";
    //     div.innerHTML = "遮罩层";
    //     div.style.display = 'none';
    //     div.style.width = 800;
    //     div.style.height = 2400;
    //     div.style.background = 'green';
    //     document.body.appendChild(div);
    //     return div;
    // }
    // // 创建iframe 
    // var createIframe = function () {
    //     var iframe = document.createElement('iframe');
    //     document.body.appendChild(iframe);
    //     return iframe;
    // };

    // // 测试创建遮罩层
    // var createSingleMask = getInstance(createMask);
    // document.addEventListener('click', function () {
    //     var win = createSingleMask();
    //     win.style.display = 'block';
    // });
    // // 测试创建iframe
    // var createSingleIframe = getInstance(createIframe);
    // document.addEventListener('click', function () {
    //     var win = createSingleIframe();
    //     win.src = "https://www.zhihu.com/question/20028810/answer/23249246";
    // });

    // // DP4. [创建型] 模块模式
    // // 1. 为类提供私有和共有封装的方法,通过此种方式,让一个对象拥有私有和共有的方法/变量,有效控制对外暴露的接口,屏蔽底层处理逻辑
    // // 2. JS中没有访问修饰符,可以通过立即执行函数,闭包和函数作用域实现
    // // 3. 实例
    // var obj = (function () {
    //     var count = 0;
    //     return {
    //         addCount: function () {
    //             count++;
    //         },
    //         getCount: function () {
    //             return count;
    //         }
    //     };
    // })();
    // // 最终外部采用调用模块的公有属性/方法来访问和操作私有变量
    // console.log(obj);
    // obj.addCount();
    // obj.getCount();  // 1
    // obj.count  // undefined
    // // 4. 应用场景
    // // 需要管理大量私有变量/方法,希望屏蔽内部处理逻辑,只对外暴露接口的独立模块
    // // 5. 优点:
    // // 采用封装的思想,只有本模块才能享有私有变量,不会暴露于外部模块
    // // 减少对全局作用域的影响,避免命名空间污染;
    // // 模块化有助于保持代码的整洁、隔离和条理性。
    // // 5. 缺点:
    // // 无法访问方法调用后产生的私有变量

    // DP5. [创建型] 揭示模块模式(改进的模块模式)
    // 1. 在模块模式基础上:在本模块的私有作用域范围内定义所有的方法和变量,将把返回对象的属性映射到我们想要公开的私有函数
    // 2. 实例
    // var obj = (function () {
    //     var count = 0;
    //     function addCount() {
    //         count++;
    //     }
    //     function getCount() {
    //         return count;
    //     }

    //     return {
    //         addCount, getCount,
    //     }
    // })();
    // console.log(obj);
    // console.log(obj.addCount());
    // console.log(obj.getCount());
    // 3. 相比于模块模式优点:
    // 方法从private改为public非常简单,只需要改属性映射
    // 返回对象不包括任何函数定义,公开的函数和变量一目了然,有助于提高代码可读性
    // 4. 相比于模块模式缺点:
    // 导致私有函数升级困难。如果一个私有函数引用一个共有函数,在需要打补丁的时候,共有函数不能被覆盖
    // function urlBuilder() {
    //     var _baseURL = "http://hao123.com";
    //     var _builder = function (relURL) {
    //         return _baseURL + relURL;
    //     }
    //     return {
    //         baseURL: _baseURL,
    //         build: _builder
    //     }
    // }

    // var builder = new urlBuilder();
    // builder.baseURL = "http://stackoverflow.com";
    // console.log(builder);  // {baseURL: "http://stackoverflow.com", build: ƒ}  builder的baseURL已经改变了
    // console.log(builder.build("/question?id=2005"));
    // // 期望: http://stackoverflow.com//question?id=2005
    // // 实际: http://hao123.com//question?id=2005
    // // 解决:加setXXX函数

    // DP6. [创建型] 代理模式
    // 6.1: 定义
    // 代理是一个对象,跟本体对象具有相同的接口,以此达到对本体对象的访问控制
    // 简言之,本体对象注重执行具体业务逻辑,而代理则控制着本体对象何时被实例化,何时被使用
    // 6.2: 优点
    // 代理对象可以代替本体被实例化,并使其可以被远程访问
    // 代理模式可以延迟创建开销很大的本体对象,它会把本体的实例化推迟到有方法被调用时
    // 6.3: 实例
    // var girl = function (name) {
    //     this.name = name;
    // }

    // var boy = function (girl) {
    //     this.girl = girl;
    //     this.sendGift = function (gift) {
    //         console.log(`Hi, ${girl.name}, 男孩子送你一个礼物:${gift}`);
    //     }
    // }

    // var proxyObj = function (girl) {
    //     this.girl = girl;
    //     this.sendGift = function (gift) {
    //         (new boy(girl)).sendGift(gift);
    //     }
    // };

    // var proxy = new proxyObj(new girl("小洋葱"));
    // proxy.sendGift('999朵玫瑰花');

    // 6.4: 使用场景:图片加载
    // 当图片过大或者网络较慢时,如果直接给img标签设置src属性,图片加载过程中有一段时间空白,降低了用户体验
    // 传统方案:在图片未加载完成之前,使用一个loading图标作为占位符,等图片完成加载后,在使用真正的图片地址代替loading图标
    // 不使用代理得预加载图片函数如下
    // var myImage = (function () {
    //     var imgNode = document.createElement("img");
    //     document.body.appendChild(imgNode);
    //     var img = new Image();
    //     img.onload = function () {
    //         imgNode.src = this.src;
    //     };
    //     var setSrc = function (src) {
    //         imgNode.src = "http://img.lanrentuku.com/img/allimg/1212/5-121204193R0.gif";
    //         img.src = src;
    //     }
    //     return { setSrc, }
    // })();

    // myImage.setSrc("https://www.baidu.com/img/bd_logo1.png");
    // 耦合性太高,myImage函数违背了单一职责原则,同时完成了多个任务

    // // 代理模式解决方案
    // var myImage = (function () {
    //     var imgNode = document.createElement('img');
    //     document.body.appendChild(imgNode);
    //     var setSrc = function (src) {
    //         imgNode.src = "http://img.lanrentuku.com/img/allimg/1212/5-121204193R0.gif";
    //     }
    //     return { setSrc, }
    // })();
    // var ProxyImage = (function () {
    //     var img = new Image();
    //     img.onload = function () {
    //         myImage.setSrc(this.src);
    //     }
    //     var setSrc = function (src) {
    //         myImage.src = "http://img.lanrentuku.com/img/allimg/1212/5-121204193R0.gif";
    //         img.src = src;
    //     }
    //     return {
    //         setSrc,
    //     }
    // })();

    // ProxyImage.setSrc("https://www.baidu.com/img/bd_logo1.png");

    // // 6.5: 缓存代理
    // var mult = function () {
    //     var a = 1;
    //     const len = arguments.length;
    //     for (let i = 0; i < len; i++) {
    //         a *= arguments[i];
    //     }
    //     return a;
    // }

    // var plus = function () {
    //     var a = 0;
    //     const len = arguments.length;
    //     for (let i = 0; i < len; i++) {
    //         a += arguments[i];
    //     }
    //     return a;
    // }

    // var proxyFunc = function (func) {
    //     var cache = {};
    //     return function () {
    //         var args = Array.prototype.join.call(arguments, ',');
    //         if (args in cache) {
    //             return cache[args]
    //         }
    //         return cache[args] = func.apply(this, arguments);
    //     }
    // }

    // var proxyMult = proxyFunc(mult);
    // console.log(proxyMult(1, 2, 3, 4));  // 24
    // console.log(proxyMult(1, 2, 3, 4));  // 取cache

    // var proxyPlus = proxyFunc(plus);
    // console.log(proxyPlus(1, 2, 3, 4));  // 10

    // DP7. [创建型] 职责链模式
    // 7.1 职责链是由多个不同对象组成的,有发送者和接收者。发送者是发送请求的对象,接收者接受请求并对其处理或者传递
    // 7.2 流程
    // 1. 发送者知道链中的第一个接收者,它向这个接收者发送该请求
    // 2. 每一个接收者都对请求进行分析,然后要么处理它,要么它往下传递
    // 3. 每一个接收者知道其他的对象只有一个,及它在链中的下家
    // 4. 如果没有任何接收者处理请求,那么请求会从链中离开
    // 7.3 场景描述
    // 以电商网站抽奖为例,规则如下:
    // 1. 用户充值500元,可以100%中奖100元红包
    // 2. 用户充值200元,可以100%中奖20元红包
    // 3. 用户不充值,可以抽奖,但是中奖概率极低
    // 4. 用户充值失败,按不充值处理
    // 7.4 普通实现
    // 1. orderType: 充值类型 1表示重置500元,2表示充值200元,3表示未充值
    // 2. isPay: 是否充值成功 true表示充值成功,false表示充值失败
    // 3. count: 表示数量  普通用户抽奖,如果数量有的话,就可以拿到优惠券。否则拿不到
    // var order = function (orderType, isPay, count) {
    //     if (orderType === 1) {
    //         if (isPay) {
    //             console.log('亲爱的用户,您中奖了100元红包');
    //         } else {
    //             if (count > 0) {
    //                 console.log('亲爱的用户,您已抽到10元优惠券');
    //             } else {
    //                 console.log('亲爱的用户,请再接再厉呦');
    //             }
    //         }
    //     } else if (orderType === 2) {
    //         if (isPay) {
    //             console.log('亲爱的用户,您中奖了20元红包');
    //         } else {
    //             if (count > 0) {
    //                 console.log('亲爱的用户,您已抽到10元优惠券');
    //             } else {
    //                 console.log('亲爱的用户,请再接再厉呦');
    //             }
    //         }
    //     } else if (orderType === 3) {
    //         if (count > 0) {
    //             console.log('亲爱的用户,您已抽到10元优惠券');
    //         } else {
    //             console.log('亲爱的用户,请再接再厉呦');
    //         }
    //     }
    // }
    // // 以上代码实现了业务需求,但是存在以下问题:
    // // 1. 业务逻辑代码耦合性太强,如果在增加条件,很容易改出问题
    // // 2. 冗余代码太多,普通用户抽奖代码可以抽离

    // // 7.5 职责链实现
    // function order500(orderType, isPay, count) {
    //     if (orderType === 1 && isPay) {
    //         console.log('亲爱的用户,您中奖了100元红包');
    //     } else {
    //         // 我也不知道下一个节点是谁,反正把请求往后面传递
    //         return 'nextSuccessor';
    //     }
    // }

    // function order200(orderType, isPay, count) {
    //     if (orderType === 2 && isPay) {
    //         console.log('亲爱的用户,您中奖了20元红包');
    //     } else {
    //         // 我也不知道下一个节点是谁,反正把请求往后面传递
    //         return 'nextSuccessor';
    //     }
    // }

    // function orderNormal(orderType, isPay, count) {
    //     if (count > 0) {
    //         console.log('亲爱的用户,您已抽到10元优惠券');
    //     } else {
    //         console.log('亲爱的用户,请再接再厉呦');
    //     }
    // }

    // // 职责链模式构造函数
    // var Chain = function (func) {
    //     this.func = func;
    //     this.successor = null;
    // };
    // Chain.prototype.setNextSuccessort = function (successor) {
    //     return this.successor = successor;
    // }
    // Chain.prototype.passRequest = function () {
    //     var ret = this.func.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.setNextSuccessort(chainOrder200);
    // chainOrder200.setNextSuccessort(chainOrderNormal);

    // // 把请求传递给第一个节点
    // chainOrder500.passRequest(1, true, 500);  // 亲爱的用户,您中奖了100元红包
    // chainOrder500.passRequest(2, true, 500);  // 亲爱的用户,您中奖了20元红包
    // chainOrder500.passRequest(3, true, 500);  // 亲爱的用户,您已抽到10元优惠券
    // chainOrder500.passRequest(1, false, 0);  // 亲爱的用户,请再接再厉呦

    // 一、分别编写order500,order200,orderNormal三个函数,处理自己的业务逻辑,如果自己的函数不能处理的话,就返回字符串nextSuccessor 往后面传递
    // 二、封装Chain构造函数,接收fn做为参数,且带有属性successor。实例化后的Chain类型的对象,就像一个一个独立存在的节点
    // 三、Chain构造函数原型上有2个方法,分别是setNextSuccessor 和 passRequest。setNextSuccessor 方法指定了节点在职责链中的顺序,passRequest方法是将请求转移到职责链的下一个节点
    // 7.6 优点
    // 解耦了请求发送者和多个接收者之间的关系,无需知道链中哪个节点可以处理你的需求,只需把需求传递到第一个节点就好
    // 链中节点可以灵活地拆分重组,增加或者删除一个和节点,都是很简单的事儿
    // 可以手动指定起始节点

    // 7.7 缺点
    // 职责链很长的时候,大部分节点可能都没起到实质性作用,时间都耗费到传递请求上了

    // DP8 策略模式
    // 8.1 定义:策略模式定义一系列算法,把他们一个个封装起来,并且使它们可以相互替换
    // 8.2 优点:
    // 1. 可以有效避免很多if条件语句
    // 2. 策略模式符合开放-封闭原则,代码跟容易看理解和扩展
    // 3. 可复用
    // 8.3 实战一
    // 以公司年终奖为例: 
    // 一、绩效为A的人,年终奖为工资的4倍 
    // 二、绩效为B的人,年终奖为工资的3倍 
    // 三、绩效为C的人,年终奖为工资的2倍
    // 传统实现
    // var calculateBonus = function (salary, level) {
    //     if (level === 'A') {
    //         return salary * 4;
    //     }
    //     if (level === 'B') {
    //         return salary * 3;
    //     }
    //     if (level === 'C') {
    //         return salary * 2;
    //     }
    // };

    // console.log(calculateBonus(4000, "A"));  // 16000
    // console.log(calculateBonus(2500, "B"));  // 7500
    // console.log(calculateBonus(2000, "C"));  // 4000

    // 使用组合函数重构代码
    // var performanceA = function (salary) {
    //     return salary * 4;
    // }
    // var performanceB = function (salary) {
    //     return salary * 3;
    // }
    // var performanceC = function (salary) {
    //     return salary * 2;
    // }

    // var calculateBonus = function (level, salary) {
    //     if (level === 'A') {
    //         return performanceA(salary);
    //     }
    //     if (level === 'B') {
    //         return performanceB(salary);
    //     }
    //     if (level === 'C') {
    //         return performanceC(salary);
    //     }
    // };

    // console.log(calculateBonus('A', 4500)); // 18000

    // 使用策略模式重构代码
    // 一个基于策略模式的程序至少由2部分组成,第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程
    // 第二个部分是环境类Context,该Context接收客户端的请求,随后把请求委托给某一个策略类
    // var obj = {
    //     'A': function (salary) {
    //         return salary * 4;
    //     },
    //     'B': function (salary) {
    //         return salary * 3;
    //     },
    //     'C': function (salary) {
    //         return salary * 2;
    //     },
    // };

    // var calculateBonus = function (level, salary) {
    //     return obj[level](salary);
    // }

    // console.log(calculateBouns('A', 10000)); // 40000

    // 8.4 实战二: 表单检验

    // var strategys = {
    //     //  非空
    //     isNotEmpty: function (value, errorMsg) {
    //         if (value === '') {
    //             return errorMsg;
    //         }
    //     },
    //     // 限制最小长度
    //     minLength: function (value, length, errorMsg) {
    //         if (value.length < length) {
    //             return errorMsg;
    //         }
    //     },
    //     // 手机号码格式
    //     mobileFormat: function (value, errorMsg) {
    //         if (!(/^1[3|5|8][0-9]{9}/.test(value))) {
    //             return errorMsg;
    //         }
    //     }
    // };

    // var Validator = function () {
    //     this.cache = {};  // 保存校验规则
    // };
    // Validator.prototype.add = function (dom, rules) {
    //     var self = this;
    //     for (var i = 0, rule; rule = rules[i++];) {
    //         (function (rule) {
    //             var strategyArr = rule.strategy.split(":");
    //             var errorMsg = rule.errorMsg;
    //             self.cache.push(function () {
    //                 var strategy = strateggArr.shift();
    //                 strategyArr.unshift(dom.value);
    //                 strategyArr.push(errorMsg);
    //                 return strategys[strategy].apply(dom, strategy);
    //             });
    //         })(rule);
    //     }
    // }

    // Validator.prototype.start = function () {
    //     for (var i = 0, validatorFunc; validatorFunc = this.cache[i++];) {
    //         var msg = validatorFunc();
    //         if (msg) {
    //             return msg;
    //         }
    //     }
    // }

    // var registerForm = document.getElementById("registerForm");
    // var validateFunc = function () {
    //     var validator = new Validator(); // 创建一个Validator对象
    //     /* 添加一些效验规则 */
    //     validator.add(registerForm.userName, [
    //         { strategy: 'isNotEmpty', errorMsg: '用户名不能为空' },
    //         { strategy: 'minLength:6', errorMsg: '用户名长度不能小于6位' }
    //     ]);
    //     validator.add(registerForm.password, [
    //         { strategy: 'minLength:6', errorMsg: '密码长度不能小于6位' },
    //     ]);
    //     validator.add(registerForm.phoneNumber, [
    //         { strategy: 'mobileFormat', errorMsg: '手机号格式不正确' },
    //     ]);
    //     var errorMsg = validator.start(); // 获得效验结果
    //     return errorMsg; // 返回效验结果
    // };

    // // 点击确定提交
    // registerForm.onsubmit = function () {
    //     var errorMsg = validateFunc();
    //     if (errorMsg) {
    //         alert(errorMsg);
    //         return false;
    //     }
    // }

    // DP8 发布-订阅模式(观察者模式)
    // 8.1 定义:定义了对象间的一种一对多关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知
    // 8.2 场景:
    // 以网购为例:
    // 假设小红看上了一双鞋子,但该鞋子已经断货了,卖家承诺她到货通知。与此同时,小明、小花灯也关注了这双鞋子。
    // 在这个场景中,卖家就是发布者,小红等人都属于订阅者。当鞋子到货时,会依次通知到每个人。
    // 8.2 优点
    // 1. 支持简单的广播通信,当对象状态发生改变时,会自动通知已经订阅过的对象
    // 2. 发布者与订阅者耦合度降低,发布者只管发布消息,订阅者只管监听发布者的事件名
    // 8.3 缺点
    // 创建订阅者需要消耗一定的时间和内存
    // 8.4 如何实现
    // 首先想好谁是发布者,然后给发布者添加一个缓存列表,用于存放回调函数来通知订阅者,最后就是发布消息,发布者遍历这个缓存列表,依次触发里面存放的订阅者回调函数
    // 8.5 代码
    // var Event = (function () {
    //     var list = {},
    //         listen,
    //         trigger,
    //         remove;
    //     listen = function (key, fn) {
    //         if (!list[key]) {
    //             // 如果还没有订阅过此类消息,给该类消息创建一个缓存列表
    //             list[key] = [];
    //         }
    //         list[key].push(fn); // 订阅消息添加到缓存列表
    //     };
    //     trigger = function () {
    //         var key = Array.prototype.shift.call(arguments), // 取出消息类型名称
    //             fns = list[key]; // 取出该消息对应的回调函数的集合
    //         // 如果没有订阅过该消息的话,则返回
    //         if (!fns || fns.length === 0) {
    //             return false;
    //         }
    //         for (var i = 0, fn; fn = fns[i++];) {
    //             fn.apply(this, arguments); // arguments 是发布消息时附送的参数
    //         }
    //     };
    //     remove = function (key, fn) {
    //         // 如果key对应的消息没有订阅过的话,则返回
    //         var fns = list[key];
    //         // 如果没有传入具体的回调函数,表示需要取消key对应消息的所有订阅
    //         if (!fns) {
    //             return false;
    //         }
    //         if (!fn) {
    //             fns && (fns.length = 0);
    //         } else {
    //             for (var i = fns.length - 1; i >= 0; i--) {
    //                 var _fn = fns[i];
    //                 if (_fn === fn) {
    //                     fns.splice(i, 1);// 删除订阅者的回调函数
    //                 }
    //             }
    //         }
    //     };
    //     return {
    //         listen,
    //         trigger,
    //         remove,
    //     }
    // })();
    // // 测试代码如下:
    // Event.listen("size", function (name) {
    //     console.log("亲爱的用户" + name + "你好,42号尺码的鞋子到货了.");
    // });
    // Event.trigger("size", ['王杰', '小明', '王炯', '刘涛']);
    // // 亲爱的用户王杰,小明,王炯,刘涛你好,42号尺码的鞋子到货了.

转载自:https://juejin.im/post/5c0e6ab96fb9a049f43b26da

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值