├── README.md ├── adapter-pattern └── README.md ├── chain-of-responsibility-pattern └── README.md ├── closure ├── 3.2.html └── README.md ├── command-pattern ├── JS中的命令模式.js ├── README.md ├── 宏命令.js ├── 撤销命令.js ├── 撤销和重做.js └── 模拟传统面向对象的命令模式.js ├── composite-pattern ├── 10.html └── README.md ├── decorator-pattern ├── 15.html └── README.md ├── factory-pattern └── README.md ├── iterator-pattern ├── README.md ├── jquery的迭代器封装各种行为.js ├── 倒序迭代和中止迭代.js ├── 内部迭代器和外部迭代器.js └── 实现自己的迭代器.js ├── object-oriented └── README.md ├── proxy-pattern ├── README.md ├── 最简单的代理模式.js ├── 缓存代理.js ├── 虚拟代理合并http请求.js ├── 虚拟代理在惰性加载中的应用.js └── 虚拟代理实现图片预加载.js ├── publish-subscribe-pattern ├── README.md ├── dom事件.js ├── 全局事件的命名冲突.js ├── 全局的发布订阅对象.js ├── 取消订阅.js ├── 最简单实现.js ├── 模块间通信.js ├── 网站登录的应用.js └── 通用实现.js ├── singleton-pattern └── README.md ├── state-pattern ├── 16.html └── README.md ├── strategy-pattern ├── JS版本的策略模式.js ├── README.md ├── 不使用策略模式.js ├── 基于面向对象语言的策略模式.js ├── 策略模式验证表单.js ├── 给某个input添加多种检验规则.js └── 缓动动画.js ├── template-method-pattern └── 11.html ├── this-call-apply └── README.md ├── 中介者模式 ├── 14.html └── README.md ├── 享元模式 ├── 12.html └── README.md ├── 代码重构 ├── 22.html └── README.md ├── 动态创建命名空间.js ├── 单一职责原则 ├── 18.html └── README.md ├── 开放封闭原则 ├── 20.html └── README.md ├── 接口和面向接口编程 ├── 21.html └── README.md └── 最少知识原则 ├── 19.html └── README.md /README.md: -------------------------------------------------------------------------------- 1 | # JS-design-pattern 2 | 3 | 根据曾探所著《JavaScript设计模式与开发实践》整理的学习笔记 4 | 5 | ## 基础知识 6 | 7 | - [面向对象的JavaScript](object-oriented) 8 | - [this call apply](this-call-apply) 9 | - [闭包](closure) 10 | 11 | ## 创建模式 12 | 13 | - [单例模式](singleton-pattern) 14 | - [工厂模式](factory-pattern) 15 | 16 | ## 结构模式 17 | 18 | - [适配器模式](adapter-pattern) 19 | - [组合模式](composite-pattern) 20 | - [装饰器模式](decorator-pattern) 21 | - [代理模式](proxy-pattern) 22 | 23 | ## 行为模式 24 | 25 | - [职责链模式](chain-of-responsibility-pattern) 26 | - [命令模式](command-pattern) 27 | - [状态模式](state-pattern) 28 | - [策略模式](strategy-pattern) 29 | - [发布订阅模式](publish-subscribe-pattern) 30 | -------------------------------------------------------------------------------- /adapter-pattern/README.md: -------------------------------------------------------------------------------- 1 | # 适配器模式 2 | 3 | ```javascript 4 | var googleMap = { 5 | show: function () { 6 | console.log('开始渲染谷歌地图'); 7 | } 8 | }; 9 | 10 | var baiduMap = { 11 | show: function () { 12 | console.log('开始渲染百度地图'); 13 | } 14 | }; 15 | 16 | var renderMap = function (map) { 17 | if (map.show instanceof Function) { 18 | map.show(); 19 | } 20 | }; 21 | 22 | renderMap(googleMap); // 输出:开始渲染谷歌地图 23 | renderMap(baiduMap); // 输出:开始渲染百度地图 24 | ``` 25 | 26 | ```javascript 27 | var googleMap = { 28 | show: function () { 29 | console.log('开始渲染谷歌地图'); 30 | } 31 | }; 32 | 33 | var baiduMap = { 34 | display: function () { 35 | console.log('开始渲染百度地图'); 36 | } 37 | }; 38 | 39 | var baiduMapAdapter = { 40 | show: function(){ 41 | return baiduMap.display(); 42 | } 43 | }; 44 | 45 | renderMap(googleMap); // 输出:开始渲染谷歌地图 46 | renderMap(baiduMapAdapter); // 输出:开始渲染百度地图 47 | ``` 48 | 49 | ```javascript 50 | var getGuangdongCity = function () { 51 | var guangdongCity = [ 52 | { 53 | name: 'shenzhen', 54 | id: 11, 55 | }, 56 | { 57 | name: 'guangzhou', 58 | id: 12, 59 | }, 60 | ]; 61 | return guangdongCity; 62 | }; 63 | 64 | var render = function (fn) { 65 | console.log('开始渲染广东省地图'); 66 | document.write(JSON.stringify(fn())); 67 | }; 68 | render(getGuangdongCity); 69 | ``` 70 | 71 | ```javascript 72 | var guangdongCity = { 73 | shenzhen: 11, 74 | guangzhou: 12, 75 | zhuhai: 13, 76 | }; 77 | 78 | var getGuangdongCity = function () { 79 | var guangdongCity = [ 80 | { 81 | name: 'shenzhen', 82 | id: 11, 83 | }, 84 | { 85 | name: 'guangzhou', 86 | id: 12, 87 | }, 88 | ]; 89 | return guangdongCity; 90 | }; 91 | 92 | var render = function (fn) { 93 | console.log('开始渲染广东省地图'); 94 | document.write(JSON.stringify(fn())); 95 | }; 96 | 97 | var addressAdapter = function(oldAddressfn) { 98 | var address = {}, 99 | oldAddress = oldAddressfn(); 100 | for (let i = 0, c; c = oldAddress[i++];){ 101 | address[c.name] = c.id; 102 | } 103 | return function () { 104 | return address; 105 | } 106 | }; 107 | render(addressAdapter(getGuangdongCity)); 108 | ``` 109 | -------------------------------------------------------------------------------- /chain-of-responsibility-pattern/README.md: -------------------------------------------------------------------------------- 1 | # 职责链模式 2 | 3 | 职责链,一系列可能处理请求的对象连接成一条链,请求在这些对象之间依次传递,直到遇到一个可以处理它的对象 4 | 5 | 示例代码 6 | 7 | ```javascript 8 | const order500 = function (orderType, pay, stock) { 9 | if (orderType === 1 && pay === true) { 10 | console.log('500元定金预购,得到100元优惠券'); 11 | } else { 12 | order200(orderType, pay, stock); 13 | } 14 | } 15 | 16 | const order200 = function (orderType, pay, stock) { 17 | if (orderType === 2 && pay === true) { 18 | console.log('200元定金预购,得到50元优惠券'); 19 | } else { 20 | orderNormal(orderType, pay, stock); 21 | } 22 | } 23 | 24 | const orderNormal = function (orderType, pay, stock) { 25 | if (stock > 0) { 26 | console.log('普通购买,无优惠券'); 27 | } else { 28 | console.log('手机库存不足'); 29 | } 30 | } 31 | 32 | order500(1, true, 500); 33 | order500(1, false, 500); 34 | order500(2, true, 500); 35 | ``` 36 | 37 | 非职责链模式 38 | 39 | ```javascript 40 | var order = function (orderType, pay, stock) { 41 | if (orderType === 1){ // 500 元定金购买模式 42 | if (pay === true){ // 已支付定金 43 | console.log('500 元定金预购, 得到100 优惠券'); 44 | } else { // 未支付定金,降级到普通购买模式 45 | if (stock > 0){ // 用于普通购买的手机还有库存 46 | console.log('普通购买, 无优惠券'); 47 | }else{ 48 | console.log('库存不足'); 49 | } 50 | } 51 | } else if (orderType === 2) { // 200 元定金购买模式 52 | if (pay === true){ 53 | console.log('200 元定金预购, 得到50 优惠券'); 54 | } else { 55 | if (stock > 0){ 56 | console.log('普通购买, 无优惠券'); 57 | } else { 58 | console.log('手机库存不足'); 59 | } 60 | } 61 | } else if (orderType === 3) { 62 | if (stock > 0){ 63 | console.log('普通购买, 无优惠券'); 64 | } else { 65 | console.log('手机库存不足'); 66 | } 67 | } 68 | }; 69 | order(1 , true, 500); // 输出: 500 元定金预购, 得到100 优惠券 70 | ``` 71 | 72 | 进一步优化后 73 | 74 | ```javascript 75 | var order500 = function (orderType, pay, stock) { 76 | if (orderType === 1 && pay === true){ 77 | console.log('500 元定金预购, 得到100 优惠券'); 78 | } else { 79 | order200(orderType, pay, stock); // 将请求传递给200 元订单 80 | } 81 | }; 82 | 83 | var order200 = function (orderType, pay, stock) { 84 | if (orderType === 2 && pay === true) { 85 | console.log('200 元定金预购, 得到50 优惠券'); 86 | } else { 87 | orderNormal(orderType, pay, stock); // 将请求传递给普通订单 88 | } 89 | }; 90 | 91 | var orderNormal = function (orderType, pay, stock) { 92 | if (stock > 0) { 93 | console.log('普通购买, 无优惠券'); 94 | } else { 95 | console.log('手机库存不足'); 96 | } 97 | }; 98 | 99 | // 测试结果: 100 | order500(1, true, 500); // 输出:500 元定金预购, 得到100 优惠券 101 | order500(1, false, 500); // 输出:普通购买, 无优惠券 102 | order500(2, true, 500); // 输出:200 元定金预购, 得到500 优惠券 103 | order500(3, false, 500); // 输出:普通购买, 无优惠券 104 | order500(3, false, 0); // 输出:手机库存不足 105 | ``` 106 | 107 | 再优化 108 | 109 | ```javascript 110 | var order500 = function (orderType, pay, stock) { 111 | if (orderType === 1 && pay === true) { 112 | console.log('500 元定金预购,得到100 优惠券'); 113 | } else { 114 | return 'nextSuccessor'; // 我不知道下一个节点是谁,反正把请求往后面传递 115 | } 116 | }; 117 | 118 | var order200 = function (orderType, pay, stock) { 119 | if (orderType === 2 && pay === true) { 120 | console.log('200 元定金预购,得到50 优惠券'); 121 | } else { 122 | return 'nextSuccessor'; // 我不知道下一个节点是谁,反正把请求往后面传递 123 | } 124 | }; 125 | 126 | var orderNormal = function (orderType, pay, stock) { 127 | if (stock > 0) { 128 | console.log('普通购买,无优惠券'); 129 | } else { 130 | console.log('手机库存不足'); 131 | } 132 | }; 133 | 134 | var Chain = function (fn) { 135 | this.fn = fn; 136 | this.successor = null; 137 | }; 138 | 139 | // 指定在链中的下一个节点 140 | Chain.prototype.setNextSuccessor = function (successor) { 141 | return this.successor = successor; 142 | }; 143 | 144 | // 传递请求给某个节点 145 | Chain.prototype.passRequest = function () { 146 | let ret = this.fn.apply(this, arguments); 147 | if (ret === 'nextSuccessor'){ 148 | return this.successor 149 | && this.successor.passRequest.apply(this.successor, arguments); 150 | } 151 | return ret; 152 | }; 153 | 154 | var chainOrder500 = new Chain(order500); 155 | var chainOrder200 = new Chain(order200); 156 | var chainOrderNormal = new Chain(orderNormal); 157 | 158 | chainOrder500.setNextSuccessor(chainOrder200); 159 | chainOrder200.setNextSuccessor(chainOrderNormal); 160 | chainOrder500.passRequest(1, true, 500); // 输出:500 元定金预购,得到100 优惠券 161 | chainOrder500.passRequest(2, true, 500); // 输出:200 元定金预购,得到50 优惠券 162 | chainOrder500.passRequest(3, true, 500); // 输出:普通购买,无优惠券 163 | chainOrder500.passRequest(1, false, 0); // 输出:手机库存不足 164 | 165 | ``` 166 | 167 | 链式传递 168 | 169 | ```javascript 170 | Function.prototype.after = function (fn) { 171 | let self = this; 172 | return function () { 173 | let ret = self.apply(this, arguments); 174 | if (ret === 'nextSuccessor'){ 175 | return fn.apply(this, arguments); 176 | } 177 | return ret; 178 | } 179 | }; 180 | 181 | const order = order500yuan.after(order200yuan).after(orderNormal); 182 | order(1, true, 500); // 输出:500 元定金预购,得到100 优惠券 183 | order(2, true, 500); // 输出:200 元定金预购,得到50 优惠券 184 | order(1, false, 500); // 输出:普通购买,无优惠券 185 | ``` -------------------------------------------------------------------------------- /closure/3.2.html: -------------------------------------------------------------------------------- 1 |  364 | 365 | 366 | 367 |
点我绑定事件
368 | 390 | 391 | 392 | -------------------------------------------------------------------------------- /closure/README.md: -------------------------------------------------------------------------------- 1 | # 闭包与高阶函数 2 | 3 | ## 词法作用域 4 | 5 | 词法作用域,也叫静态作用域,是在代码定义时确定的,关注函数在何处声明。 6 | 7 | 最内嵌套作用域规则,由一个声明引进的标志符在这个声明所在的作用域里可见, 8 | 而且在其内部嵌套的每个作用域里也可见,除非被内部覆盖。 9 | 10 | ## 动态作用域 11 | 12 | 动态作用域,是在代码运行时确定的,关注函数从何处调用。 13 | 14 | 从最近的活动记录中寻找变量的值。 15 | 16 | ## 闭包 17 | 18 | `f` 返回了一个匿名函数的引用 19 | 它可以访问到 `func()` 被调用时产生的环境,而局部变量 `a` 一直处在这个环境里, 20 | 局部变量就有了不被销毁的理由,从而产生了一个 **闭包结构** 21 | 22 | ```javascript 23 | var func = function () { 24 | var a = 1; 25 | return function () { 26 | a++; 27 | console.log(a); 28 | } 29 | }; 30 | var f = func(); 31 | 32 | f(); // 输出:2 33 | f(); // 输出:3 34 | ``` 35 | 36 | ### 闭包作用 37 | 38 | * 封装变量 39 | * 延续局部变量寿命 40 | 41 | ```javascript 42 | // 利用闭包,把小函数封闭起来 43 | // 加入缓存机制提高函数性能,避免重复计算 44 | var mult = (function(){ 45 | var cache = {}; 46 | var calculate = function () { 47 | var a = 1; 48 | for (var i = 0, l = arguments.length; i < l; i++) { 49 | a = a * arguments[i]; 50 | } 51 | return a; 52 | }; 53 | 54 | return function () { 55 | var args = Array.prototype.join.call(arguments, ','); 56 | if (cache[args]) { 57 | return cache[args]; 58 | } 59 | return cache[args] = calculate.apply(null, arguments); 60 | } 61 | })(); 62 | 63 | console.log(mult(1,2,3)); // 输出:6 64 | console.log(mult(1,2,3)); // 输出:6 65 | ``` 66 | 67 | ### 用闭包实现面向对象 68 | 69 | ```javascript 70 | var extent = function(){ 71 | var value = 0; 72 | return { 73 | call: function(){ 74 | value++; 75 | console.log( value ); 76 | } 77 | } 78 | }; 79 | 80 | var extent = extent(); 81 | extent.call(); // 输出:1 82 | extent.call(); // 输出:2 83 | extent.call(); // 输出:3 84 | ``` 85 | 86 | ### 闭包与内存管理 87 | 88 | 把变量放在闭包中和放在全局作用域对内存的影响是一致,这并不能说是内存泄露。 89 | 90 | 和内存泄露有关系的地方是:使用闭包的同时比较容易形成循环引用, 91 | 如果闭包的作用域链中保存着一些DOM节点,这时候就有可能造成内存泄露。 92 | 93 | 把变量手动设为 `null` 意味着切断变量与它之前引用的值之间的连接, 94 | 当垃圾收集器下次运行时就会删除这些值,并回收占用的内存。 95 | 96 | ## 高阶函数 97 | 98 | * 函数可以作为参数被传递 99 | * 回调函数,把可变部分封装为回调 100 | * Array.prototype.sort 101 | * 函数可以作为返回值输出 102 | 103 | ```javascript 104 | // 这个高阶函数的例子,既把函数当作参数传递,又让函数执行后返回了另外一个函数。 105 | var getSingle = function (fn) { 106 | var ret; 107 | return function () { 108 | return ret || (ret = fn.apply(this, arguments)); 109 | }; 110 | }; 111 | 112 | var getScript = getSingle(function(){ 113 | return document.createElement('script'); 114 | }); 115 | var script1 = getScript(); 116 | var script2 = getScript(); 117 | console.log(script1 === script2); // 输出:true 118 | ``` 119 | 120 | ### 高阶函数实现面向切面编程 AOP 121 | 122 | > AOP 面向切面编程 主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来, 123 | > 这些功能通常包括日志统计、安全控制、异常处理等。 124 | > 把这些功能抽离出来后,再通过『动态织入』的方式掺入到业务逻辑中。 125 | > 好处是:可以保持业务逻辑的纯净和高内聚性,其次可以方便复用其他功能模块。 126 | > 类似装饰器模式 127 | 128 | ```javascript 129 | Function.prototype.before = function (beforefn) { 130 | var __self = this; // 保存原函数的引用 131 | return function () { // 返回包含了原函数和新函数的"代理"函数 132 | beforefn.apply(this, arguments); // 执行新函数,修正this 133 | return __self.apply(this, arguments); // 执行原函数 134 | } 135 | }; 136 | 137 | Function.prototype.after = function (afterfn) { 138 | var __self = this; 139 | return function () { 140 | var ret = __self.apply(this, arguments); 141 | afterfn.apply(this, arguments); 142 | return ret; 143 | } 144 | }; 145 | 146 | var func = function () { 147 | console.log(2); 148 | }; 149 | 150 | func = func.before(function () { 151 | console.log(1); 152 | }).after(function(){ 153 | console.log(3); 154 | }); 155 | 156 | func(); // 输出 1 2 3 157 | ``` 158 | 159 | ### Currying 160 | 161 | > currying 又称为 部分求值。 162 | > 一个 currying 的函数首先会接收一些参数,接受了这些参数后不会立即求值, 163 | > 而是继续返回另一个函数,刚传入的参数在函数形成的闭包中被保存起来。 164 | > 等到函数被真正求值时,所有参数才会一次性求值 165 | 166 | 167 | ```javascript 168 | var currying = function (fn) { 169 | var args = []; 170 | return function () { 171 | if (arguments.length === 0){ 172 | return fn.apply(this, args); 173 | }else{ 174 | [].push.apply(args, arguments); 175 | return arguments.callee; 176 | } 177 | } 178 | }; 179 | var cost = (function () { 180 | var money = 0; 181 | return function () { 182 | for (var i = 0, l = arguments.length; i < l; i++) { 183 | money += arguments[i]; 184 | } 185 | return money; 186 | } 187 | })(); 188 | 189 | var cost = currying(cost); // 转化成currying 函数 190 | cost(100); // 未真正求值 191 | cost(200); // 未真正求值 192 | cost(300); // 未真正求值 193 | console.log(cost()); // 求值并输出:600 194 | ``` 195 | 196 | ### uncurrying 197 | 198 | ```javascript 199 | Function.prototype.uncurrying = function () { 200 | var self = this; 201 | return function () { 202 | var obj = Array.prototype.shift.call(arguments); 203 | return self.apply(obj, arguments); 204 | }; 205 | }; 206 | 207 | var push = Array.prototype.push.uncurrying(); 208 | 209 | (function(){ 210 | push(arguments, 4); 211 | console.log(arguments); // 输出:[1, 2, 3, 4] 212 | })(1, 2, 3); 213 | ``` 214 | 215 | -------------------------------------------------------------------------------- /command-pattern/JS中的命令模式.js: -------------------------------------------------------------------------------- 1 | // 不适用命令模式 2 | var bindClick = function( button, func ){ 3 | button.onclick = func; 4 | }; 5 | var MenuBar = { 6 | refresh: function(){ 7 | console.log( '刷新菜单界面' ); 8 | } 9 | }; 10 | var SubMenu = { 11 | add: function(){ 12 | console.log( '增加子菜单' ); 13 | }, 14 | del: function(){ 15 | console.log( '删除子菜单' ); 16 | } 17 | }; 18 | bindClick( button1, MenuBar.refresh ); 19 | bindClick( button2, SubMenu.add ); 20 | bindClick( button3, SubMenu.del ); 21 | 22 | // 闭包实现的命令模式 23 | var setCommand = function( button, func ){ 24 | button.onclick = function(){ 25 | func(); 26 | } 27 | }; 28 | var MenuBar = { // 接收者 29 | refresh: function(){ 30 | console.log( '刷新菜单界面' ); 31 | } 32 | }; 33 | var RefreshMenuBarCommand = function( receiver ){ // 命令类 34 | return function(){ 35 | receiver.refresh(); 36 | } 37 | }; 38 | var refreshMenuBarCommand = RefreshMenuBarCommand( MenuBar ); // command 对象 39 | setCommand( button1, refreshMenuBarCommand ); 40 | 41 | // 为类更明确表达当前正在使用命令模式,把执行函数改为调用execute方法 42 | var RefreshMenuBarCommand = function( receiver ){ 43 | return { 44 | execute: function(){ 45 | receiver.refresh(); 46 | } 47 | } 48 | }; 49 | var setCommand = function( button, command ){ 50 | button.onclick = function(){ 51 | command.execute(); 52 | } 53 | }; 54 | 55 | var refreshMenuBarCommand = RefreshMenuBarCommand( MenuBar ); 56 | setCommand( button1, refreshMenuBarCommand ); 57 | 58 | -------------------------------------------------------------------------------- /command-pattern/README.md: -------------------------------------------------------------------------------- 1 | # 命令模式 2 | 3 | ## 应用场景 4 | 5 | 需要向某些对象发送请求,但并不知道请求的接收者是谁,也不知道被请求的操作是什么。 6 | 用一种松耦合的方式来设计程序,使得发送者和请求接收者能够消除彼此之间的耦合关系。 7 | 8 | 举例子: 9 | 10 | 客人向厨师发送请求,但完全不知道厨师名字和炒菜方式。命令模式把客人的**订餐请求**封装成 `command` 对象,也就是订单对象,这个对象可以四处传递(通过服务员传到厨师手中),客人不需要知道厨师的名字,从而解开了请求调用者和接收者之间的耦合关系。 11 | 12 | 命令模式还支持撤销、排队等操作。 13 | 14 | 再举例子: 15 | 16 | 点击按钮后,必须向某些负责具体行为的对象发送请求,这些对象就是请求的接收者。但目前并不知道接收者是什么对象,也不知道接收者究竟会做什么。此时借助**命令对象**的帮助,解开按钮和负责具体行为的对象之间的耦合。 17 | 18 | ## 不同的实现 19 | 20 | 在面向对象设计中,命令模式的接收者被当作command对象的属性保存起来, 21 | 约定执行命令的操作是调用 `command.execute` 方法。 22 | 23 | 在使用闭包的命令模式中,接收者被封装在闭包产生的环境中, 24 | 执行命令的操作是执行回调函数。 25 | 26 | 无论哪种实现,在将来执行命令时,都能顺利访问到接收者。 27 | 28 | ## 命令队列 29 | 30 | 一个命令执行结束后该如何通知队列,通常可以使用回调函数来通知队列,此外还可以选择发布订阅模式,在命令结束后发布一个消息,订阅者收到消息后便开始执行队列里的下一个命令。 31 | 32 | ## 宏命令 33 | 34 | 一组命令的集合,通过执行宏命令的方式,可以一次执行一批命令。 35 | 宏命令是命令模式与组合模式的联用产物。 36 | -------------------------------------------------------------------------------- /command-pattern/宏命令.js: -------------------------------------------------------------------------------- 1 | var closeDoorCommand = { 2 | execute: function(){ 3 | console.log( '关门' ); 4 | } 5 | }; 6 | var openPcCommand = { 7 | execute: function(){ 8 | console.log( '开电脑' ); 9 | } 10 | }; 11 | 12 | var openQQCommand = { 13 | execute: function(){ 14 | console.log( '登录QQ' ); 15 | } 16 | }; 17 | 18 | var MacroCommand = function(){ 19 | return { 20 | commandsList: [], 21 | add: function( command ){ 22 | this.commandsList.push( command ); 23 | }, 24 | execute: function(){ 25 | for ( var i = 0, command; command = this.commandsList[ i++ ]; ){ 26 | command.execute(); 27 | } 28 | } 29 | } 30 | }; 31 | var macroCommand = MacroCommand(); 32 | macroCommand.add( closeDoorCommand ); 33 | macroCommand.add( openPcCommand ); 34 | macroCommand.add( openQQCommand ); 35 | macroCommand.execute(); 36 | 37 | -------------------------------------------------------------------------------- /command-pattern/撤销命令.js: -------------------------------------------------------------------------------- 1 | // 命令模式,没有撤销功能 2 | var ball = document.getElementById( 'ball' ); 3 | var pos = document.getElementById( 'pos' ); 4 | var moveBtn = document.getElementById( 'moveBtn' ); 5 | 6 | var MoveCommand = function( receiver, pos ){ 7 | this.receiver = receiver; 8 | this.pos = pos; 9 | }; 10 | MoveCommand.prototype.execute = function(){ 11 | this.receiver.start( 'left', this.pos, 1000, 'strongEaseOut' ); 12 | }; 13 | 14 | var moveCommand; 15 | moveBtn.onclick = function(){ 16 | var animate = new Animate( ball ); 17 | moveCommand = new MoveCommand( animate, pos.value ); 18 | moveCommand.execute(); 19 | }; 20 | 21 | // 命令模式,具有撤销功能 22 | var ball = document.getElementById( 'ball' ); 23 | var pos = document.getElementById( 'pos' ); 24 | var moveBtn = document.getElementById( 'moveBtn' ); 25 | var cancelBtn = document.getElementById( 'cancelBtn' ); 26 | var MoveCommand = function( receiver, pos ){ 27 | this.receiver = receiver; 28 | this.pos = pos; 29 | this.oldPos = null; 30 | }; 31 | MoveCommand.prototype.execute = function(){ 32 | this.receiver.start( 'left', this.pos, 1000, 'strongEaseOut' ); 33 | // 记录小球开始移动前的位置 34 | this.oldPos = this.receiver.dom.getBoundingClientRect()[ this.receiver.propertyName ]; 35 | }; 36 | 37 | // 撤销 undo 方法 38 | MoveCommand.prototype.undo = function(){ 39 | this.receiver.start( 'left', this.oldPos, 1000, 'strongEaseOut' ); 40 | // 回到小球移动前记录的位置 41 | }; 42 | 43 | var moveCommand; 44 | moveBtn.onclick = function(){ 45 | var animate = new Animate( ball ); 46 | moveCommand = new MoveCommand( animate, pos.value ); 47 | moveCommand.execute(); 48 | }; 49 | 50 | cancelBtn.onclick = function(){ 51 | moveCommand.undo(); // 撤销命令 52 | }; 53 | 54 | -------------------------------------------------------------------------------- /command-pattern/撤销和重做.js: -------------------------------------------------------------------------------- 1 | // 逆转不可逆命令的办法是 2 | // 利用一个历史列表堆栈,记录命令日志,然后重复执行它们 3 | var Ryu = { 4 | attack: function(){ 5 | console.log( '攻击' ); 6 | }, 7 | defense: function(){ 8 | console.log( '防御' ); 9 | }, 10 | jump: function(){ 11 | console.log( '跳跃' ); 12 | }, 13 | crouch: function(){ 14 | console.log( '蹲下' ); 15 | } 16 | }; 17 | 18 | var makeCommand = function( receiver, state ){ // 创建命令 19 | return function(){ 20 | receiver[ state ](); 21 | } 22 | }; 23 | var commands = { 24 | "119": "jump", // W 25 | "115": "crouch", // S 26 | "97": "defense", // A 27 | "100": "attack" // D 28 | }; 29 | 30 | var commandStack = []; // 保存命令的堆栈 31 | 32 | document.onkeypress = function( ev ){ 33 | var keyCode = ev.keyCode, 34 | command = makeCommand( Ryu, commands[ keyCode ] ); 35 | if ( command ){ 36 | command(); // 执行命令 37 | commandStack.push( command ); // 将刚刚执行过的命令保存进堆栈 38 | } 39 | }; 40 | 41 | document.getElementById( 'replay' ).onclick = function(){ // 点击播放录像 42 | var command; 43 | while( command = commandStack.shift() ){ // 从堆栈里依次取出命令并执行 44 | command(); 45 | } 46 | }; 47 | 48 | 49 | -------------------------------------------------------------------------------- /command-pattern/模拟传统面向对象的命令模式.js: -------------------------------------------------------------------------------- 1 | // 请求发送者 2 | var button1 = document.getElementById( 'button1' ), 3 | button2 = document.getElementById( 'button2' ), 4 | button3 = document.getElementById( 'button3' ); 5 | 6 | // 命令的接收者 7 | var MenuBar = { 8 | refresh: function(){ 9 | console.log( '刷新菜单目录' ); 10 | } 11 | }; 12 | var SubMenu = { 13 | add: function(){ 14 | console.log( '增加子菜单' ); 15 | }, 16 | del: function(){ 17 | console.log( '删除子菜单' ); 18 | } 19 | }; 20 | 21 | // 在让button 变得有用起来之前,我们要先把这些行为都封装在命令类中: 22 | var RefreshMenuBarCommand = function( receiver ){ 23 | this.receiver = receiver; 24 | }; 25 | RefreshMenuBarCommand.prototype.execute = function(){ 26 | this.receiver.refresh(); 27 | }; 28 | 29 | var AddSubMenuCommand = function( receiver ){ 30 | this.receiver = receiver; 31 | }; 32 | AddSubMenuCommand.prototype.execute = function(){ 33 | this.receiver.add(); 34 | }; 35 | 36 | var DelSubMenuCommand = function( receiver ){ 37 | this.receiver = receiver; 38 | }; 39 | DelSubMenuCommand.prototype.execute = function(){ 40 | console.log( '删除子菜单' ); 41 | }; 42 | 43 | // 把命令接收者传入到 command 对象中 44 | var refreshMenuBarCommand = new RefreshMenuBarCommand( MenuBar ); 45 | var addSubMenuCommand = new AddSubMenuCommand( SubMenu ); 46 | var delSubMenuCommand = new DelSubMenuCommand( SubMenu ); 47 | 48 | // 负责往按钮上面安装命令 49 | var setCommand = function( button, command ){ 50 | button.onclick = function(){ 51 | command.execute(); 52 | } 53 | }; 54 | 55 | // 把command对象安装到button上面 56 | setCommand( button1, refreshMenuBarCommand ); 57 | setCommand( button2, addSubMenuCommand ); 58 | setCommand( button3, delSubMenuCommand ); 59 | 60 | -------------------------------------------------------------------------------- /composite-pattern/10.html: -------------------------------------------------------------------------------- 1 |  38 | 39 | 40 | 41 | 42 | 43 | 109 | 110 | 111 | 139 | 140 | 141 | 190 | 191 | -------------------------------------------------------------------------------- /composite-pattern/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzlu/JS-design-pattern/d409eed0b63f51bb8218438356245da7a7da71de/composite-pattern/README.md -------------------------------------------------------------------------------- /decorator-pattern/15.html: -------------------------------------------------------------------------------- 1 |  73 | 74 | 75 | 76 | 77 | 117 | 118 | 119 | 120 | 121 | 122 | 133 | 134 | 135 | 136 | 137 | 138 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 用户名: 164 | 165 | 密码: 166 | 167 | 168 | 255 | -------------------------------------------------------------------------------- /decorator-pattern/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzlu/JS-design-pattern/d409eed0b63f51bb8218438356245da7a7da71de/decorator-pattern/README.md -------------------------------------------------------------------------------- /factory-pattern/README.md: -------------------------------------------------------------------------------- 1 | # 工厂模式 2 | 3 | 定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类,该模式使实例化延迟到子类中发生。 4 | 5 | 不暴露创建对象的具体逻辑,而是将逻辑封装在一个函数中,那么这个函数就可以被视为一个工厂。工厂模式根据抽象程度的不同可以分为: 6 | 7 | * 简单工厂模式 8 | * 工厂方法模式 9 | * 抽象工厂模式 10 | 11 | 例子 1 12 | 13 | ```javascript 14 | const CarFactory = (function () { 15 | const Car = function (model, year, miles) { 16 | this.model = model; 17 | this.year = year; 18 | this.miles = miles; 19 | }; 20 | return function (model, year, miles) { 21 | return new Car(model, year, miles); 22 | }; 23 | })(); 24 | const bmw = new CarFactory('BMW', 2009, 20000); 25 | const benz = new CarFactory('Benz', 2010, 5000); 26 | ``` 27 | 28 | 例子 2 29 | 30 | ```javascript 31 | const productManager = {}; 32 | productManager.createProductA = function () { 33 | console.log('ProductA'); 34 | } 35 | productManager.createProductB = function () { 36 | console.log('ProductB'); 37 | } 38 | productManager.factory = function (typeType) { 39 | return new productManager[typeType]; 40 | } 41 | productManager.factory("createProductA"); 42 | productManager.factory("createProductB"); 43 | ``` 44 | 45 | ## 简单工厂模式 46 | 47 | 使用 ES6 的 `static` 关键字将简单工厂封装到类的静态方法中。 48 | 49 | ```javascript 50 | //User类 51 | class User { 52 | //构造器 53 | constructor(opt) { 54 | this.name = opt.name; 55 | this.viewPage = opt.viewPage; 56 | } 57 | 58 | //静态方法 59 | static getInstance(role) { 60 | switch (role) { 61 | case 'superAdmin': 62 | return new User({ name: '超级管理员', viewPage: ['首页', '通讯录', '发现页', '应用数据', '权限管理'] }); 63 | break; 64 | case 'admin': 65 | return new User({ name: '管理员', viewPage: ['首页', '通讯录', '发现页', '应用数据'] }); 66 | break; 67 | case 'user': 68 | return new User({ name: '普通用户', viewPage: ['首页', '通讯录', '发现页'] }); 69 | break; 70 | default: 71 | throw new Error('参数错误, 可选参数:superAdmin、admin、user') 72 | } 73 | } 74 | } 75 | 76 | //调用 77 | let superAdmin = User.getInstance('superAdmin'); 78 | let admin = User.getInstance('admin'); 79 | let normalUser = User.getInstance('user'); 80 | ``` 81 | 82 | ## 工厂方法模式 83 | 84 | ```javascript 85 | // 安全模式创建的工厂方法函数 86 | let UserFactory = function(role) { 87 | if(this instanceof UserFactory) { 88 | var s = new this[role](); 89 | return s; 90 | } else { 91 | return new UserFactory(role); 92 | } 93 | } 94 | 95 | // 工厂方法函数的原型中设置所有对象的构造函数 96 | UserFactory.prototype = { 97 | SuperAdmin: function() { 98 | this.name = "超级管理员", 99 | this.viewPage = ['首页', '通讯录', '发现页', '应用数据', '权限管理'] 100 | }, 101 | Admin: function() { 102 | this.name = "管理员", 103 | this.viewPage = ['首页', '通讯录', '发现页', '应用数据'] 104 | }, 105 | NormalUser: function() { 106 | this.name = '普通用户', 107 | this.viewPage = ['首页', '通讯录', '发现页'] 108 | } 109 | } 110 | 111 | // 调用 112 | let superAdmin = UserFactory('SuperAdmin'); 113 | let admin = UserFactory('Admin'); 114 | let normalUser = UserFactory('NormalUser'); 115 | ``` 116 | 117 | ### ES6 重写工厂方法模式 118 | 119 | > `new.target` 指向直接被 `new` 执行的构造函数 120 | 121 | ```javascript 122 | class User { 123 | constructor(name = '', viewPage = []) { 124 | if(new.target === User) { 125 | throw new Error('抽象类不能实例化!'); 126 | } 127 | this.name = name; 128 | this.viewPage = viewPage; 129 | } 130 | } 131 | 132 | class UserFactory extends User { 133 | constructor(name, viewPage) { 134 | super(name, viewPage) 135 | } 136 | create(role) { 137 | switch (role) { 138 | case 'superAdmin': 139 | return new UserFactory('超级管理员', ['首页', '通讯录', '发现页', '应用数据', '权限管理']); 140 | break; 141 | case 'admin': 142 | return new UserFactory('普通用户', ['首页', '通讯录', '发现页']); 143 | break; 144 | case 'user': 145 | return new UserFactory('普通用户', ['首页', '通讯录', '发现页']); 146 | break; 147 | default: 148 | throw new Error('参数错误, 可选参数:superAdmin、admin、user') 149 | } 150 | } 151 | } 152 | 153 | let userFactory = new UserFactory(); 154 | let superAdmin = userFactory.create('superAdmin'); 155 | let admin = userFactory.create('admin'); 156 | let user = userFactory.create('user'); 157 | ``` 158 | 159 | ## 抽象工厂模式 160 | 161 | 于简单工厂、工厂方法模式不同,抽象工厂模式不直接生成实例,而是类的继承进行类的管理。 162 | 163 | ```javascript 164 | let AccountAbstractFactory = function (subType, superType) { 165 | //判断抽象工厂中是否有该抽象类 166 | if(typeof AccountAbstractFactory[superType] === 'function') { 167 | //缓存类 168 | function F() {}; 169 | //继承父类属性和方法 170 | F.prototype = new AccountAbstractFactory[superType] (); 171 | //子类原型继承父类 172 | subType.prototype = new F(); 173 | //将子类的constructor指向子类 174 | subType.prototype.constructor = subType; 175 | } else { 176 | throw new Error('抽象类不存在!') 177 | } 178 | } 179 | 180 | //微信用户抽象类 181 | AccountAbstractFactory.WechatUser = function() { 182 | this.type = 'wechat'; 183 | } 184 | AccountAbstractFactory.WechatUser.prototype = { 185 | getName: function() { 186 | return new Error('抽象方法不能调用'); 187 | } 188 | } 189 | 190 | //普通微信用户子类 191 | function UserOfWechat(name) { 192 | this.name = name; 193 | this.viewPage = ['首页', '通讯录', '发现页'] 194 | } 195 | //抽象工厂实现WechatUser类的继承 196 | AccountAbstractFactory(UserOfWechat, 'WechatUser'); 197 | //子类中重写抽象方法 198 | UserOfWechat.prototype.getName = function() { 199 | return this.name; 200 | } 201 | 202 | //实例化微信用户 203 | let wechatUserA = new UserOfWechat('微信小李'); 204 | console.log(wechatUserA.getName(), wechatUserA.type); //微信小李 wechat 205 | let wechatUserB = new UserOfWechat('微信小王'); 206 | console.log(wechatUserB.getName(), wechatUserB.type); //微信小王 wechat 207 | ``` 208 | -------------------------------------------------------------------------------- /iterator-pattern/README.md: -------------------------------------------------------------------------------- 1 | 迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。 2 | 3 | ### 内部迭代器 4 | 5 | 内部迭代器在调用的时候非常方便,外界不用关心迭代器的内部实现,跟迭代器的交互也仅仅是一次初始调用。 6 | 缺点是迭代规则已经被提前规定,不灵活。 7 | 8 | ### 外部迭代器 9 | 10 | 外部迭代器增加了一些调用的复杂度,但相对也增强了迭代器的灵活性,我们可以手工控制迭代的过程或者顺序。 11 | 12 | ### 可迭代对象 13 | 14 | 只要被迭代的聚合对象拥有length属性而且可以用下标访问,那它就可以被迭代。 15 | -------------------------------------------------------------------------------- /iterator-pattern/jquery的迭代器封装各种行为.js: -------------------------------------------------------------------------------- 1 | $.each = function( obj, callback ) { 2 | var value, 3 | i = 0, 4 | length = obj.length, 5 | isArray = isArraylike( obj ); 6 | if ( isArray ) { // 迭代类数组 7 | for ( ; i < length; i++ ) { 8 | value = callback.call( obj[ i ], i, obj[ i ] ); 9 | if ( value === false ) { 10 | break; 11 | } 12 | } 13 | } else { 14 | for ( i in obj ) { // 迭代object 对象 15 | value = callback.call( obj[ i ], i, obj[ i ] ); 16 | if ( value === false ) { 17 | break; 18 | } 19 | } 20 | } 21 | return obj; 22 | }; 23 | 24 | -------------------------------------------------------------------------------- /iterator-pattern/倒序迭代和中止迭代.js: -------------------------------------------------------------------------------- 1 | // 倒序迭代 2 | var reverseEach = function( ary, callback ){ 3 | for ( var l = ary.length - 1; l >= 0; l-- ){ 4 | callback( l, ary[ l ] ); 5 | } 6 | }; 7 | 8 | reverseEach( [ 0, 1, 2 ], function( i, n ){ 9 | console.log( n ); // 分别输出:2, 1 ,0 10 | }); 11 | 12 | // 中止迭代器 13 | // 如果回调函数的执行结果返回false则提前终止循环 14 | var each = function( ary, callback ){ 15 | for ( var i = 0, l = ary.length; i < l; i++ ){ 16 | if ( callback( i, ary[ i ] ) === false ){ // callback 的执行结果返回false,提前终止迭代 17 | break; 18 | } 19 | } 20 | }; 21 | 22 | each( [ 1, 2, 3, 4, 5 ], function( i, n ){ 23 | if ( n > 3 ){ // n 大于3 的时候终止循环 24 | return false; 25 | } 26 | console.log( n ); // 分别输出:1, 2, 3 27 | }); 28 | 29 | -------------------------------------------------------------------------------- /iterator-pattern/内部迭代器和外部迭代器.js: -------------------------------------------------------------------------------- 1 | // 内部迭代器,不能自定义迭代规则 2 | var compare = function( ary1, ary2 ){ 3 | if ( ary1.length !== ary2.length ){ 4 | throw new Error ( 'ary1 和ary2 不相等' ); 5 | } 6 | each( ary1, function( i, n ){ 7 | if ( n !== ary2[ i ] ){ 8 | throw new Error ( 'ary1 和ary2 不相等' ); 9 | } 10 | }); 11 | alert ( 'ary1 和ary2 相等' ); 12 | }; 13 | compare( [ 1, 2, 3 ], [ 1, 2, 4 ] ); // throw new Error ( 'ary1 和ary2 不相等' ); 14 | 15 | // 外部迭代器 16 | var Iterator = function( obj ){ 17 | var current = 0; 18 | var next = function(){ 19 | current += 1; 20 | }; 21 | var isDone = function(){ 22 | return current >= obj.length; 23 | }; 24 | var getCurrItem = function(){ 25 | return obj[ current ]; 26 | }; 27 | return { 28 | next: next, 29 | isDone: isDone, 30 | getCurrItem: getCurrItem 31 | } 32 | }; 33 | 34 | //再看看如何改写compare 函数: 35 | var compare = function( iterator1, iterator2 ){ 36 | while( !iterator1.isDone() && !iterator2.isDone() ){ 37 | if ( iterator1.getCurrItem() !== iterator2.getCurrItem() ){ 38 | throw new Error ( 'iterator1 和iterator2 不相等' ); 39 | } 40 | iterator1.next(); 41 | iterator2.next(); 42 | } 43 | alert ( 'iterator1 和iterator2 相等' ); 44 | } 45 | var iterator1 = Iterator( [ 1, 2, 3 ] ); 46 | var iterator2 = Iterator( [ 1, 2, 3 ] ); 47 | compare( iterator1, iterator2 ); // 输出:iterator1 和iterator2 相等 48 | 49 | -------------------------------------------------------------------------------- /iterator-pattern/实现自己的迭代器.js: -------------------------------------------------------------------------------- 1 | // jQuery的迭代器 2 | $.each( [1, 2, 3], function( i, n ){ 3 | console.log( '当前下标为: '+ i ); 4 | console.log( '当前值为:' + n ); 5 | }); 6 | 7 | // 自己实现的迭代器 8 | var each = function( ary, callback ){ 9 | for ( var i = 0, l = ary.length; i < l; i++ ){ 10 | callback.call( ary[i], i, ary[ i ] ); // 把下标和元素当作参数传给callback 函数 11 | } 12 | }; 13 | 14 | each( [ 1, 2, 3 ], function( i, n ){ 15 | alert ( [ i, n ] ); 16 | }); 17 | 18 | -------------------------------------------------------------------------------- /object-oriented/README.md: -------------------------------------------------------------------------------- 1 | # 面向对象的 JavaScript 2 | 3 | ## 鸭子类型 4 | 5 | > 鸭子类型(duck typing) 6 | > 7 | > 如果它走起路来像鸭子,叫起来也是鸭子,那么它就是鸭子。 8 | > 9 | > 鸭子类型指导我们只关注*对象的行为*,而不关注对象本身。 10 | 11 | |分类|静态类型语言|动态类型语言| 12 | |---|---|---| 13 | |优点|编译时就能发现类型不匹配错误,编译器针对程序进行优化|编写代码量更少,更专注于业务逻辑| 14 | |缺点|类型声明增加代码,分散程序员专注业务思考的精力|无法保证变量类型| 15 | 16 | 示例代码 17 | 18 | ```javascript 19 | var duck = { 20 | duckSinging: function () { 21 | console.log('嘎嘎嘎'); 22 | } 23 | }; 24 | var chicken = { 25 | duckSinging: function () { 26 | console.log('嘎嘎嘎'); 27 | } 28 | }; 29 | var choir = []; // 合唱团 30 | var joinChoir = function (animal) { 31 | if (animal && typeof animal.duckSinging === 'function') { 32 | choir.push(animal); 33 | console.log('恭喜加入合唱团'); 34 | console.log('合唱团已有成员数量:' + choir.length); 35 | } 36 | }; 37 | 38 | joinChoir( duck ); // 恭喜加入合唱团 39 | joinChoir( chicken ); // 恭喜加入合唱团 40 | ``` 41 | 42 | ### 面向接口编程 43 | 44 | > 面向实现编程,变量指向特定类的实例,有一种强烈的依赖关系,会大大抑制编程的灵活性和可复用性。 45 | > 46 | > 面向接口编程,客户无需知道他们使用对象的特定类型,只需对象有客户所期望的接口(方法)。 47 | > 48 | > 面向接口编程,而不是面向实现编程。 49 | 50 | ## 多态 51 | 52 | > polymorphism 53 | > 54 | > 同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果。 55 | > 56 | > 多态思想实际上是把『做什么』和『谁去做』分离开来。 57 | > 58 | > 多态思想是把不变的部分(动物都会叫)隔离出来,把可变的部分(具体怎么叫)封装起来。 59 | 60 | ```javascript 61 | // 随着动物种类的增加,makeSound 函数就会变得越来越大。 62 | var makeSound = function (animal) { 63 | if (animal instanceof Duck) { 64 | console.log('嘎嘎嘎'); 65 | } else if (animal instanceof Chicken) { 66 | console.log('咯咯咯'); 67 | } 68 | }; 69 | var Duck = function () {}; 70 | var Chicken = function () {}; 71 | 72 | makeSound(new Duck()); 73 | makeSound(new Chicken()); 74 | ``` 75 | 76 | 77 | ```javascript 78 | // 『做什么』与『怎么做』分离 79 | var makeSound = function (animal) { 80 | animal.sound(); 81 | }; 82 | 83 | var Duck = function () {}; 84 | Duck.prototype.sound = function () { 85 | console.log('嘎嘎嘎'); 86 | }; 87 | 88 | var Chicken = function () {}; 89 | Chicken.prototype.sound = function () { 90 | console.log('咯咯咯'); 91 | }; 92 | ``` 93 | 94 | ### 使用继承获得多态效果 95 | 96 | 使用继承(*实现继承*和*接口继承*)来得到多态效果,是让对象表现出多态性的最常用手段。 97 | 98 | ### JavaScript的多态 99 | 100 | 由于JavaScript的变量类型在运行期间可变,没有类型检查的过程,不存在类型耦合,这意味着JavaScript对象的多态性是与生俱来的,不需要像JAVA通过向上转型(转型到超类)来实现多态。 101 | 102 | ### 多态在面向对象程序设计中的作用 103 | 104 | 利用对象的多态性,发布消息后,不必考虑各个对象接到消息后应该做什么,将行为分布在各个对象中,并让这些对象各自负责自己的行为,正是面向对象设计的优点。 105 | 106 | ## 封装 107 | 108 | > 封装的目的是将信息隐藏,封装数据、封装实现、封装类型、封装变化。 109 | 110 | ### 封装数据 111 | 112 | JavaScript由于不支持 `private`、`public`、`protected` 等关键字,只能通过变量作用域来实现封装特性。 113 | 114 | ```javascript 115 | var myObject = (function () { 116 | var _name = 'foo'; // 私有变量 117 | return { 118 | getName: function () { // 公开方法 119 | return _name; 120 | } 121 | } 122 | })(); 123 | 124 | console.log(myObject._name) // 输出:undefined 125 | console.log(myObject.getName()); // 输出:foo 126 | ``` 127 | 128 | > 在 ES6 中可以通过 symbol 创建私有属性 129 | 130 | ### 封装实现 131 | 132 | 封装使得对象内部的变化对其他对象而言是不可见的,对象对自己行为负责,其他对象或用户不关心它的内部实现。 133 | 134 | 封装使得对象之间*松耦合*,对象之间只通过暴露的API来通信。 135 | 136 | ### 封装类型 137 | 138 | JavaScript 并没有对抽象类和接口的支持 139 | 140 | ### 封装变化 141 | 142 | 把系统中稳定不变的部分和容易变化的部分隔离开来,在系统演变过程中,只需要替换那些容易变化的部分,如果这些部分是已经封装好的,替换起来也相对容易。 143 | 144 | ## 原型模式 145 | 146 | 原型模式不单是一种设计模式,也被称为一种编程泛型。我们不再关心对象的具体类型,而是找到一个对象,通过克隆创建一个一模一样的对象。 147 | 148 | ```javascript 149 | Object.create = Object.create || function (obj) { 150 | var F = function () {}; 151 | F.prototype = obj; 152 | return new F(); 153 | } 154 | ``` 155 | 156 | > 基于原型链的委托机制就是原型继承的本质。 157 | 158 | ### JavaScript中的原型继承 159 | 160 | * 所有的数据都是对象(基本数据类型可以通过包装类的方式变成对象数据类型) 161 | * 要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它 162 | * 对象的构造函数有原型 163 | * 根对象 `Object.prototype` 164 | * 对象把请求委托给它的构造器的原型 165 | * JavaScript的函数既可以作为普通函数被调用,也可以作为**构造器**被调用。 166 | * 用 `new` 运算符创建对象的过程,实际上是先克隆原型,再进行一些其他额外的操作。 167 | * 原型链并不是无限长的,最后 `Object.prototype.__proto__` 是 `null`,说明原型链后面已经没有了别的节点。 168 | * 对象的构造器有原型 `obj.__proto__ === Constructor.prototype` 169 | * `Object.create` 创建对象的效率比构造函数创建对象要慢 170 | * 通过 `Object.create(null)` 可以创建出没有原型的对象 171 | 172 | ```javascript 173 | // 理解 new 运算的过程 174 | function Person (name) { 175 | this.name = name; 176 | }; 177 | 178 | Person.prototype.getName = function () { 179 | return this.name; 180 | }; 181 | 182 | var objectFactory = function(){ 183 | // 从 Object.prototype 上克隆一个空的对象 184 | var obj = new Object(); 185 | // 取得外部传入的构造器,此例是 Person 186 | var Constructor = [].shift.call(arguments); 187 | // 指向正确的原型 188 | obj.__proto__ = Constructor.prototype; 189 | // 借用外部传入的构造器给 obj 设置属性 190 | // 构造器 this 指向 obj 191 | var ret = Constructor.apply(obj, arguments); 192 | // 确保构造器总是会返回一个对象 193 | return typeof ret === 'object' ? ret : obj; 194 | }; 195 | 196 | var a = objectFactory(Person, 'foo'); 197 | 198 | console.log(a.name); // 输出:foo 199 | console.log(a.getName()); // 输出:foo 200 | console.log(Object.getPrototypeOf(a) === Person.prototype); // 输出:true 201 | ``` 202 | 203 | ```javascript 204 | // 下面的代码是我们最常用的原型继承方式: 205 | var obj = { name: 'foo' }; 206 | var A = function () {}; 207 | A.prototype = obj; 208 | var a = new A(); 209 | console.log(a.name); // 输出:foo 210 | 211 | // 当我们期望得到一个“类”继承自另外一个“类”的效果时,往往会用下面的代码来模拟实现: 212 | var A = function () {}; 213 | A.prototype = { name: 'foo' }; 214 | var B = function () {}; 215 | B.prototype = new A(); 216 | var b = new B(); 217 | console.log(b.name); // 输出:foo 218 | ``` 219 | 220 | ## 结语 221 | 222 | 设计模式在很多时候其实都体现了语言的不足之处。 223 | 224 | > 设计模式是对语言不足的补充,如果要使用设计模式,不如去找一门更好的语言。 225 | > 226 | > Peter Norving 227 | 228 | -------------------------------------------------------------------------------- /proxy-pattern/README.md: -------------------------------------------------------------------------------- 1 | ## 代理模式 2 | 3 | 代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对本体对象的访问,客户实际上访问的是替身对象。 4 | 替身对象对请求做出一些处理之后,再把请求转交给本体对象。 5 | 6 | ### 保护代理和虚拟代理 7 | 8 | - 保护代理,用于控制不同权限的客户对目标对象的访问。 9 | - 虚拟代理,把一些开销很大的对象,延迟到真正需要它的时候才去创建。 10 | - 缓存代理,为一些开销很大的运算结果提供暂时的存储,在下次运算时,如果参数跟之前的一致,则可以直接返回前面存储的结果。 11 | 12 | ### 代理和本体接口的一致性 13 | 14 | 关键原则,代理对象和本体对象都对外提供一致的接口,用户并不请求代理和本体的区别,代理接手请求的过程对于用户来说是透明的。 15 | 在`Java`等语言中,代理和本体都需要显式地实线同一个接口,一方面接口保证了它们会有同样的方法,另一方面,面向接口编程迎合依赖倒置原则,通过接口进行向上转型,从而代理和本体将来可以被替换使用。 16 | -------------------------------------------------------------------------------- /proxy-pattern/最简单的代理模式.js: -------------------------------------------------------------------------------- 1 | // 不使用代理 2 | var Flower = function(){}; 3 | var xiaoming = { 4 | sendFlower: function( target ){ 5 | var flower = new Flower(); 6 | target.receiveFlower( flower ); 7 | } 8 | }; 9 | var A = { 10 | receiveFlower: function( flower ){ 11 | console.log( '收到花 ' + flower ); 12 | } 13 | }; 14 | xiaoming.sendFlower( A ); 15 | 16 | // 接下来,我们引入代理B,即小明通过B 来给A 送花: 17 | var Flower = function(){}; 18 | var xiaoming = { 19 | sendFlower: function( target){ 20 | var flower = new Flower(); 21 | target.receiveFlower( flower ); 22 | } 23 | }; 24 | var B = { 25 | receiveFlower: function( flower ){ 26 | A.receiveFlower( flower ); 27 | } 28 | }; 29 | var A = { 30 | receiveFlower: function( flower ){ 31 | console.log( '收到花 ' + flower ); 32 | } 33 | }; 34 | xiaoming.sendFlower( B ); 35 | 36 | 37 | // 选择A 心情好的时候把花转交给A,代码如下: 38 | var Flower = function(){}; 39 | var xiaoming = { 40 | sendFlower: function( target){ 41 | var flower = new Flower(); 42 | target.receiveFlower( flower ); 43 | } 44 | }; 45 | var B = { 46 | receiveFlower: function( flower ){ 47 | A.listenGoodMood(function(){ // 监听A 的好心情 48 | A.receiveFlower( flower ); 49 | }); 50 | } 51 | }; 52 | var A = { 53 | receiveFlower: function( flower ){ 54 | console.log( '收到花 ' + flower ); 55 | }, 56 | listenGoodMood: function( fn ){ 57 | setTimeout(function(){ // 假设10 秒之后A 的心情变好 58 | fn(); 59 | }, 10000 ); 60 | } 61 | }; 62 | xiaoming.sendFlower( B ); 63 | 64 | -------------------------------------------------------------------------------- /proxy-pattern/缓存代理.js: -------------------------------------------------------------------------------- 1 | // 未加入缓存代理函数 2 | var mult = function(){ 3 | console.log( '开始计算乘积' ); 4 | var a = 1; 5 | for ( var i = 0, l = arguments.length; i < l; i++ ){ 6 | a = a * arguments[i]; 7 | } 8 | return a; 9 | }; 10 | 11 | mult( 2, 3 ); // 输出:6 12 | mult( 2, 3, 4 ); // 输出:24 13 | 14 | //现在加入缓存代理函数: 15 | var proxyMult = (function(){ 16 | var cache = {}; 17 | return function(){ 18 | var args = Array.prototype.join.call( arguments, ',' ); 19 | if ( args in cache ){ 20 | return cache[ args ]; 21 | } 22 | return cache[ args ] = mult.apply( this, arguments ); 23 | } 24 | })(); 25 | 26 | proxyMult( 1, 2, 3, 4 ); // 输出:24 27 | proxyMult( 1, 2, 3, 4 ); // 输出:24 28 | 29 | /* 30 | * 使用高阶函数动态创建代理 31 | * 把计算方法当作参数传入一个专门用于创建缓存代理的工厂中 32 | */ 33 | 34 | /**************** 计算乘积 *****************/ 35 | var mult = function(){ 36 | var a = 1; 37 | for ( var i = 0, l = arguments.length; i < l; i++ ){ 38 | a = a * arguments[i]; 39 | } 40 | return a; 41 | }; 42 | 43 | /**************** 计算加和 *****************/ 44 | var plus = function(){ 45 | var a = 0; 46 | for ( var i = 0, l = arguments.length; i < l; i++ ){ 47 | a = a + arguments[i]; 48 | } 49 | return a; 50 | }; 51 | 52 | /**************** 创建缓存代理的工厂 *****************/ 53 | var createProxyFactory = function( fn ){ 54 | var cache = {}; 55 | return function(){ 56 | var args = Array.prototype.join.call( arguments, ',' ); 57 | if ( args in cache ){ 58 | return cache[ args ]; 59 | } 60 | return cache[ args ] = fn.apply( this, arguments ); 61 | } 62 | }; 63 | 64 | var proxyMult = createProxyFactory( mult ), 65 | proxyPlus = createProxyFactory( plus ); 66 | 67 | alert ( proxyMult( 1, 2, 3, 4 ) ); // 输出:24 68 | alert ( proxyMult( 1, 2, 3, 4 ) ); // 输出:24 69 | alert ( proxyPlus( 1, 2, 3, 4 ) ); // 输出:10 70 | alert ( proxyPlus( 1, 2, 3, 4 ) ); // 输出:10 71 | 72 | -------------------------------------------------------------------------------- /proxy-pattern/虚拟代理合并http请求.js: -------------------------------------------------------------------------------- 1 | var synchronousFile = function( id ){ 2 | console.log( '开始同步文件,id 为: ' + id ); 3 | }; 4 | 5 | var proxySynchronousFile = (function(){ 6 | var cache = [], // 保存一段时间内需要同步的ID 7 | timer; // 定时器 8 | return function( id ){ 9 | cache.push( id ); 10 | if ( timer ){ // 保证不会覆盖已经启动的定时器 11 | return; 12 | } 13 | timer = setTimeout(function(){ 14 | synchronousFile( cache.join( ',' ) ); // 2 秒后向本体发送需要同步的ID 集合 15 | clearTimeout( timer ); // 清空定时器 16 | timer = null; 17 | cache.length = 0; // 清空ID 集合 18 | }, 2000 ); 19 | } 20 | })(); 21 | 22 | var checkbox = document.getElementsByTagName( 'input' ); 23 | 24 | for ( var i = 0, c; c = checkbox[ i++ ]; ){ 25 | c.onclick = function(){ 26 | if ( this.checked === true ){ 27 | proxySynchronousFile( this.id ); 28 | } 29 | } 30 | }; 31 | 32 | -------------------------------------------------------------------------------- /proxy-pattern/虚拟代理在惰性加载中的应用.js: -------------------------------------------------------------------------------- 1 | // 未加载真正的miniConsole.js之前的代码 2 | // 把log请求都包裹在一个函数里,这些函数全被放入缓存队列中 3 | var cache = []; 4 | // 占位的miniConsole代理对象 5 | var miniConsole = { 6 | log: function(){ 7 | var args = arguments; 8 | cache.push( function(){ 9 | return miniConsole.log.apply( miniConsole, args ); 10 | }); 11 | } 12 | }; 13 | miniConsole.log(1); 14 | 15 | // 当用户按下F12时才加载真正的miniConsole.js 16 | var handler = function( ev ){ 17 | if ( ev.keyCode === 113 ){ 18 | var script = document.createElement( 'script' ); 19 | script.onload = function(){ 20 | for ( var i = 0, fn; fn = cache[ i++ ]; ){ 21 | fn(); 22 | } 23 | }; 24 | script.src = 'miniConsole.js'; 25 | document.getElementsByTagName( 'head' )[0].appendChild( script ); 26 | } 27 | }; 28 | document.body.addEventListener( 'keydown', handler, false ); 29 | 30 | // miniConsole.js 代码: 31 | miniConsole = { 32 | log: function(){ 33 | // 真正代码略 34 | console.log( Array.prototype.join.call( arguments ) ); 35 | } 36 | }; 37 | 38 | /****************************************************************************************/ 39 | /**************** 保证在F2被重复按下的时候,miniConsole.js只被加载一次 *****************/ 40 | /**************************************************************************************/ 41 | 42 | var miniConsole = (function(){ 43 | var cache = []; 44 | var handler = function( ev ){ 45 | if ( ev.keyCode === 113 ){ 46 | var script = document.createElement( 'script' ); 47 | script.onload = function(){ 48 | for ( var i = 0, fn; fn = cache[ i++ ]; ){ 49 | fn(); 50 | } 51 | }; 52 | script.src = 'miniConsole.js'; 53 | document.getElementsByTagName( 'head' )[0].appendChild( script ); 54 | document.body.removeEventListener( 'keydown', handler ); // 只加载一次miniConsole.js 55 | } 56 | }; 57 | document.body.addEventListener( 'keydown', handler, false ); 58 | return { 59 | log: function(){ 60 | var args = arguments; 61 | cache.push( function(){ 62 | return miniConsole.log.apply( miniConsole, args ); 63 | }); 64 | } 65 | } 66 | })(); 67 | 68 | miniConsole.log( 11 ); // 开始打印log 69 | 70 | // miniConsole.js 代码 71 | miniConsole = { 72 | log: function(){ 73 | // 真正代码略 74 | console.log( Array.prototype.join.call( arguments ) ); 75 | } 76 | } 77 | 78 | -------------------------------------------------------------------------------- /proxy-pattern/虚拟代理实现图片预加载.js: -------------------------------------------------------------------------------- 1 | var myImage = (function(){ 2 | var imgNode = document.createElement( 'img' ); 3 | document.body.appendChild( imgNode ); 4 | return { 5 | setSrc: function( src ){ 6 | imgNode.src = src; 7 | } 8 | } 9 | })(); 10 | myImage.setSrc( 'http:// imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' ); 11 | 12 | // 先用占位图,等图片加载好再填充到img节点里 13 | // 单一职责原则,本体对象负责设置src 14 | var myImage = (function(){ 15 | var imgNode = document.createElement( 'img' ); 16 | document.body.appendChild( imgNode ); 17 | return { 18 | setSrc: function( src ){ 19 | imgNode.src = src; 20 | } 21 | } 22 | })(); 23 | // 单一职责原则,代理对象负责图片预加载 24 | // 即便以后不需要预加载,则只需改成请求本体而不是请求代理对象即可 25 | var proxyImage = (function(){ 26 | var img = new Image; 27 | img.onload = function(){ 28 | myImage.setSrc( this.src ); 29 | } 30 | return { 31 | setSrc: function( src ){ 32 | myImage.setSrc( 'file:// /C:/Users/svenzeng/Desktop/loading.gif' ); 33 | img.src = src; 34 | } 35 | } 36 | })(); 37 | proxyImage.setSrc( 'http:// imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' ); 38 | 39 | // 不用代理的预加载图片函数实现 40 | // 违反了单一职责原则 41 | var MyImage = (function(){ 42 | var imgNode = document.createElement( 'img' ); 43 | document.body.appendChild( imgNode ); 44 | var img = new Image; 45 | img.onload = function(){ 46 | imgNode.src = img.src; 47 | }; 48 | return { 49 | setSrc: function( src ){ 50 | imgNode.src = 'file:// /C:/Users/svenzeng/Desktop/loading.gif'; 51 | img.src = src; 52 | } 53 | } 54 | })(); 55 | MyImage.setSrc( 'http:// imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' ); 56 | 57 | -------------------------------------------------------------------------------- /publish-subscribe-pattern/README.md: -------------------------------------------------------------------------------- 1 | # 发布订阅模式 2 | 3 | 发布订阅模式又称为观察者模式,它定义对象间的一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知。 4 | 5 | ## 实现 6 | 7 | 1. 首先要指定好谁充当发布者(比如售楼处); 8 | 2. 然后给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者(售楼处的花名册); 9 | 3. 最后发布消息的时候,发布者会遍历这个缓存列表,依次触发里面存放的订阅者回调函数(遍历花名册,依次发短信)。 10 | 11 | ## 能力 12 | 13 | 建立一个存放离线事件的堆栈,当事件发布的时候,如果此时还没有订阅者来订阅这个事件,我们暂时把发布事件的动作包裹在一个函数里,这些包装函数将被存入堆栈中,等到终于有对象订阅此事件的时候,我们才遍历堆栈并且依次执行这些包装函数,也就是重新发布里面的事件。 14 | 离线事件的生命周期只有一次。 15 | 16 | ```javascript 17 | // 发布者 18 | function Publisher() { 19 | this.listeners = []; 20 | } 21 | 22 | Publisher.prototype = { 23 | 'addListener': function(listener) { 24 | this.listeners.push(listener); 25 | }, 26 | 'removeListener': function(listener) { 27 | delete this.listeners[listener]; 28 | }, 29 | 'notify': function(obj) { 30 | for(let i = 0; i < this.listeners.length; i += 1) { 31 | let listener = this.listeners[i]; 32 | if (typeof listener !== 'undefined') { 33 | listener.process(obj); 34 | } 35 | } 36 | } 37 | }; 38 | 39 | // 订阅者 40 | function Subscriber() { 41 | } 42 | 43 | Subscriber.prototype.process = function (obj) { 44 | console.log(obj); 45 | }; 46 | 47 | const publisher = new Publisher(); 48 | publisher.addListener(new Subscriber()); 49 | publisher.addListener(new Subscriber()); 50 | publisher.notify({name: 'michaelqin', ageo: 30}); // 发布一个对象到所有订阅者 51 | publisher.notify('2 subscribers will both perform process'); // 发布一个字符串到所有订阅者 52 | ``` 53 | -------------------------------------------------------------------------------- /publish-subscribe-pattern/dom事件.js: -------------------------------------------------------------------------------- 1 | document.body.addEventListener( 'click', function(){ 2 | alert(2); 3 | }, false ); 4 | 5 | document.body.click(); // 模拟用户点击 6 | 7 | document.body.addEventListener( 'click', function(){ 8 | alert(2); 9 | }, false ); 10 | document.body.addEventListener( 'click', function(){ 11 | alert(3); 12 | }, false ); 13 | document.body.addEventListener( 'click', function(){ 14 | alert(4); 15 | }, false ); 16 | 17 | document.body.click(); // 模拟用户点击 18 | 19 | -------------------------------------------------------------------------------- /publish-subscribe-pattern/全局事件的命名冲突.js: -------------------------------------------------------------------------------- 1 | var Event = (function(){ 2 | var global = this, 3 | Event, 4 | _default = 'default'; 5 | Event = function(){ 6 | var _listen, 7 | _trigger, 8 | _remove, 9 | _slice = Array.prototype.slice, 10 | _shift = Array.prototype.shift, 11 | _unshift = Array.prototype.unshift, 12 | namespaceCache = {}, 13 | _create, 14 | find, 15 | each = function( ary, fn ){ 16 | var ret; 17 | for ( var i = 0, l = ary.length; i < l; i++ ){ 18 | var n = ary[i]; 19 | ret = fn.call( n, i, n); 20 | } 21 | return ret; 22 | }; 23 | _listen = function( key, fn, cache ){ 24 | if ( !cache[ key ] ){ 25 | cache[ key ] = []; 26 | } 27 | cache[key].push( fn ); 28 | }; 29 | _remove = function( key, cache ,fn){ 30 | if ( cache[ key ] ){ 31 | if( fn ){ 32 | for( var i = cache[ key ].length; i >= 0; i-- ){ 33 | if( cache[ key ] === fn ){ 34 | cache[ key ].splice( i, 1 ); 35 | } 36 | } 37 | }else{ 38 | cache[ key ] = []; 39 | } 40 | } 41 | }; 42 | _trigger = function(){ 43 | var cache = _shift.call(arguments), 44 | key = _shift.call(arguments), 45 | args = arguments, 46 | _self = this, 47 | ret, 48 | stack = cache[ key ]; 49 | if ( !stack || !stack.length ){ 50 | return; 51 | } 52 | return each( stack, function(){ 53 | return this.apply( _self, args ); 54 | }); 55 | }; 56 | _create = function( namespace ){ 57 | var namespace = namespace || _default; 58 | var cache = {}, 59 | offlineStack = [], // 离线事件 60 | ret = { 61 | listen: function( key, fn, last ){ 62 | _listen( key, fn, cache ); 63 | if ( offlineStack === null ){ 64 | return; 65 | } 66 | if ( last === 'last' ){ 67 | }else{ 68 | each( offlineStack, function(){ 69 | this(); 70 | }); 71 | } 72 | offlineStack = null; 73 | }, 74 | one: function( key, fn, last ){ 75 | _remove( key, cache ); 76 | this.listen( key, fn ,last ); 77 | }, 78 | remove: function( key, fn ){ 79 | _remove( key, cache ,fn); 80 | }, 81 | trigger: function(){ 82 | var fn, 83 | args, 84 | _self = this; 85 | _unshift.call( arguments, cache ); 86 | args = arguments; 87 | fn = function(){ 88 | return _trigger.apply( _self, args ); 89 | }; 90 | if ( offlineStack ){ 91 | return offlineStack.push( fn ); 92 | } 93 | return fn(); 94 | } 95 | }; 96 | return namespace ? 97 | ( namespaceCache[ namespace ] ? namespaceCache[ namespace ] : 98 | namespaceCache[ namespace ] = ret ) 99 | : ret; 100 | }; 101 | return { 102 | create: _create, 103 | one: function( key,fn, last ){ 104 | var event = this.create( ); 105 | event.one( key,fn,last ); 106 | }, 107 | remove: function( key,fn ){ 108 | var event = this.create( ); 109 | event.remove( key,fn ); 110 | }, 111 | listen: function( key, fn, last ){ 112 | var event = this.create( ); 113 | event.listen( key, fn, last ); 114 | }, 115 | trigger: function(){ 116 | var event = this.create( ); 117 | event.trigger.apply( this, arguments ); 118 | } 119 | }; 120 | }(); 121 | return Event; 122 | })(); 123 | 124 | /************************* 先发布后订阅 **************************/ 125 | Event.trigger('click', 1); 126 | 127 | Event.listen('click', function(a) { 128 | console.log(a); 129 | }); 130 | 131 | /************************* 使用命名空间 **************************/ 132 | Event.create('namespace1').listen('click', function(a) { 133 | console.log(a); 134 | }); 135 | Event.create('namespace1').trigger('click', 1); 136 | 137 | Event.create('namespace2').listen('click', function(a) { 138 | console.log(a); 139 | }); 140 | Event.create('namespace2').trigger('click', 2); 141 | 142 | -------------------------------------------------------------------------------- /publish-subscribe-pattern/全局的发布订阅对象.js: -------------------------------------------------------------------------------- 1 | // 全局的Event对象实现,相当于中介,订阅者就不需要知道发布者的名字 2 | // 把订阅者和发布者联系起来 3 | // 4 | var Event = (function(){ 5 | var clientList = {}, 6 | listen, 7 | trigger, 8 | remove; 9 | 10 | listen = function( key, fn ){ 11 | if ( !clientList[ key ] ){ 12 | clientList[ key ] = []; 13 | } 14 | clientList[ key ].push( fn ); 15 | }; 16 | 17 | trigger = function(){ 18 | var key = Array.prototype.shift.call( arguments ), 19 | fns = clientList[ key ]; 20 | if ( !fns || fns.length === 0 ){ 21 | return false; 22 | } 23 | for( var i = 0, fn; fn = fns[ i++ ]; ){ 24 | fn.apply( this, arguments ); 25 | } 26 | }; 27 | 28 | remove = function( key, fn ){ 29 | var fns = clientList[ key ]; 30 | if ( !fns ){ 31 | return false; 32 | } 33 | if ( !fn ){ 34 | fns && ( fns.length = 0 ); 35 | }else{ 36 | for ( var l = fns.length - 1; l >=0; l-- ){ 37 | var _fn = fns[ l ]; 38 | if ( _fn === fn ){ 39 | fns.splice( l, 1 ); 40 | } 41 | } 42 | } 43 | }; 44 | 45 | return { 46 | listen: listen, 47 | trigger: trigger, 48 | remove: remove 49 | } 50 | })(); 51 | 52 | Event.listen( 'squareMeter88', function( price ){ // 小红订阅消息 53 | console.log( '价格= ' + price ); // 输出:'价格=2000000' 54 | }); 55 | 56 | Event.trigger( 'squareMeter88', 2000000 ); // 售楼处发布消息 57 | 58 | -------------------------------------------------------------------------------- /publish-subscribe-pattern/取消订阅.js: -------------------------------------------------------------------------------- 1 | event.remove = function( key, fn ){ 2 | var fns = this.clientList[ key ]; 3 | if ( !fns ){ // 如果key 对应的消息没有被人订阅,则直接返回 4 | return false; 5 | } 6 | if ( !fn ){ // 如果没有传入具体的回调函数,表示需要取消key 对应消息的所有订阅 7 | fns && ( fns.length = 0 ); 8 | }else{ 9 | for ( var l = fns.length - 1; l >=0; l-- ){ // 反向遍历订阅的回调函数列表 10 | var _fn = fns[ l ]; 11 | if ( _fn === fn ){ 12 | fns.splice( l, 1 ); // 删除订阅者的回调函数 13 | } 14 | } 15 | } 16 | }; 17 | 18 | var installEvent = function( obj ){ 19 | for ( var i in event ){ 20 | obj[ i ] = event[ i ]; 21 | } 22 | } 23 | 24 | var salesOffices = {}; 25 | installEvent( salesOffices ); 26 | 27 | salesOffices.listen( 'squareMeter88', fn1 = function( price ){ // 小明订阅消息 28 | console.log( '价格= ' + price ); 29 | }); 30 | 31 | salesOffices.listen( 'squareMeter88', fn2 = function( price ){ // 小红订阅消息 32 | console.log( '价格= ' + price ); 33 | }); 34 | 35 | salesOffices.remove( 'squareMeter88', fn1 ); // 删除小明的订阅 36 | salesOffices.trigger( 'squareMeter88', 2000000 ); // 输出:2000000 37 | 38 | -------------------------------------------------------------------------------- /publish-subscribe-pattern/最简单实现.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 最简单的发布订阅模式 3 | * 但订阅者会收到发布者发布的每个信息 4 | * 后面有改良版 5 | */ 6 | var salesOffices = {}; // 定义售楼处 7 | salesOffices.clientList = []; // 缓存列表,存放订阅者的回调函数 8 | 9 | // 增加订阅者 10 | salesOffices.listen = function( fn ){ 11 | this.clientList.push( fn ); // 订阅的消息添加进缓存列表 12 | }; 13 | // 发布消息 14 | salesOffices.trigger = function(){ 15 | for( var i = 0, fn; fn = this.clientList[ i++ ]; ){ 16 | fn.apply( this, arguments ); // arguments 是发布消息时带上的参数 17 | } 18 | }; 19 | 20 | //下面我们来进行一些简单的测试: 21 | salesOffices.listen( function( price, squareMeter ){ // 小明订阅消息 22 | console.log( '价格= ' + price ); 23 | console.log( 'squareMeter= ' + squareMeter ); 24 | }); 25 | 26 | salesOffices.listen( function( price, squareMeter ){ // 小红订阅消息 27 | console.log( '价格= ' + price ); 28 | console.log( 'squareMeter= ' + squareMeter ); 29 | }); 30 | 31 | salesOffices.trigger( 2000000, 88 ); // 输出:200 万,88 平方米 32 | salesOffices.trigger( 3000000, 110 ); // 输出:300 万,110 平方米 33 | 34 | /***********************************************************************/ 35 | 36 | /* 37 | * 添加标示key,让订阅者只订阅自己感兴趣的消息 38 | */ 39 | var salesOffices = {}; // 定义售楼处 40 | salesOffices.clientList = []; // 缓存列表,存放订阅者的回调函数 41 | salesOffices.listen = function( key, fn ){ 42 | if ( !this.clientList[ key ] ){ // 如果还没有订阅过此类消息,给该类消息创建一个缓存列表 43 | this.clientList[ key ] = []; 44 | } 45 | this.clientList[ key ].push( fn ); // 订阅的消息添加进消息缓存列表 46 | }; 47 | 48 | salesOffices.trigger = function(){ // 发布消息 49 | var key = Array.prototype.shift.call( arguments ), // 取出消息类型 50 | fns = this.clientList[ key ]; // 取出该消息对应的回调函数集合 51 | if ( !fns || fns.length === 0 ){ // 如果没有订阅该消息,则返回 52 | return false; 53 | } 54 | for( var i = 0, fn; fn = fns[ i++ ]; ){ 55 | fn.apply( this, arguments ); // (2) // arguments 是发布消息时附送的参数 56 | } 57 | }; 58 | 59 | salesOffices.listen( 'squareMeter88', function( price ){ // 小明订阅88 平方米房子的消息 60 | console.log( '价格= ' + price ); // 输出: 2000000 61 | }); 62 | salesOffices.listen( 'squareMeter110', function( price ){ // 小红订阅110 平方米房子的消息 63 | console.log( '价格= ' + price ); // 输出: 3000000 64 | }); 65 | 66 | salesOffices.trigger( 'squareMeter88', 2000000 ); // 发布88 平方米房子的价格 67 | salesOffices.trigger( 'squareMeter110', 3000000 ); // 发布110 平方米房子的价格 68 | 69 | -------------------------------------------------------------------------------- /publish-subscribe-pattern/模块间通信.js: -------------------------------------------------------------------------------- 1 | var a = (function(){ 2 | var count = 0; 3 | var button = document.getElementById( 'count' ); 4 | button.onclick = function(){ 5 | Event.trigger( 'add', count++ ); 6 | } 7 | })(); 8 | var b = (function(){ 9 | var div = document.getElementById( 'show' ); 10 | Event.listen( 'add', function( count ){ 11 | div.innerHTML = count; 12 | }); 13 | })(); 14 | 15 | -------------------------------------------------------------------------------- /publish-subscribe-pattern/网站登录的应用.js: -------------------------------------------------------------------------------- 1 | // 与用户信息模块产生了强耦合,不妥! 2 | login.succ(function(data){ 3 | header.setAvatar( data.avatar); // 设置header 模块的头像 4 | nav.setAvatar( data.avatar ); // 设置导航模块的头像 5 | message.refresh(); // 刷新消息列表 6 | cart.refresh(); // 刷新购物车列表 7 | }); 8 | 9 | // 对用户信息感兴趣的业务模块将自行订阅登录成功的消息事件。 10 | // 11 | $.ajax('http://xxx.com?login', function(data) { 12 | login.trigger('loginSucc', data); // 发布登录成功的消息 13 | }); 14 | 15 | var header = (function(){ // header 模块 16 | login.listen( 'loginSucc', function( data){ 17 | header.setAvatar( data.avatar ); 18 | }); 19 | return { 20 | setAvatar: function( data ){ 21 | console.log( '设置header 模块的头像' ); 22 | } 23 | } 24 | })(); 25 | 26 | var nav = (function(){ // nav 模块 27 | login.listen( 'loginSucc', function( data ){ 28 | nav.setAvatar( data.avatar ); 29 | }); 30 | return { 31 | setAvatar: function( avatar ){ 32 | console.log( '设置nav 模块的头像' ); 33 | } 34 | } 35 | })(); 36 | 37 | var address = (function(){ // nav 模块 38 | login.listen( 'loginSucc', function( obj ){ 39 | address.refresh( obj ); 40 | }); 41 | return { 42 | refresh: function( avatar ){ 43 | console.log( '刷新收货地址列表' ); 44 | } 45 | } 46 | })(); 47 | 48 | -------------------------------------------------------------------------------- /publish-subscribe-pattern/通用实现.js: -------------------------------------------------------------------------------- 1 | //所以我们把发布—订阅的功能提取出来,放在一个单独的对象内: 2 | var event = { 3 | clientList: {}, 4 | listen: function( key, fn ){ 5 | if ( !this.clientList[ key ] ){ 6 | this.clientList[ key ] = []; 7 | } 8 | this.clientList[ key ].push( fn ); // 订阅的消息添加进缓存列表 9 | }, 10 | trigger: function(){ 11 | var key = Array.prototype.shift.call( arguments ), 12 | fns = this.clientList[ key ]; 13 | if ( !fns || fns.length === 0 ){ // 如果没有绑定对应的消息 14 | return false; 15 | } 16 | for( var i = 0, fn; fn = fns[ i++ ]; ){ 17 | fn.apply( this, arguments ); // (2) // arguments 是trigger 时带上的参数 18 | } 19 | } 20 | }; 21 | 22 | // installEvent函数可以给所有对象都动态安装发布订阅功能 23 | var installEvent = function( obj ){ 24 | for ( var i in event ){ 25 | obj[ i ] = event[ i ]; 26 | } 27 | }; 28 | 29 | // 我们给售楼处对象salesOffices 动态增加发布—订阅功能: 30 | var salesOffices = {}; 31 | installEvent( salesOffices ); 32 | 33 | // 订阅 34 | salesOffices.listen( 'squareMeter88', function( price ){ // 小明订阅消息 35 | console.log( '价格= ' + price ); 36 | }); 37 | salesOffices.listen( 'squareMeter100', function( price ){ // 小红订阅消息 38 | console.log( '价格= ' + price ); 39 | }); 40 | 41 | // 发布 42 | salesOffices.trigger( 'squareMeter88', 2000000 ); // 输出:2000000 43 | salesOffices.trigger( 'squareMeter100', 3000000 ); // 输出:3000000 44 | 45 | -------------------------------------------------------------------------------- /singleton-pattern/README.md: -------------------------------------------------------------------------------- 1 | # 单例模式 2 | 3 | 保证一个类只有一个实例(单例),通过接口访问该单例。 4 | 5 | 适用场景为:应用当且仅当需要唯一的实例,此外,该实例仅当用到时才去初始化。 6 | 7 | ## 使用闭包封装私有变量 8 | 9 | ```javascript 10 | const user = (function () { 11 | const _name = 'sven'; 12 | const _age = 29; 13 | 14 | return { 15 | getUserInfo: function () { 16 | return `${_name}-${_age}`; 17 | } 18 | } 19 | })(); 20 | 21 | user.getUserInfo(); 22 | ``` 23 | 24 | ## 标准单例 25 | 26 | ```javascript 27 | const Singleton = function (name) { 28 | this.name = name; 29 | this.instance = null; 30 | }; 31 | 32 | Singleton.prototype.getName = function () { 33 | console.log(this.name); 34 | }; 35 | 36 | Singleton.getInstance = function (name) { 37 | if (!this.instance){ 38 | this.instance = new Singleton(name); 39 | } 40 | return this.instance; 41 | }; 42 | 43 | const a = Singleton.getInstance('foo'); 44 | const b = Singleton.getInstance('bar'); 45 | a.getName(); 46 | console.log(a === b); // true 47 | ``` 48 | 49 | ## 标准单例 2 50 | 51 | ```javascript 52 | const Singleton = function (name) { 53 | this.name = name; 54 | }; 55 | 56 | Singleton.prototype.getName = function () { 57 | console.log(this.name); 58 | }; 59 | 60 | Singleton.getInstance = (function(){ 61 | var instance = null; 62 | // 闭包 63 | return function(name){ 64 | if (!instance){ 65 | instance = new Singleton(name); 66 | } 67 | return instance; 68 | } 69 | })(); 70 | 71 | Singleton.getInstance('foo').getName(); 72 | Singleton.getInstance('bar').getName(); 73 | ``` 74 | 75 | ## 用代理实现单例 76 | 77 | ```javascript 78 | const CreateDiv = function (html) { 79 | this.html = html; 80 | this.init(); 81 | }; 82 | 83 | CreateDiv.prototype.init = function () { 84 | var div = document.createElement('div'); 85 | div.innerHTML = this.html; 86 | document.body.appendChild(div); 87 | }; 88 | 89 | const ProxySingletonCreateDiv = (function(){ 90 | let instance; 91 | return function (html) { 92 | if (!instance) { 93 | // 代理给 CreateDiv 94 | instance = new CreateDiv(html); 95 | } 96 | return instance; 97 | } 98 | })(); 99 | 100 | const a = new ProxySingletonCreateDiv('foo'); 101 | const b = new ProxySingletonCreateDiv('bar'); 102 | 103 | console.log(a === b); 104 | ``` 105 | 106 | ## 透明单例 107 | 108 | ```javascript 109 | const OnlyDiv = (function () { 110 | let instance; 111 | const CreateDiv = function (html) { 112 | if (instance){ 113 | return instance; 114 | } 115 | this.html = html; 116 | this.init(); 117 | return instance = this; 118 | }; 119 | CreateDiv.prototype.init = function () { 120 | const div = document.createElement('div'); 121 | div.innerHTML = this.html; 122 | document.body.appendChild(div); 123 | }; 124 | return CreateDiv; 125 | })(); 126 | 127 | const a = new OnlyDiv('foo'); 128 | const b = new OnlyDiv('bar'); 129 | console.log(a === b); // true 130 | ``` 131 | 132 | ## 惰性单例 133 | 134 | ```javascript 135 | /* 136 | * 非惰性求值 137 | */ 138 | var loginLayer = (function(){ 139 | var div = document.createElement( 'div' ); 140 | div.innerHTML = '我是登录浮窗'; 141 | div.style.display = 'none'; 142 | document.body.appendChild( div ); 143 | return div; 144 | })(); 145 | 146 | document.getElementById('loginBtn').onclick = function(){ 147 | loginLayer.style.display = 'block'; 148 | }; 149 | 150 | /* 151 | * 达到了惰性的目的,但失去了单例的效果 152 | */ 153 | var createLoginLayer = function(){ 154 | var div = document.createElement('div'); 155 | div.innerHTML = '我是登录浮窗'; 156 | div.style.display = 'none'; 157 | document.body.appendChild( div ); 158 | return div; 159 | }; 160 | document.getElementById('loginBtn').onclick = function(){ 161 | var loginLayer = createLoginLayer(); 162 | loginLayer.style.display = 'block'; 163 | }; 164 | 165 | /* 166 | * 惰性单例 167 | */ 168 | const createLoginLayer = (function () { 169 | let div; 170 | return function () { 171 | if (!div) { 172 | div = document.createElement('div'); 173 | div.innerHTML = '我是登录浮窗'; 174 | div.style.display = 'none'; 175 | document.body.appendChild(div); 176 | } 177 | return div; 178 | } 179 | })(); 180 | 181 | document.getElementById('loginBtn').onclick = function () { 182 | let loginLayer = createLoginLayer(); 183 | loginLayer.style.display = 'block'; 184 | }; 185 | ``` 186 | 187 | -------------------------------------------------------------------------------- /state-pattern/16.html: -------------------------------------------------------------------------------- 1 |  44 | 45 | 46 | 100 | 101 | 102 | 131 | 132 | 133 | 148 | 149 | 150 | 275 | 276 | 277 | 449 | 450 | 451 | 486 | 487 | 532 | 533 | 534 | 556 | 557 | 558 | 584 | -------------------------------------------------------------------------------- /state-pattern/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzlu/JS-design-pattern/d409eed0b63f51bb8218438356245da7a7da71de/state-pattern/README.md -------------------------------------------------------------------------------- /strategy-pattern/JS版本的策略模式.js: -------------------------------------------------------------------------------- 1 | // 函数也是对象,更简单和直接的做法是把strategy直接定义为函数 2 | var strategies = { 3 | "S": function( salary ){ 4 | return salary * 4; 5 | }, 6 | "A": function( salary ){ 7 | return salary * 3; 8 | }, 9 | "B": function( salary ){ 10 | return salary * 2; 11 | 12 | } 13 | }; 14 | 15 | // 用函数充当Context来接受用户请求 16 | var calculateBonus = function( level, salary ){ 17 | return strategies[ level ]( salary ); 18 | }; 19 | 20 | console.log( calculateBonus( 'S', 20000 ) ); // 输出:80000 21 | console.log( calculateBonus( 'A', 10000 ) ); // 输出:30000 22 | 23 | -------------------------------------------------------------------------------- /strategy-pattern/README.md: -------------------------------------------------------------------------------- 1 | # 策略模式 2 | 3 | ## 定义 4 | 5 | * 定义一系列的算法,把它们一个个封装起来,并且使它们可以互相替换。 6 | * 将不变的部分和变化的部分隔开是每个设计模式的主题,策略模式的目的就是将算法的使用和算法的实现分离开来。 7 | 8 | ## 实现 9 | 10 | 一个基于策略模式的程序至少由两部分组成: 11 | 12 | * 第一部分是一组策略类 13 | * 策略类封装了具体的算法 14 | * 负责具体的计算过程 15 | 16 | * 第二部分是环境类 context 17 | * context 接受客户的请求 18 | * 把请求委托给某一个策略类 19 | * context 要维持对某个策略对象的引用 20 | 21 | ## 多态在策略模式中的体现 22 | 23 | 每个策略对象负责的算法已被各自封装在对象内部。当我们对这些策略对象发出请求时,它们会返回不同的计算结果,这正是对象多态性的体现。 24 | 25 | 『它们可以互相替换』替换 context 中当前保存的策略对象,便能执行不同的算法来得到我们想要的结果。 26 | 27 | ## 封装更广义的『算法』 28 | 29 | 从定义上看,策略模式就是用来封装算法的。通常会把算法的含义扩散开来,使策略模式也可以用来封装一系列的『业务规则』。只要这些业务规则指向的目标一致,并且可以被替换使用,我们就可以用策略模式来封装它们。 30 | 31 | ## 示例 32 | 33 | ```javascript 34 | const strategies = { 35 | 'S': function (salary) { 36 | return salary * 4; 37 | }, 38 | 'A': function (salary) { 39 | return salary * 3; 40 | }, 41 | 'B': function (salary) { 42 | return salary * 2; 43 | }, 44 | }; 45 | 46 | const calculateBonus = function (level, salary) { 47 | return strategies[level](salary); 48 | } 49 | 50 | calculateBonus('A', 10000); 51 | ``` 52 | -------------------------------------------------------------------------------- /strategy-pattern/不使用策略模式.js: -------------------------------------------------------------------------------- 1 | var calculateBonus = function( performanceLevel, salary ){ 2 | if ( performanceLevel === 'S' ){ 3 | return salary * 4; 4 | } 5 | if ( performanceLevel === 'A' ){ 6 | return salary * 3; 7 | } 8 | if ( performanceLevel === 'B' ){ 9 | return salary * 2; 10 | } 11 | }; 12 | 13 | calculateBonus( 'B', 20000 ); // 输出:40000 14 | calculateBonus( 'S', 6000 ); // 输出:24000 15 | 16 | // 使用组合函数 17 | var performanceS = function( salary ){ 18 | return salary * 4; 19 | }; 20 | var performanceA = function( salary ){ 21 | return salary * 3; 22 | }; 23 | var performanceB = function( salary ){ 24 | return salary * 2; 25 | }; 26 | 27 | var calculateBonus = function( performanceLevel, salary ){ 28 | if ( performanceLevel === 'S' ){ 29 | return performanceS( salary ); 30 | } 31 | if ( performanceLevel === 'A' ){ 32 | return performanceA( salary ); 33 | } 34 | if ( performanceLevel === 'B' ){ 35 | return performanceB( salary ); 36 | } 37 | }; 38 | 39 | calculateBonus( 'A' , 10000 ); // 输出:30000 40 | 41 | -------------------------------------------------------------------------------- /strategy-pattern/基于面向对象语言的策略模式.js: -------------------------------------------------------------------------------- 1 | // 把每种绩效的计算规则封装在对应的策略类里面 2 | var performanceS = function(){}; 3 | performanceS.prototype.calculate = function( salary ){ 4 | return salary * 4; 5 | }; 6 | 7 | var performanceA = function(){}; 8 | performanceA.prototype.calculate = function( salary ){ 9 | return salary * 3; 10 | }; 11 | 12 | var performanceB = function(){}; 13 | performanceB.prototype.calculate = function( salary ){ 14 | return salary * 2; 15 | }; 16 | 17 | //接下来定义奖金类Bonus(环境类Context): 18 | var Bonus = function(){ 19 | this.salary = null; // 原始工资 20 | this.strategy = null; // 绩效等级对应的策略对象 21 | }; 22 | Bonus.prototype.setSalary = function( salary ){ 23 | this.salary = salary; // 设置员工的原始工资 24 | }; 25 | Bonus.prototype.setStrategy = function( strategy ){ 26 | this.strategy = strategy; // 设置员工绩效等级对应的策略对象 27 | }; 28 | Bonus.prototype.getBonus = function(){ // 取得奖金数额 29 | // 把计算奖金的操作委托给对应的策略对象 30 | return this.strategy.calculate( this.salary ); 31 | }; 32 | 33 | var bonus = new Bonus(); 34 | bonus.setSalary( 10000 ); 35 | 36 | // 设置策略对象 37 | bonus.setStrategy( new performanceS() ); 38 | console.log( bonus.getBonus() ); // 输出:40000 39 | 40 | // 设置策略对象 41 | bonus.setStrategy( new performanceA() ); 42 | console.log( bonus.getBonus() ); // 输出:30000 43 | 44 | -------------------------------------------------------------------------------- /strategy-pattern/策略模式验证表单.js: -------------------------------------------------------------------------------- 1 | // 把校验逻辑封装成策略对象 2 | var strategies = { 3 | isNonEmpty: function( value, errorMsg ){ // 不为空 4 | if ( value === '' ){ 5 | return errorMsg ; 6 | } 7 | }, 8 | minLength: function( value, length, errorMsg ){ // 限制最小长度 9 | if ( value.length < length ){ 10 | return errorMsg; 11 | } 12 | }, 13 | isMobile: function( value, errorMsg ){ // 手机号码格式 14 | if ( !/(^1[3|5|8][0-9]{9}$)/.test( value ) ){ 15 | return errorMsg; 16 | } 17 | } 18 | }; 19 | 20 | // Validator 类在这里作为context,负责接收用户的请求并委托给strategy对象 21 | var Validator = function(){ 22 | this.cache = []; // 保存校验规则 23 | }; 24 | 25 | // 通过add方法往validator对象中添加一些检验规则 26 | Validator.prototype.add = function( dom, rule, errorMsg ){ 27 | var ary = rule.split( ':' ); // 把strategy 和参数分开 28 | this.cache.push(function(){ // 把校验的步骤用空函数包装起来,并且放入cache 29 | var strategy = ary.shift(); // 用户挑选的strategy 30 | ary.unshift( dom.value ); // 把input 的value 添加进参数列表 31 | ary.push( errorMsg ); // 把errorMsg 添加进参数列表 32 | return strategies[ strategy ].apply( dom, ary ); 33 | }); 34 | }; 35 | 36 | // 启动检验 37 | Validator.prototype.start = function(){ 38 | for ( var i = 0, validatorFunc; validatorFunc = this.cache[ i++ ]; ){ 39 | var msg = validatorFunc(); // 开始校验,并取得校验后的返回信息 40 | if ( msg ){ // 如果有确切的返回值,说明校验没有通过 41 | return msg; 42 | } 43 | } 44 | }; 45 | 46 | /* 47 | * 用户通过validateFunc向validator类发送请求 48 | */ 49 | var validataFunc = function(){ 50 | var validator = new Validator(); // 创建一个validator 对象 51 | /***************添加一些校验规则****************/ 52 | validator.add( registerForm.userName, 'isNonEmpty', '用户名不能为空' ); 53 | validator.add( registerForm.password, 'minLength:6', '密码长度不能少于6 位' ); 54 | validator.add( registerForm.phoneNumber, 'isMobile', '手机号码格式不正确' ); 55 | var errorMsg = validator.start(); // 获得校验结果 56 | return errorMsg; // 返回校验结果 57 | } 58 | 59 | var registerForm = document.getElementById( 'registerForm' ); 60 | 61 | registerForm.onsubmit = function(){ 62 | var errorMsg = validataFunc(); // 如果errorMsg 有确切的返回值,说明未通过校验 63 | if ( errorMsg ){ 64 | alert ( errorMsg ); 65 | return false; // 阻止表单提交 66 | } 67 | }; 68 | 69 | -------------------------------------------------------------------------------- /strategy-pattern/给某个input添加多种检验规则.js: -------------------------------------------------------------------------------- 1 | /***********************策略对象**************************/ 2 | var strategies = { 3 | isNonEmpty: function( value, errorMsg ){ 4 | if ( value === '' ){ 5 | return errorMsg; 6 | } 7 | }, 8 | minLength: function( value, length, errorMsg ){ 9 | if ( value.length < length ){ 10 | return errorMsg; 11 | } 12 | }, 13 | isMobile: function( value, errorMsg ){ 14 | if ( !/(^1[3|5|8][0-9]{9}$)/.test( value ) ){ 15 | return errorMsg; 16 | } 17 | } 18 | }; 19 | 20 | /***********************Validator 类**************************/ 21 | var Validator = function(){ 22 | this.cache = []; 23 | }; 24 | Validator.prototype.add = function( dom, rules ){ 25 | var self = this; 26 | for ( var i = 0, rule; rule = rules[ i++ ]; ){ 27 | (function( rule ){ 28 | var strategyAry = rule.strategy.split( ':' ); 29 | var errorMsg = rule.errorMsg; 30 | self.cache.push(function(){ 31 | var strategy = strategyAry.shift(); 32 | strategyAry.unshift( dom.value ); 33 | strategyAry.push( errorMsg ); 34 | return strategies[ strategy ].apply( dom, strategyAry ); 35 | }); 36 | })( rule ) 37 | } 38 | }; 39 | Validator.prototype.start = function(){ 40 | for ( var i = 0, validatorFunc; validatorFunc = this.cache[ i++ ]; ){ 41 | var errorMsg = validatorFunc(); 42 | if ( errorMsg ){ 43 | return errorMsg; 44 | } 45 | } 46 | }; 47 | 48 | /***********************客户调用代码**************************/ 49 | var registerForm = document.getElementById( 'registerForm' ); 50 | var validataFunc = function(){ 51 | var validator = new Validator(); 52 | validator.add( registerForm.userName, [{ 53 | strategy: 'isNonEmpty', 54 | errorMsg: '用户名不能为空' 55 | }, { 56 | strategy: 'minLength:6', 57 | errorMsg: '用户名长度不能小于10 位' 58 | }]); 59 | validator.add( registerForm.password, [{ 60 | strategy: 'minLength:6', 61 | errorMsg: '密码长度不能小于6 位' 62 | }]); 63 | var errorMsg = validator.start(); 64 | return errorMsg; 65 | } 66 | registerForm.onsubmit = function(){ 67 | var errorMsg = validataFunc(); 68 | if ( errorMsg ){ 69 | alert ( errorMsg ); 70 | return false; 71 | } 72 | }; 73 | 74 | -------------------------------------------------------------------------------- /strategy-pattern/缓动动画.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 缓动算法 3 | * 4个参数的含义: 4 | * 动画已消耗的时间、小球原始位置、小球目标位置、动画持续的总时间 5 | */ 6 | var tween = { 7 | linear: function( t, b, c, d ){ 8 | return c*t/d + b; 9 | }, 10 | easeIn: function( t, b, c, d ){ 11 | return c * ( t /= d ) * t + b; 12 | }, 13 | strongEaseIn: function(t, b, c, d){ 14 | return c * ( t /= d ) * t * t * t * t + b; 15 | }, 16 | strongEaseOut: function(t, b, c, d){ 17 | return c * ( ( t = t / d - 1) * t * t * t * t + 1 ) + b; 18 | }, 19 | sineaseIn: function( t, b, c, d ){ 20 | return c * ( t /= d) * t * t + b; 21 | }, 22 | sineaseOut: function(t,b,c,d){ 23 | return c * ( ( t = t / d - 1) * t * t + 1 ) + b; 24 | } 25 | }; 26 | 27 | // 接收一个参数,即将运动起来的dom节点 28 | var Animate = function( dom ){ 29 | this.dom = dom; // 进行运动的dom 节点 30 | this.startTime = 0; // 动画开始时间 31 | this.startPos = 0; // 动画开始时,dom 节点的位置,即dom 的初始位置 32 | this.endPos = 0; // 动画结束时,dom 节点的位置,即dom 的目标位置 33 | this.propertyName = null; // dom 节点需要被改变的css 属性名 34 | this.easing = null; // 缓动算法 35 | this.duration = null; // 动画持续时间 36 | }; 37 | 38 | /* 39 | * 负责启动这个动画,在动画被启动的瞬间要记录一些信息, 40 | * 供缓动算法在以后计算小球当前位置的时候使用。 41 | * 此外,还要负责启动定时器 42 | * 4个参数:要改变的CSS属性名、小球运动的目标位置、动画持续时间、缓动算法 43 | */ 44 | Animate.prototype.start = function( propertyName, endPos, duration, easing ){ 45 | this.startTime = +new Date; // 动画启动时间 46 | this.startPos = this.dom.getBoundingClientRect()[ propertyName ]; // dom 节点初始位置 47 | this.propertyName = propertyName; // dom 节点需要被改变的CSS 属性名 48 | this.endPos = endPos; // dom 节点目标位置 49 | this.duration = duration; // 动画持续事件 50 | this.easing = tween[ easing ]; // 缓动算法 51 | var self = this; 52 | var timeId = setInterval(function(){ // 启动定时器,开始执行动画 53 | if ( self.step() === false ){ // 如果动画已结束,则清除定时器 54 | clearInterval( timeId ); 55 | } 56 | }, 19 ); 57 | }; 58 | 59 | /* 60 | * 该方法代表小球运动的每一帧要做的事情 61 | * 此外,还要负责计算小球的当前位置 62 | * 调用更新CSS属性值的方法Animate.prototype.update 63 | */ 64 | Animate.prototype.step = function(){ 65 | var t = +new Date; // 取得当前时间 66 | if ( t >= this.startTime + this.duration ){ // (1) 67 | this.update( this.endPos ); // 更新小球的CSS 属性值 68 | return false; 69 | } 70 | var pos = this.easing( t - this.startTime, this.startPos, this.endPos - this.startPos, this.duration ); // pos 为小球当前位置 71 | this.update( pos ); // 更新小球的CSS 属性值 72 | }; 73 | 74 | // 负责更新小球的CSS属性 75 | Animate.prototype.update = function( pos ){ 76 | this.dom.style[ this.propertyName ] = pos + 'px'; 77 | }; 78 | 79 | var div = document.getElementById( 'div' ); 80 | var animate = new Animate( div ); 81 | animate.start( 'left', 500, 1000, 'strongEaseOut' ); 82 | animate.start( 'top', 1500, 500, 'strongEaseIn' ); 83 | 84 | -------------------------------------------------------------------------------- /template-method-pattern/11.html: -------------------------------------------------------------------------------- 1 |  62 | 63 | 64 | 103 | 104 | 146 | 147 | -------------------------------------------------------------------------------- /this-call-apply/README.md: -------------------------------------------------------------------------------- 1 | # this call apply 2 | 3 | ## this 4 | 5 | * this 具体指向哪个对象是在运行时基于函数的执行环境动态绑定,而非声明时的环境 6 | * 作为对象的方法调用,this 指向该对象 7 | * 作为普通函数调用,this 指向全局对象 8 | * 在严格模式下 this 不会指向全局对象,而是 `undefined` 9 | * 作为构造器函数调用,this 指向返回的对象 10 | * 如果构造器显式返回一个 `object` 类型的对象,new 运算结果最终返回这个对象,而不是 this 11 | * 如果不显式返回数据,或返回非对象类型的数据,就不会有上面的问题 12 | * call 和 apply 可以动态改变传入的 this,能很好体现 JavaScript 的函数式语言特性 13 | 14 | ```javascript 15 | var getId = function (id) { 16 | return document.getElementById(id); 17 | }; 18 | getId('div1'); 19 | 20 | // 我们也许思考过为什么不能用下面这种更简单的方式: 21 | var getId = document.getElementById; 22 | getId('div1'); 23 | 24 | // 我们可以尝试利用 apply 把 document 当作this 传入 getId 函数,帮助修正 this: 25 | document.getElementById = (function (func) { 26 | return function () { 27 | return func.apply(document, arguments); 28 | } 29 | })(document.getElementById); 30 | 31 | var getId = document.getElementById; 32 | var div = getId('div1'); 33 | ``` 34 | 35 | ## Function.prototype.call 与 Function.prototype.apply 36 | 37 | * JavaScript 的参数在内部就是用一个数组来表示,从这个意义上来说,apply 比 call 使用率更高 38 | * call 是包装在 apply 上面的一颗语法糖 39 | * 如果传入第一个参数是 `null` 函数体内 `this` 会指向默认宿主对象 `global` 40 | * 严格模式下,函数体内 `this` 还是为 `null` 41 | 42 | ```javascript 43 | // 模拟 Function.prototype.bind 44 | Function.prototype.bind = function () { 45 | // 保存原函数 46 | const self = this; 47 | // 需绑定的 this 上下文 48 | const context = [].shift.call(arguments); 49 | // 剩余的参数转为数组 50 | const args = [].slice.call(arguments); 51 | return function () { 52 | // 合并两次分别传入的参数作为新函数的参数 53 | return self.apply(context, [].concat(args, [].slice.call(arguments))); 54 | }; 55 | }; 56 | ``` 57 | 58 | -------------------------------------------------------------------------------- /中介者模式/14.html: -------------------------------------------------------------------------------- 1 |  29 | 30 | 31 | -------------------------------------------------------------------------------- /中介者模式/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzlu/JS-design-pattern/d409eed0b63f51bb8218438356245da7a7da71de/中介者模式/README.md -------------------------------------------------------------------------------- /享元模式/12.html: -------------------------------------------------------------------------------- 1 |  20 | 21 | 43 | 44 | 45 | 112 | 113 | 114 | 212 | 213 | 214 | 251 | 252 | 253 | -------------------------------------------------------------------------------- /享元模式/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzlu/JS-design-pattern/d409eed0b63f51bb8218438356245da7a7da71de/享元模式/README.md -------------------------------------------------------------------------------- /代码重构/22.html: -------------------------------------------------------------------------------- 1 |  24 | 25 | 26 | 48 | 49 | 71 | 72 | 100 | 101 | 102 | 128 | 129 | 130 | 158 | 159 | 165 | 166 | 179 | 180 | 215 | 216 | 263 | 264 | 265 | -------------------------------------------------------------------------------- /代码重构/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzlu/JS-design-pattern/d409eed0b63f51bb8218438356245da7a7da71de/代码重构/README.md -------------------------------------------------------------------------------- /动态创建命名空间.js: -------------------------------------------------------------------------------- 1 | const MyApp = {}; 2 | 3 | MyApp.namespace = function (name) { 4 | const parts = name.split('.'); 5 | let current = MyApp; 6 | for (let part of parts){ 7 | if (!current[part]){ 8 | current[part] = {}; 9 | } 10 | current = current[part]; 11 | } 12 | }; 13 | 14 | MyApp.namespace('event'); 15 | MyApp.namespace('dom.style'); 16 | MyApp.namespace('foo.bar.baz'); 17 | 18 | console.dir(MyApp); 19 | 20 | // 上述代码等价于: 21 | // const MyApp = { 22 | // event: {}, 23 | // dom: { 24 | // style: {} 25 | // } 26 | // }; 27 | -------------------------------------------------------------------------------- /单一职责原则/18.html: -------------------------------------------------------------------------------- 1 |  28 | 29 | 59 | 60 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 118 | -------------------------------------------------------------------------------- /单一职责原则/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzlu/JS-design-pattern/d409eed0b63f51bb8218438356245da7a7da71de/单一职责原则/README.md -------------------------------------------------------------------------------- /开放封闭原则/20.html: -------------------------------------------------------------------------------- 1 |  15 | 16 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /开放封闭原则/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzlu/JS-design-pattern/d409eed0b63f51bb8218438356245da7a7da71de/开放封闭原则/README.md -------------------------------------------------------------------------------- /接口和面向接口编程/21.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 33 | 34 | 35 | 36 | 47 | 48 | -------------------------------------------------------------------------------- /接口和面向接口编程/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzlu/JS-design-pattern/d409eed0b63f51bb8218438356245da7a7da71de/接口和面向接口编程/README.md -------------------------------------------------------------------------------- /最少知识原则/19.html: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /最少知识原则/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzlu/JS-design-pattern/d409eed0b63f51bb8218438356245da7a7da71de/最少知识原则/README.md --------------------------------------------------------------------------------