├── 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
--------------------------------------------------------------------------------