├── .project ├── AMD └── README.md ├── CommonJS ├── README.md └── cmd_Amd.md ├── JavaScript-Design-Patterns ├── Chaining │ ├── 1-Introduction to chaining.js │ ├── 3-Building a chainable JavaScript library.js │ ├── 3-The structure of the chain.js │ ├── 4-Using callbacks.js │ └── README.md ├── README.md ├── State-Pattern │ └── README.md ├── The-Adapter-Pattern │ ├── 1 - Characteristics of an adapter.js │ ├── 2 - Adapting one library to another.js │ ├── 3 - Adapting an email API.html │ ├── 4 - More on adapting an email API.js │ └── README.md ├── The-Bridge-Pattern │ ├── 1 - Event listener example.js │ ├── 2 - Other examples of bridges.js │ ├── 3 - Bridging multiple classes together.js │ ├── 4 - Building an XHR connection queue.js │ ├── 5 - XHR connection queue example page.html │ ├── 6 - Where have bridges been used_.js │ └── README.md ├── The-Chain-of-Responsibility-Pattern │ ├── 1 - PublicLibrary class.js │ ├── 2 - PublicLibrary class with hard-coded catalogs.js │ ├── 3 - PublicLibrary class with chain of responsibility catalogs.js │ ├── 4 - GenreCatalog and SciFiCatalog classes.js │ ├── 5 - The findBooks method.js │ ├── 6 - DynamicGallery class from Chapter 9.js │ ├── 7 - DynamicGallery class with optimization.js │ ├── 8 - DynamicGallery class with tags.js │ └── README.md ├── The-Command-Pattern │ ├── 1 - StopAd and StartAd classes.js │ ├── 2 - Commands using closures.js │ ├── 3 - Using interfaces with the command pattern.js │ ├── 4 - Types of commands.js │ ├── 5 - Menu commands.js │ ├── 6 - Undo with reversible commands.js │ ├── 7 - Undo with command logging.js │ └── README.md ├── The-Composite-Pattern │ ├── 1 - Form validation.js │ ├── 2 - Adding operations to FormItem.js │ ├── 3 - Adding classes to the hierarchy.js │ ├── 4 - Image gallery example.js │ └── README.md ├── The-Decorator-Pattern │ ├── 1 - Structure of the decorator.js │ ├── 2 - In what ways can a decorator modify its component.js │ ├── 3 - The role of the factory.js │ ├── 4 - Function decorators.js │ ├── 5 - Method profiler.js │ └── README.md ├── The-Facade-Pattern │ ├── 1 - Some facades you probably already know about.js │ ├── 2 - Facades as convenience methods.js │ ├── 3 - Setting styles on HTML elements.js │ ├── 4 - Creating an event utility.js │ └── README.md ├── The-Factory-Pattern │ ├── 1 - The simple factory.js │ ├── 2 - The factory pattern.js │ ├── 3 - XHR factory example.js │ ├── 4 - Specialized connection objects.js │ ├── 5 - Choosing connection objects at run-time.js │ ├── 6 - RSS reader example.js │ └── README.md ├── The-Flyweight-Pattern │ ├── 1 - Car registration example.js │ ├── 2 - Web calendar example.js │ ├── 3 - Tooltip example.js │ ├── 4 - Storing instances for later reuse.js │ └── README.md ├── The-Observer-Pattern │ ├── 1 - Sellsian approach.js │ ├── 2 - Newspapers and subscribers.js │ ├── 3 - Building an observer API.js │ ├── 4 - Animation example.js │ ├── 5 - Event listeners are also observers.js │ └── README.md ├── The-Proxy-Pattern │ ├── 1 - PublicLibrary class from Chapter 3.js │ ├── 2 - PublicLibraryProxy class.js │ ├── 3 - PublicLibraryVirtualProxy class.js │ ├── 4 - Page statistics example.js │ ├── 5 - General pattern for wrapping a web service.js │ ├── 6 - Directory lookup example.js │ ├── 7 - General pattern for creating a virtual proxy.js │ └── README.md └── The-Singleton-Pattern │ ├── 1 - Basic structure of the singleton.js │ ├── 2 - Namespacing.js │ ├── 3 - Wrappers for page specific code.js │ ├── 4 - Private methods with underscores.js │ ├── 5 - Private methods with closures.js │ ├── 6 - Comparing the two techniques.js │ ├── 7 - Lazy instantiation.js │ ├── 8 - Branching.js │ ├── 9 - Creating XHR objects with branching.js │ └── README.md ├── README.md ├── README.txt ├── Recommend └── README.md ├── index.html ├── javascript-based ├── crossDomain.html ├── event.html ├── namespace │ ├── README.md │ ├── namespace.HTML │ └── namespace1.HTML ├── reference.js └── urlHelper ├── jquery ├── 1_closure.js └── README.md ├── mobile └── README.md ├── object-oriented ├── AOP │ └── Markdown.md ├── Encapsulation-and-Information-Hiding │ ├── 1-Book example class.js │ ├── 2-Fully exposed object.js │ ├── 3 - Private methods with underscores.js │ ├── 4 - Scope, nested functions, and closures.js │ ├── 5 - Private methods with closures.js │ ├── 6 - Static members.js │ └── 7 - Constants.js ├── Expressive-JavaScript │ ├── 1 - The flexibility of JavaScript.js │ ├── 2 - Functions as first-class objects.js │ └── 3 - The mutability of objects.js ├── Inheritance │ ├── 1 - Classical inheritance.js │ ├── 10 - Edit-in-place example, prototypal.js │ ├── 11 - Edit-in-place example, mixin.js │ ├── 2 - The prototype chain.js │ ├── 3 - The extend function.js │ ├── 4 - Prototypal inheritance.js │ ├── 5 - Asymmetrical reading and writing.js │ ├── 6 - The clone function.js │ ├── 7 - Mixin classes.js │ ├── 8 - The augment function.js │ └── 9 - Edit-in-place example, classical.js ├── Interfaces │ ├── 1 - Describing interfaces with comments.js │ ├── 2 - Emulating interfaces with attribute checking.js │ ├── 3 - Emulating interfaces with duck typing.js │ ├── 4 - The interface implementation for this book.js │ ├── 5 - The Interface class.js │ ├── 6 - When to use the Interface class.js │ └── 7 - An example illustrating the use of the Interface class.js └── Introduction │ ├── Interface.js │ └── Library.js └── question ├── Demo1.js └── bind_demo /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | Source Code 4 | 5 | 6 | 7 | 8 | 9 | 10 | com.aptana.projects.webnature 11 | 12 | 13 | -------------------------------------------------------------------------------- /AMD/README.md: -------------------------------------------------------------------------------- 1 |

异步模块定义


2 | AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。
3 | 所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
4 |
5 | AMD也采用require()语句加载模块,但是不同于CommonJS,它要求两个参数:
6 | 7 | 8 |   require([module], callback); 9 | 10 | 11 | 第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数。如果将前面的代码改写成AMD形式,就是下面这样:<
12 | 13 | 14 |   require(['math'], function (math) { 15 | 16 |     math.add(2, 3); 17 | 18 |   }); 19 | 20 |
21 | math.add()与math模块加载不是同步的,浏览器不会发生假死。所以很显然,AMD比较适合浏览器环境。
22 | 23 | 目前,主要有两个Javascript库实现了AMD规范:require.js和curl.js。本系列的第三部分,将通过介绍require.js,进一步讲解AMD的用法,以及如何将模块化编程投入实战。
24 | -------------------------------------------------------------------------------- /CommonJS/README.md: -------------------------------------------------------------------------------- 1 |

CommonJS

2 | 3 | 2009年,美国程序员Ryan Dahl创造了node.js项目,将javascript语言用于服务器端编程。
4 | 5 | 这标志"Javascript模块化编程"正式诞生。因为老实说,在浏览器环境下,没有模块也不是特别大的问题,毕竟网页程序的复杂性有限;
6 | 但是在服务器端,一定要有模块,与操作系统和其他应用程序互动,否则根本没法编程。
7 |
8 | node.js的模块系统,就是参照CommonJS规范实现的。在CommonJS中,有一个全局性方法require(),用于加载模块。假定有一个数学模块math.js,就可以像下面这样加载。
9 | 10 |   var math = require('math'); 11 | 12 | 然后,就可以调用模块提供的方法:
13 | 14 | 15 |   var math = require('math'); 16 |   math.add(2,3); // 5 17 | 18 | 因为这个系列主要针对浏览器编程,不涉及node.js,所以对CommonJS就不多做介绍了。我们在这里只要知道,require()用于加载模块就行了 19 | -------------------------------------------------------------------------------- /CommonJS/cmd_Amd.md: -------------------------------------------------------------------------------- 1 | ### 定义: ### 2 | 3 | 异步模块定义(AMD)是Asynchronous Module Definition的缩写,是 RequireJS 在推广过程中对模块定义的规范化产出。规范传送门 4 | 通用模块定义(CMD)是Common Module Definition的缩写,是SeaJS 在推广过程中对模块定义的规范化产出。规范传送门 5 | 6 | ### 区别: ### 7 | 8 | 1. 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible. 9 | 10 | 2. CMD 推崇依赖就近,AMD 推崇依赖前置。看代码: 11 | 12 | // CMD 13 | define(function(require, exports, module) { 14 | var a = require('./a') 15 | a.doSomething() 16 | // 此处略去 100 行 17 | var b = require('./b') // 依赖可以就近书写 18 | b.doSomething() 19 | // ... 20 | }) 21 | 22 | // AMD 默认推荐的是 23 | define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好 24 | a.doSomething() 25 | // 此处略去 100 行 26 | b.doSomething() 27 | ... 28 | }) 29 | 30 | 虽然 AMD 也支持 CMD 的写法,同时还支持将 require 作为依赖项传递,但 RequireJS 的作者默认是最喜欢上面的写法,也是官方文档里默认的模块定义写法。 31 | 32 | 33 | 3. AMD 的 API 默认是一个当多个用,CMD 的 API 严格区分,推崇职责单一。比如 AMD 里,require 分全局 require 和局部 require,都叫 require。CMD 里,没有全局 require,而是根据模块系统的完备性,提供 seajs.use 来实现模块系统的加载启动。CMD 里,每个 API 都简单纯粹。 34 | 35 | 36 | 4. 还有一些细节差异,具体看这个规范的定义就好,就不多说了。 37 | 38 | 另外,SeaJS 和 RequireJS 的差异,可以参考:https://github.com/seajs/seajs/issues/277 39 | 40 | 比较 NodeJS 和 SeaJS 在模块机制上的异同 41 | 42 | ### 比较两者在模块环境上的区别。 ### 43 | 44 | 已知差异点: 45 | > 46 | NodeJS 里,模块文件里的 this === module.exports; 47 | SeaJS 里,this === window。 48 | NodeJS 里,模块文件里的 return xx 会被忽略; 49 | SeaJS 里,return xx 等价 module.exports = xx。 50 | NodeJS 里,require 是懒加载 + 懒执行的。在 SeaJS 里是提前加载好 + 懒执行。 51 | SeaJS 里,require(id) 中的 id 必须是字符串直接量。NodeJS 里没这个限制。 52 | 53 | 参考:https://github.com/seajs/seajs/issues/141 -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/Chaining/1-Introduction to chaining.js: -------------------------------------------------------------------------------- 1 | // Without chaining: 2 | addEvent($('example'), 'click', function() { 3 | setStyle(this, 'color', 'green'); 4 | show(this); 5 | }); 6 | 7 | // With chaining: 8 | $('example').addEvent('click', function() { 9 | $(this).setStyle('color', 'green').show(); 10 | }); 11 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/Chaining/3-Building a chainable JavaScript library.js: -------------------------------------------------------------------------------- 1 | // Include syntactic sugar to help the development of our interface. 2 | 3 | Function.prototype.method = function(name, fn) { 4 | this.prototype[name] = fn; 5 | return this; 6 | }; 7 | (function() { 8 | function _$(els) { 9 | // //todo 10 | } 11 | /* 12 | Events 13 | * addEvent 14 | * getEvent 15 | */ 16 | _$.method('addEvent', function(type, fn) { 17 | // //todo 18 | }).method('getEvent', function(e) { 19 | // //todo 20 | }). 21 | /* 22 | DOM 23 | * addClass 24 | * removeClass 25 | * replaceClass 26 | * hasClass 27 | * getStyle 28 | * setStyle 29 | */ 30 | method('addClass', function(className) { 31 | // //todo 32 | }).method('removeClass', function(className) { 33 | // ... 34 | }).method('replaceClass', function(oldClass, newClass) { 35 | // ... 36 | }).method('hasClass', function(className) { 37 | // ... 38 | }).method('getStyle', function(prop) { 39 | // ... 40 | }).method('setStyle', function(prop, val) { 41 | // ... 42 | }). 43 | /* 44 | AJAX 45 | * load. Fetches an HTML fragment from a URL and inserts it into an element. 46 | */ 47 | method('load', function(uri, method) { 48 | // ... 49 | }); 50 | window.$ = function() { 51 | return new _$(arguments); 52 | }); 53 | })(); 54 | 55 | Function.prototype.method = function(name, fn) { 56 | // ... 57 | }; 58 | (function() { 59 | function _$(els) { 60 | // ... 61 | } 62 | _$.method('addEvent', function(type, fn) { 63 | // ... 64 | }) 65 | // ... 66 | 67 | window.installHelper = function(scope, interface) { 68 | scope[interface] = function() { 69 | return new _$(arguments); 70 | } 71 | }; 72 | })(); 73 | 74 | 75 | /* Usage. */ 76 | 77 | installHelper(window, '$'); 78 | 79 | $('example').show(); 80 | 81 | 82 | /* Another usage example. */ 83 | 84 | // Define a namespace without overwriting it if it already exists. 85 | window.com = window.com || {}; 86 | com.example = com.example || {}; 87 | com.example.util = com.example.util || {}; 88 | 89 | installHelper(com.example.util, 'get'); 90 | 91 | (function() { 92 | var get = com.example.util.get; 93 | get('example').addEvent('click', function(e) { 94 | get(this).addClass('hello'); 95 | }); 96 | })(); 97 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/Chaining/3-The structure of the chain.js: -------------------------------------------------------------------------------- 1 | function $() { 2 | var elements = []; 3 | for (var i = 0, len = arguments.length; i < len; ++i) { 4 | var element = arguments[i]; 5 | if (typeof element == 'string') { 6 | element = document.getElementById(element); 7 | } 8 | if (arguments.length == 1) { 9 | return element; 10 | } 11 | elements.push(element); 12 | } 13 | return elements; 14 | } 15 | 16 | 17 | 18 | (function() { 19 | // Use a private class. 20 | function _$(els) { 21 | this.elements = []; 22 | for (var i = 0, len = els.length; i < len; ++i) { 23 | var element = els[i]; 24 | if (typeof element == 'string') { 25 | element = document.getElementById(element); 26 | } 27 | this.elements.push(element); 28 | } 29 | } 30 | // The public interface remains the same. 31 | window.$ = function() { 32 | return new _$(arguments); 33 | }; 34 | })(); 35 | 36 | 37 | 38 | (function() { 39 | function _$(els) { 40 | // ... 41 | } 42 | _$.prototype = { 43 | each: function(fn) { 44 | for ( var i = 0, len = this.elements.length; i < len; ++i ) { 45 | fn.call(this, this.elements[i]); 46 | } 47 | return this; 48 | }, 49 | setStyle: function(prop, val) { 50 | this.each(function(el) { 51 | el.style[prop] = val; 52 | }); 53 | return this; 54 | }, 55 | show: function() { 56 | var that = this; 57 | this.each(function(el) { 58 | that.setStyle('display', 'block'); 59 | }); 60 | return this; 61 | }, 62 | addEvent: function(type, fn) { 63 | var add = function(el) { 64 | if (window.addEventListener) { 65 | el.addEventListener(type, fn, false); 66 | } 67 | else if (window.attachEvent) { 68 | el.attachEvent('on'+type, fn); 69 | } 70 | }; 71 | this.each(function(el) { 72 | add(el); 73 | }); 74 | return this; 75 | } 76 | }; 77 | window.$ = function() { 78 | return new _$(arguments); 79 | }; 80 | })(); 81 | 82 | 83 | /* Usage. */ 84 | 85 | $(window).addEvent('load', function() { 86 | $('test-1', 'test-2').show(). 87 | setStyle('color', 'red'). 88 | addEvent('click', function(e) { 89 | $(this).setStyle('color', 'green'); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/Chaining/4-Using callbacks.js: -------------------------------------------------------------------------------- 1 | // Accessor without function callbacks: returning requested data in accessors. 2 | window.API = window.API || {}; 3 | API.prototype = function() { 4 | var name ='Hello world'; 5 | // Privileged mutator method. 6 | this.setName= function (newName) { 7 | name = newName; 8 | return this; 9 | }; 10 | // Privileged accessor method. 11 | this.getName = function() { 12 | return name; 13 | } 14 | }(); 15 | 16 | // Implementation code. 17 | var o = new API; 18 | console.log(o.getName()); // Displays 'Hello world'. 19 | console.log(o.setName('Meow').getName()); // Displays 'Meow'. 20 | 21 | // Accessor with function callbacks. 22 | window.API2 = window.API2 || {}; 23 | API2.prototype = function() { 24 | var name = 'Hello world'; 25 | // Privileged mutator method. 26 | this.setName= function(newName) { 27 | name = newName; 28 | return this; 29 | }, 30 | // Privileged accessor method. 31 | this.getName=function(callback) { 32 | callback.call(this, name); 33 | return this; 34 | } 35 | }(); 36 | 37 | // Implementation code. 38 | var o2 = new API2; 39 | o2.getName(console.log).setName('Meow').getName(console.log); 40 | // Displays 'Hello world' and then 'Meow'. 41 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/Chaining/README.md: -------------------------------------------------------------------------------- 1 | 方法的链式调用
2 | Javascript中的对象是作为引用被传递的,所以可以让每个方法都传回对象的引用,即返回this值;
3 | 这种编程风格可以简化代码的编写工作,让代码更简洁易读,
4 | 示例程序
5 |
6 | 为了解决命名冲突,使用指定的API,可以为框架提供一个安装器
7 | 示例程序
8 |
9 | 如果想让getter和setter方法都支持链式调用,可以在getter方法中使用回调技术, 10 | 示例程序 -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/README.md: -------------------------------------------------------------------------------- 1 |

设计模式

2 | 对“模式”的广义解释是“反复发生的事件或对象的固定用法...可以用来作为重复使用的模板或模型”(http://en.wikipedia.org/wiki/Pattern)。
3 | 在软件开发领域,模式是指常见问题的通用解决方案。模式不是简单的代码复制和粘贴,而是一种最佳实践,一种高级抽象,是解决某一类问题的范本。
4 | 学习这些模式非常重要,因为:
5 | > > 这些模式提供了经过论证的最佳实践,它可以帮助我们更好的编码,避免重复造轮子。 6 | > > 这些模式提供了高一层的抽象。一个时间段内大脑只能处理一定复杂度的逻辑,因此当你处理更繁琐棘手的问题时,使用模式可以帮你理清头绪,不会被低级的琐事阻碍大脑思考, 7 | 因为所有的细枝末节都可以被归类和切分成不同的块(模式)。 8 | 这些模式为开发者和团队提供了沟通的渠道,团队开发者之间往往是异地协作,不会有经常面对面的沟通机会。简单的代码编写技巧和技术问题处理方式的约定(代码注释) 9 | 可以使开发者之间的交流更加通畅。例如,说“即时函数”(immediate function)比说“你写好一个函数后,在函数的结束花括号的后面添加一对括号, 10 | 这样能在定义函数结束后马上执行这个函数”要更容易表达和理解。 11 |
12 | 《parctical common lisp》的作者曾说,如果你需要一种模式,那一定是哪里出了问题。他所说的问题是指因为语言的天生缺陷,不得不去寻求和总结一种通用的解决方案。
13 | 不管是弱类型或强类型,静态或动态语言,命令式或说明式语言、每种语言都有天生的优缺点。一个牙买加运动员, 在短跑甚至拳击方面有一些优势,在练瑜伽上就欠缺一些。
14 | 术士和暗影牧师很容易成为一个出色的辅助,而一个背着梅肯满地图飞的敌法就会略显尴尬。
15 |
16 | ###反模式 17 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/State-Pattern/README.md: -------------------------------------------------------------------------------- 1 | 状态模式主要可以用于这种场景 2 | 1 一个对象的行为取决于它的状态 3 | 2 一个操作中含有庞大的条件分支语句 4 |

回想下街头霸王的游戏

5 | 隆有走动,攻击,防御,跌倒,跳跃等等多种状态,而这些状态之间既有联系又互相约束。
6 | 比如跳跃的时候是不能攻击和防御的。跌倒的时候既不能攻击又不能防御,而走动的时候既可以攻击也可以跳跃。
7 | 要完成这样一系列逻辑, 常理下if else是少不了的. 而且数量无法估计, 特别是增加一种新状态的时候, 可能要从代码的第10行一直改到900行.
8 | 9 | 10 | ```javascript 11 | if ( state === 'jump' ){ 12 | if ( currState === 'attack' || currState === 'defense' ){ 13 | return false; 14 | } 15 | }else if ( state === 'wait' ){ 16 | if ( currState === 'attack' || currState === 'defense' ){ 17 | return true; 18 | } 19 | } 20 | 21 | ``` 22 | 23 | 为了消灭这些if else, 并且方便修改和维护, 我们引入一个状态类. 24 | 25 | ```javascript 26 | var StateManager = function(){ 27 | var currState = 'wait'; 28 | var states = { 29 | jump: function( state ){ 30 | }, 31 | wait: function( state ){ 32 | }, 33 | attack: function( state ){ 34 | }, 35 | crouch: function( state ){ 36 | }, 37 | defense: function( state ){ 38 | if ( currState === 'jump' ){ 39 | return false; //不成功,跳跃的时候不能防御 40 | } 41 | //do something; //防御的真正逻辑代码, 为了防止状态类的代码过多, 应该把这些逻辑继续扔给真正的fight类来执行. 42 | currState = 'defense'; // 切换状态 43 | } 44 | } 45 | var changeState = function( state ){ 46 | states[ state ] && states[ state ](); 47 | } 48 | return { 49 | changeState : changeState 50 | } 51 | } 52 | var stateManager = StateManager(); 53 | stateManager.changeState( 'defense' ); 54 | ``` 55 | Edit By [MaHua](http://mahua.jser.me) 56 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Adapter-Pattern/1 - Characteristics of an adapter.js: -------------------------------------------------------------------------------- 1 | var clientObject = { 2 | string1: 'foo', 3 | string2: 'bar', 4 | string3: 'baz' 5 | }; 6 | function interfaceMethod(str1, str2, str3) { 7 | //todo 8 | } 9 | 10 | function clientToInterfaceAdapter(o) { 11 | interfaceMethod(o.string1, o.string2, o.string3); 12 | } 13 | 14 | /* Usage. */ 15 | 16 | clientToInterfaceAdapter(clientObject); 17 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Adapter-Pattern/2 - Adapting one library to another.js: -------------------------------------------------------------------------------- 1 | // Prototype $ function. 2 | function $() { 3 | var elements = new Array(); 4 | for(var i = 0; i < arguments.length; i++) { 5 | var element = arguments[i]; 6 | if(typeof element == 'string') 7 | element = document.getElementById(element); 8 | if(arguments.length == 1) 9 | return element; 10 | elements.push(element); 11 | } 12 | return elements; 13 | } 14 | 15 | /* YUI get method. */ 16 | YAHOO.util.Dom.get = function(el) { 17 | if(YAHOO.lang.isString(el)) { 18 | return document.getElementById(el); 19 | } 20 | if(YAHOO.lang.isArray(el)) { 21 | var c = []; 22 | for(var i = 0, len = el.length; i < len; ++i) { 23 | c[c.length] = Y.Dom.get(el[i]); 24 | } 25 | return c; 26 | } 27 | if(el) { 28 | return el; 29 | } 30 | return null; 31 | }; 32 | 33 | function PrototypeToYUIAdapter() { 34 | return YAHOO.util.Dom.get(arguments); 35 | } 36 | function YUIToPrototypeAdapter(el) { 37 | return $.apply(window, el); 38 | } 39 | 40 | $ = PrototypeToYUIAdapter; 41 | or vice-versa, for those who are migrating from YUI to Prototype: 42 | YAHOO.util.Dom.get = YUIToPrototypeAdapter; 43 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Adapter-Pattern/4 - More on adapting an email API.js: -------------------------------------------------------------------------------- 1 | // DED.util.substitute()函数的使用例子 2 | var substitutionObject = { 3 | name: "world", 4 | place: "Google" 5 | }; 6 | var text = 'Hello {name}, welcome to {place}'; 7 | var replacedText = DED.util.substitute(text, substitutionObject); 8 | console.log(replacedText); 9 | // produces "Hello world, welcome to Google" 10 | 11 | // 原来fooMail的API中getMail的使用方式 12 | fooMail.getMail(function(text) { 13 | $('message-pane').innerHTML = text; 14 | }); 15 | 16 | // 为dedMail添加适配器,使其调用方式和fooMail的一样 17 | var dedMailtoFooMailAdapter = {}; 18 | dedMailtoFooMailAdapter.getMail = function(id, callback) { 19 | dedMail.getMail(id, function(resp) { 20 | var resp = eval('('+resp+')'); 21 | var details = '

From: {from}
'; 22 | details += 'Sent: {date}

'; 23 | details += '

Message:
'; 24 | details += '{message}

'; 25 | callback(DED.util.substitute(details, resp)); 26 | }); 27 | }; 28 | // Other methods needed to adapt dedMail to the fooMail interface. 29 | //todo 30 | 31 | // Assign the adapter to the fooMail variable. 32 | // 把适配器赋给fooMail变量,这样就不用修改客户端的其他代码了 33 | fooMail = dedMailtoFooMailAdapter; 34 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Adapter-Pattern/README.md: -------------------------------------------------------------------------------- 1 |

适配器模式

2 | 适配模式可用来在现有接口和不兼容的类之间进行适配,使用这种模式的对象又叫包装器(wrapper),因为它们是在用一个新的接口包装另一个对象。
3 | 从表面上看,适配器模式很像外观模式。它们都要对别的对象进行包装并改变其呈现的接口。 4 | > 二者的差别在于它们如何改变接口。
5 | > > 外观元素展现的是一个简化的接口,它并不提供额外的选择,而且有时为了方便完成常见任务它还会做出一些假定。
6 | > > 适配器则要把一个接口转换为另一个接口,它并不会滤除某些能力,也不会简化接口。如果客户系统API不可用,就需要用到适配器。
7 | PS2-to-USB适配器就是一个例子。
8 | 示例: 9 | 创建一个简单的适配器方法,修改传入参数的方式 10 | 11 |
12 | 示例: 13 | 14 | 适配两个库 15 | 16 |
17 | 示例: 18 | 19 | 适配电子邮件API 20 | 21 |
22 | 23 | 添加适配器 24 | 25 |
26 | ###1.1、适配器模式的适用场合:
27 | 适配器适用于客户系统期待的接口与现有API提供的接口不兼容这种场合。适配器所适配的两个方法执行的应该是类似的任务,否则的话就解决不了问题。就像桥接元素和外观元素一样,通过创建适配器,可以把抽象与其实现隔离开来,以便二者独立变化。
28 | ###1.2、适配器模式之利:
29 | 用一个新的接口对现有类的接口进行包装,这样客户程序就能使用这个并非为其量身打造的类而又无需为此大动手术。
30 | ###1.3、设配器模式之弊:
31 | 有人认为适配器是一种不必要的开销,完全可以通过重写现有代码避免。此外适配器模式也会引入一批需要支持的新工具。如果现有API还未定形,或者新接口还未定形,那么适配器可能不会一直管用。
32 | 在涉及大型系统和遗留框架的情况下,它的优点往往比缺点更突出。 -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Bridge-Pattern/1 - Event listener example.js: -------------------------------------------------------------------------------- 1 | addEvent(element, 'click', getBeerById); 2 | function getBeerById(e) { 3 | var id = this.id; 4 | asyncRequest('GET', 'beer.uri?id=' + id, function(resp) { 5 | // Callback response. 6 | console.log('Requested Beer: ' + resp.responseText); 7 | }); 8 | } 9 | 10 | // 把事件处理的语句封装到回调函数中,现在我们通过接口而不是实现进行编程 11 | // getBeerById并没有和事件对象捆绑在一起。 12 | function getBeerById(id, callback) { 13 | // Make request for beer by ID, then return the beer data. 14 | asyncRequest('GET', 'beer.uri?id=' + id, function(resp) { 15 | // callback response 16 | callback(resp.responseText); 17 | }); 18 | } 19 | // 桥接器的使用:事件监听器回调函数 20 | addEvent(element, 'click', getBeerByIdBridge); 21 | function getBeerByIdBridge (e) { 22 | getBeerById(this.id, function(beer) { 23 | console.log('Requested Beer: '+beer); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Bridge-Pattern/2 - Other examples of bridges.js: -------------------------------------------------------------------------------- 1 | // 使用桥接模式收集某些私用性的信息,用特权方法作为桥梁以便访问私用变量空间 。 2 | var Public = function() { 3 | var secret = 3; 4 | this.privilegedGetter = function() { 5 | return secret; 6 | }; 7 | }; 8 | 9 | var o = new Public; 10 | var data = o.privilegedGetter(); 11 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Bridge-Pattern/3 - Bridging multiple classes together.js: -------------------------------------------------------------------------------- 1 | var Class1 = function(a, b, c) { 2 | this.a = a; 3 | this.b = b; 4 | this.c = c; 5 | } 6 | var Class2 = function(d) { 7 | this.d = d; 8 | }; 9 | 10 | var BridgeClass = function(a, b, c, d) { 11 | this.one = new Class1(a, b, c); 12 | this.two = new Class2(d); 13 | }; 14 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Bridge-Pattern/5 - XHR connection queue example page.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Ajax Connection Queue 7 | 8 | 9 | 95 | 106 | 107 | 108 |
109 |

Ajax Connection Queue

110 |
111 |
112 |

Add Requests to Queue

113 | 118 |
119 |

Other Queue Actions

120 | 126 |
127 |

Results:

128 |
129 |
130 |
131 | 132 | 133 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Bridge-Pattern/6 - Where have bridges been used_.js: -------------------------------------------------------------------------------- 1 | // Original function. 2 | 3 | var addRequest = function(request) { 4 | var data = request.split('-')[1]; 5 | // etc... 6 | }; 7 | 8 | // Function de-coupled. 9 | 10 | var addRequest = function(data) { 11 | // etc... 12 | }; 13 | 14 | // Bridge 15 | 16 | var addRequestFromClick = function(request) { 17 | addRequest(request.split('-')[0]); 18 | }; 19 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Bridge-Pattern/README.md: -------------------------------------------------------------------------------- 1 |

桥接模式

2 | 按照GoF的定义,桥接模式的作用在于“将抽象与其实现隔离开来,以便二者独立变化”。这种模式对于Javascript中常见的事件驱动的编程大有裨益。
3 | 桥接模式最常见和实际的应用场合之一是事件监听器回调函数。 example:事件监听器,把事件处理的语句封装到回调函数中,通过接口而不是实现进行编程。
4 | 桥接模式的其他例子:
5 | 除了在事件回调函数与接口之间进行桥接外
6 | 桥接模式也可以用于连接公开的API代码和私用的实现代码
7 | 此外,它还可以用来把多个类联结在一起。从类的角度来看,这意味着把接口作为公开的代码编写,而把类的实现作为私用代码编写。
8 | 9 | example:用桥接模式收集某些私用性的信息
10 | 11 | example:用桥接模式联结多个类 使用桥接模式,让Class1和Class2能够独立于BridgeClass而发生改变。 12 | 13 |
14 | 构建一个Ajax请求队列,把请求存储在浏览器内存中的一个队列 化数组中,刷新队列时,每个请求都会按“先入先出”的顺序被发送给一个后端的Web服务。
15 | 16 | 该Ajax请求队列的框架实现:example 17 | 18 |
19 | 20 | 该Ajax请求队列的使用例子:example
21 |
22 | 23 | 添加桥接方法的例子:example
24 |
25 | 这个应用程序到处都有桥接元素的身影,最明显的是,事件监听器回调函数并不直接与队列打交道,而是使用了桥接函数。
26 | 在供用户执行刷新和暂停操作的部分,我们提供了一个动作调度函数,其作用就是桥接用户操作所包含的输入信息并将其委托给恰当的处理代码。在DOM脚本编程中,这种技术也称为事件委托。click事件所代表的用户操作与DED.Queue实现完全分开的结果就是,后者的方法并不直接与那些事件耦合在一起,因此你可以在任何地方执行这些方法。
27 | 2.1、适用场合:
28 | 这里的要诀是要让接口“可桥接”,实际上也就是可适配。
29 | 2.2、优点:
30 | 把抽象与实现隔离开,有助于独立地管理软件的各组成部分。
31 | 2.3、缺点:
32 | 每使用一个桥接元素都要增加一次函数调用,这对应用程序的性能会有一些负面影响。提高了系统的复杂程度。如果一个桥接函数被用于连接两个函数,而其中某个函数根本不会在桥接函数之外被调用,那么此时这个桥接函数就不是非要不可的。
33 | 桥接模式“将抽象与实现隔离开来,以便二者独立变化”。它可以促进代码的模块化、促成更简洁的实现并提高抽象的灵活性。它可以用来把一组类和函数连接起来,而且提供了一种借助于特权函数访问私用数据的手段。 -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Chain-of-Responsibility-Pattern/1 - PublicLibrary class.js: -------------------------------------------------------------------------------- 1 | /* Interfaces. */ 2 | // 首先定义一些接口 3 | var Publication = new Interface('Publication', ['getIsbn', 'setIsbn', 'getTitle', 4 | 'setTitle', 'getAuthor', 'setAuthor', 'getGenres', 'setGenres', 'display']); 5 | var Library = new Interface('Library', ['addBook', 'findBooks', 'checkoutBook', 6 | 'returnBook']); 7 | var Catalog = new Interface('Catalog', ['handleFilingRequest', 'findBooks', 8 | 'setSuccessor']); 9 | 10 | /* Book class. */ 11 | 12 | var Book = function(isbn, title, author, genres) { // implements Publication 13 | //todo 14 | } 15 | 16 | 17 | /* PublicLibrary class. */ 18 | 19 | var PublicLibrary = function(books) { // implements Library 20 | this.catalog = {}; 21 | for(var i = 0, len = books.length; i < len; i++) { 22 | this.addBook(books[i]); 23 | } 24 | }; 25 | PublicLibrary.prototype = { 26 | findBooks: function(searchString) { 27 | var results = []; 28 | for(var isbn in this.catalog) { 29 | if(!this.catalog.hasOwnProperty(isbn)) continue; 30 | if(this.catalog[isbn].getTitle().match(searchString) || 31 | this.catalog[isbn].getAuthor().match(searchString)) { 32 | results.push(this.catalog[isbn]); 33 | } 34 | } 35 | return results; 36 | }, 37 | checkoutBook: function(book) { 38 | var isbn = book.getIsbn(); 39 | if(this.catalog[isbn]) { 40 | if(this.catalog[isbn].available) { 41 | this.catalog[isbn].available = false; 42 | return this.catalog[isbn]; 43 | } 44 | else { 45 | throw new Error('PublicLibrary: book ' + book.getTitle() + 46 | ' is not currently available.'); 47 | } 48 | } 49 | else { 50 | throw new Error('PublicLibrary: book ' + book.getTitle() + ' not found.'); 51 | } 52 | }, 53 | returnBook: function(book) { 54 | var isbn = book.getIsbn(); 55 | if(this.catalog[isbn]) { 56 | this.catalog[isbn].available = true; 57 | } 58 | else { 59 | throw new Error('PublicLibrary: book ' + book.getTitle() + ' not found.'); 60 | } 61 | }, 62 | addBook: function(newBook) { 63 | this.catalog[newBook.getIsbn()] = { book: newBook, available: true }; 64 | } 65 | }; 66 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Chain-of-Responsibility-Pattern/2 - PublicLibrary class with hard-coded catalogs.js: -------------------------------------------------------------------------------- 1 | /* PublicLibrary class, with hard-coded catalogs for genre. */ 2 | 3 | var PublicLibrary = function(books) { // implements Library 4 | this.catalog = {}; 5 | this.biographyCatalog = new BiographyCatalog(); 6 | this.fantasyCatalog = new FantasyCatalog(); 7 | this.mysteryCatalog = new MysteryCatalog(); 8 | this.nonFictionCatalog = new NonFictionCatalog(); 9 | this.sciFiCatalog = new SciFiCatalog(); 10 | 11 | for(var i = 0, len = books.length; i < len; i++) { 12 | this.addBook(books[i]); 13 | } 14 | }; 15 | PublicLibrary.prototype = { 16 | findBooks: function(searchString) { //todo }, 17 | checkoutBook: function(book) { //todo }, 18 | returnBook: function(book) { //todo }, 19 | // 这里需要分别对每个图书分类调用handleFilingRequest方法 20 | // 如果想增加更多图书类别那就需要修改构造函数和addBook方法这两处的代码。 21 | addBook: function(newBook) { 22 | // Always add the book to the main catalog. 23 | this.catalog[newBook.getIsbn()] = { book: newBook, available: true }; 24 | 25 | // Try to add the book to each genre catalog. 26 | this.biographyCatalog.handleFilingRequest(newBook); 27 | this.fantasyCatalog.handleFilingRequest(newBook); 28 | this.mysteryCatalog.handleFilingRequest(newBook); 29 | this.nonFictionCatalog.handleFilingRequest(newBook); 30 | this.sciFiCatalog.handleFilingRequest(newBook); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Chain-of-Responsibility-Pattern/3 - PublicLibrary class with chain of responsibility catalogs.js: -------------------------------------------------------------------------------- 1 | /* PublicLibrary class, with genre catalogs in a chain of responsibility. */ 2 | // 改进方法 3 | var PublicLibrary = function(books, firstGenreCatalog) { // implements Library 4 | this.catalog = {}; 5 | this.firstGenreCatalog = firstGenreCatalog; 6 | 7 | for(var i = 0, len = books.length; i < len; i++) { 8 | this.addBook(books[i]); 9 | } 10 | }; 11 | PublicLibrary.prototype = { 12 | findBooks: function(searchString) { //todo }, 13 | checkoutBook: function(book) { //todo }, 14 | returnBook: function(book) { //todo }, 15 | addBook: function(newBook) { 16 | // Always add the book to the main catalog. 17 | this.catalog[newBook.getIsbn()] = { book: newBook, available: true }; 18 | 19 | // Try to add the book to each genre catalog. 20 | this.firstGenreCatalog.handleFilingRequest(newBook); 21 | } 22 | }; 23 | 24 | 25 | // ----------------------------------------------------------------------------- 26 | // Usage example. 27 | // ----------------------------------------------------------------------------- 28 | // 这个例子中,原来的链上有5个环节,第6个环节是后来加的。这意味着图书馆每增加一本书都会通过调用链上第一个环节的 29 | // handleFilingRequest方法发起对该书的编目请求。该请求将沿目录链逐一经过6个目录,最后从链尾离开。 30 | // 链上新增的任何目录都会被挂到链尾。 31 | // Instantiate the catalogs. 32 | var biographyCatalog = new BiographyCatalog(); 33 | var fantasyCatalog = new FantasyCatalog(); 34 | var mysteryCatalog = new MysteryCatalog(); 35 | var nonFictionCatalog = new NonFictionCatalog(); 36 | var sciFiCatalog = new SciFiCatalog(); 37 | 38 | // Set the links in the chain. 39 | biographyCatalog.setSuccessor(fantasyCatalog); 40 | fantasyCatalog.setSuccessor(mysteryCatalog); 41 | mysteryCatalog.setSuccessor(nonFictionCatalog); 42 | nonFictionCatalog.setSuccessor(sciFiCatalog); 43 | 44 | // Give the first link in the chain as an argument to the constructor. 45 | var myLibrary = new PublicLibrary(books, biographyCatalog); 46 | 47 | // You can add links to the chain whenever you like. 48 | var historyCatalog = new HistoryCatalog(); 49 | sciFiCatalog.setSuccessor(historyCatalog); 50 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Chain-of-Responsibility-Pattern/4 - GenreCatalog and SciFiCatalog classes.js: -------------------------------------------------------------------------------- 1 | /* GenreCatalog class, used as a superclass for specific catalog classes. */ 2 | // 链上的对象,分类目录对象的实现 3 | var GenreCatalog = function() { // implements Catalog 4 | this.successor = null; 5 | this.catalog = []; 6 | }; 7 | GenreCatalog.prototype = { 8 | _bookMatchesCriteria: function(book) { 9 | return false; // Default implementation; this method will be overriden in 10 | // the subclasses. 11 | } 12 | handleFilingRequest: function(book) { 13 | // Check to see if the book belongs in this catagory. 14 | if(this._bookMatchesCriteria(book)) { 15 | this.catalog.push(book); 16 | } 17 | // Pass the request on to the next link. 18 | if(this.successor) { 19 | this.successor.handleFilingRequest(book); 20 | } 21 | }, 22 | findBooks: function(request) { 23 | if(this.successor) { 24 | return this.successor.findBooks(request); 25 | } 26 | }, 27 | setSuccessor: function(successor) { 28 | if(Interface.ensureImplements(successor, Catalog) { 29 | this.successor = successor; 30 | } 31 | } 32 | }; 33 | 34 | 35 | /* SciFiCatalog class. */ 36 | // 从这个超类中派生一个分类目录 37 | var SciFiCatalog = function() {}; // implements Catalog 38 | extend(SciFiCatalog, GenreCatalog); 39 | // 这个方法对图书的书名和类别进行检查,判断是否二者中有一个能够匹配搜索用词。 40 | SciFiCatalog.prototype._bookMatchesCriteria = function(book) { 41 | var genres = book.getGenres(); 42 | if(book.getTitle().match(/space/i)) { 43 | return true; 44 | } 45 | for(var i = 0, len = genres.length; i < len; i++) { 46 | var genre = genres[i].toLowerCase(); 47 | if(genres === 'sci-fi' || genres === 'scifi' || genres === 'science fiction') { 48 | return true; 49 | } 50 | } 51 | return false; 52 | }; 53 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Chain-of-Responsibility-Pattern/5 - The findBooks method.js: -------------------------------------------------------------------------------- 1 | /* PublicLibrary class. */ 2 | 3 | var PublicLibrary = function(books) { // implements Library 4 | //todo 5 | }; 6 | PublicLibrary.prototype = { 7 | // 修改findBooks方法,以便可以根据类别来缩小搜索范文。 8 | // 如果调用该方法时提供了可选的genres参数,那么搜索将只在属于其指定类别的图书中进行 9 | findBooks: function(searchString, genres) { 10 | // If the optional genres argument is given, search for books only in 11 | // those genres. Use the chain of responsibility to perform the search. 12 | if(typeof genres === 'array' && genres.length > 0) { 13 | var requestObject = { 14 | searchString: searchString, 15 | genres: genres, 16 | results: [] 17 | }; 18 | // 在分类中进行搜索 19 | var responseObject = this.firstGenreCatalog.findBooks(requestObject); 20 | return responseObject.results; 21 | } 22 | // Otherwise, search through all books. 23 | else { 24 | var results = []; 25 | for(var isbn in this.catalog) { 26 | if(!this.catalog.hasOwnProperty(isbn)) continue; 27 | if(this.catalog[isbn].getTitle().match(searchString) || 28 | this.catalog[isbn].getAuthor().match(searchString)) { 29 | results.push(this.catalog[isbn]); 30 | } 31 | } 32 | return results; 33 | } 34 | }, 35 | checkoutBook: function(book) { 36 | //todo 37 | }, 38 | returnBook:function(book) { 39 | //todo 40 | }, 41 | addBook: function(newBook) { 42 | //todo 43 | 44 | } 45 | }; 46 | 47 | /* GenreCatalog class, used as a superclass for specific catalog classes. */ 48 | 49 | var GenreCatalog = function() { // implements Catalog 50 | this.successor = null; 51 | this.catalog = []; 52 | this.genreNames = []; 53 | }; 54 | GenreCatalog.prototype = { 55 | _bookMatchesCriteria: function(book) { //todo 56 | }, 57 | handleFilingRequest: function(book) { //todo 58 | }, 59 | // 实现GenreCatalog这个超类中的findBooks方法,这个方法将被用在所有子类中,不需要被重写。 60 | findBooks: function(request) { 61 | var found = false; 62 | // 逐一检查请求对象中的每一个类别名称,看看其是否与对象中保存的一组类别名称中的某一个匹配。 63 | for(var i = 0, len = request.genres.length; i < len; i++) { 64 | for(var j = 0, nameLen = this.genreNames.length; j < nameLen; j++) { 65 | if(this.genreNames[j] === request.genres[i]) { 66 | found = true; // This link in the chain should handle 67 | // the request. 68 | break; 69 | } 70 | } 71 | } 72 | // 如果匹配,代码的第二部分会逐一检查目录中的所有图书,看看其书名和作者姓名是否与搜索用词匹配。 73 | if(found) { // Search through this catalog for books that match the search 74 | // string and aren't already in the results. 75 | outerloop: for(var i = 0, len = this.catalog.length; i < len; i++) { 76 | var book = this.catalog[i]; 77 | if(book.getTitle().match(searchString) || 78 | book.getAuthor().match(searchString)) { 79 | for(var j = 0, requestLen = request.results.length; j < requestLen; j++) { 80 | if(request.results[j].getIsbn() === book.getIsbn()) { 81 | continue outerloop; // The book is already in the results; skip it. 82 | } 83 | } 84 | // 与搜索名匹配的图书将被添加到请求对象中的results数组中 85 | request.results.push(book); // The book matches and doesn't already 86 | // appear in the results. Add it. 87 | } 88 | } 89 | } 90 | // 如果当前目录对象不是链上的最后一环,那么请求将被沿目录链继续下去 91 | // Continue to pass the request down the chain if the successor is set. 92 | if(this.successor) { 93 | return this.successor.findBooks(request); 94 | } 95 | // Otherwise, we have reached the end of the chain. Return the request 96 | // object back up the chain. 97 | else { 98 | return request; 99 | } 100 | }, 101 | setSuccessor: function(successor) { //todo 102 | 103 | } 104 | } 105 | 106 | 107 | 108 | /* SciFiCatalog class. */ 109 | 110 | var SciFiCatalog = function() { // implements Catalog 111 | // 在子类中填入一些具体的类别名称 112 | this.genreNames = ['sci-fi', 'scifi', 'science fiction']; 113 | }; 114 | extend(SciFiCatalog, GenreCatalog); 115 | SciFiCatalog.prototype._bookMatchesCriteria = function(book) { 116 | //todo 117 | }; 118 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Chain-of-Responsibility-Pattern/6 - DynamicGallery class from Chapter 9.js: -------------------------------------------------------------------------------- 1 | /* Interfaces. */ 2 | 3 | var Composite = new Interface('Composite', ['add', 'remove', 'getChild']); 4 | var GalleryItem = new Interface('GalleryItem', ['hide', 'show']); 5 | 6 | /* DynamicGallery class. */ 7 | // 动态图片库 8 | var DynamicGallery = function(id) { // implements Composite, GalleryItem 9 | this.children = []; 10 | this.element = document.createElement('div'); 11 | this.element.id = id; 12 | this.element.className = 'dynamic-gallery'; 13 | } 14 | DynamicGallery.prototype = { 15 | add: function(child) { 16 | Interface.ensureImplements(child, Composite, GalleryItem); 17 | this.children.push(child); 18 | this.element.appendChild(child.getElement()); 19 | }, 20 | remove: function(child) { 21 | for(var node, i = 0; node = this.getChild(i); i++) { 22 | if(node == child) { 23 | this.formComponents[i].splice(i, 1); 24 | break; 25 | } 26 | } 27 | this.element.removeChild(child.getElement()); 28 | }, 29 | getChild: function(i) { 30 | return this.children[i]; 31 | }, 32 | 33 | hide: function() { 34 | for(var node, i = 0; node = this.getChild(i); i++) { 35 | node.hide(); 36 | } 37 | this.element.style.display = 'none'; 38 | }, 39 | show: function() { 40 | this.element.style.display = ''; 41 | for(var node, i = 0; node = this.getChild(i); i++) { 42 | node.show(); 43 | } 44 | }, 45 | 46 | getElement: function() { 47 | return this.element; 48 | } 49 | }; 50 | 51 | /* GalleryImage class. */ 52 | // 图片 53 | var GalleryImage = function(src) { // implements Composite, GalleryItem 54 | this.element = document.createElement('img'); 55 | this.element.className = 'gallery-image'; 56 | this.element.src = src; 57 | } 58 | GalleryImage.prototype = { 59 | add: function() {}, // This is a leaf node, so we don't 60 | remove: function() {}, // implement these methods, we just 61 | getChild: function() {}, // define them. 62 | 63 | hide: function() { 64 | this.element.style.display = 'none'; 65 | }, 66 | show: function() { 67 | this.element.style.display = ''; 68 | }, 69 | 70 | getElement: function() { 71 | return this.element; 72 | } 73 | }; 74 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Chain-of-Responsibility-Pattern/7 - DynamicGallery class with optimization.js: -------------------------------------------------------------------------------- 1 | /* DynamicGallery class. */ 2 | 3 | var DynamicGallery = function(id) { // implements Composite, GalleryItem 4 | //todo 5 | } 6 | DynamicGallery.prototype = { 7 | add: function(child) { 8 | //todo 9 | }, 10 | remove: function(child) { 11 | //todo 12 | }, 13 | getChild: function(i) { 14 | //todo 15 | }, 16 | hide: function() { 17 | // 优化:从hide方法中删除将方法调用传递给子节点的那部分代码 18 | this.element.style.display = 'none'; 19 | }, 20 | show: function() { //todo 21 | }, 22 | getElement: function() { //todo 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Chain-of-Responsibility-Pattern/8 - DynamicGallery class with tags.js: -------------------------------------------------------------------------------- 1 | /* Interfaces. */ 2 | 3 | var Composite = new Interface('Composite', ['add', 'remove', 'getChild', 4 | 'getAllLeaves']); 5 | // 添加使用标签进行搜索的方法 6 | var GalleryItem = new Interface('GalleryItem', ['hide', 'show', 'addTag', 7 | 'getPhotosWithTag']); 8 | 9 | /* DynamicGallery class. */ 10 | 11 | var DynamicGallery = function(id) { // implements Composite, GalleryItem 12 | this.children = []; 13 | // 标签 14 | this.tags = []; 15 | this.element = document.createElement('div'); 16 | this.element.id = id; 17 | this.element.className = 'dynamic-gallery'; 18 | } 19 | DynamicGallery.prototype = { 20 | //todo 21 | addTag: function(tag) { 22 | this.tags.push(tag); 23 | for(var node, i = 0; node = this.getChild(i); i++) { 24 | node.addTag(tag); 25 | } 26 | }, 27 | getAllLeaves: function() { 28 | var leaves = []; 29 | for(var node, i = 0; node = this.getChild(i); i++) { 30 | leaves.concat(node.getAllLeaves()); 31 | } 32 | return leaves; 33 | }, 34 | // 职责链模式从组合对象中查找对应标签的对象 35 | getPhotosWithTag: function(tag) { 36 | // First search in this object's tags; if the tag is found here, we can stop 37 | // the search and just return all the leaf nodes. 38 | for(var i = 0, len = this.tags.length; i < len; i++) { 39 | if(this.tags[i] === tag) { 40 | return this.getAllLeaves(); 41 | } 42 | } 43 | 44 | // If the tag isn't found in this object's tags, pass the request down 45 | // the hierarchy. 46 | for(var results = [], node, i = 0; node = this.getChild(i); i++) { 47 | results.concat(node.getPhotosWithTag(tag)); 48 | } 49 | return results; 50 | }, 51 | //todo 52 | }; 53 | 54 | /* GalleryImage class. */ 55 | // 在叶类中的实现很简单,返回的结果都只包含叶对象自身。 56 | var GalleryImage = function(src) { // implements Composite, GalleryItem 57 | this.element = document.createElement('img'); 58 | this.element.className = 'gallery-image'; 59 | this.element.src = src; 60 | this.tags = []; 61 | } 62 | GalleryImage.prototype = { 63 | //todo 64 | addTag: function(tag) { 65 | this.tags.push(tag); 66 | }, 67 | getAllLeaves: function() { // Just return this. 68 | return [this]; 69 | }, 70 | getPhotosWithTag: function(tag) { 71 | for(var i = 0, len = this.tags.length; i < len; i++) { 72 | if(this.tags[i] === tag) { 73 | return [this]; 74 | } 75 | } 76 | return []; // Return an empty array if no matches were found. 77 | }, 78 | //todo 79 | }; 80 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Chain-of-Responsibility-Pattern/README.md: -------------------------------------------------------------------------------- 1 |

职责链模式

2 |
3 | 1、职责链的结构
4 | 5 | 首先来看一个图书馆增加图书的例子 6 |
7 | 8 | 添加目录类 9 |
10 | addBook方法中的代码固化了对5个不同类的依赖。如果想增加更多图书类别,那就需要修改构造函数和addBook方法这两处的代码。此外,把这些目录类别固化在构造函数中也没有多大意义。
11 | 使用职责链模式进行 12 | 13 | 改进 14 | 15 |
16 | 分类目录对象的具体 17 | 18 | 实现代码 19 | 20 |
21 | 2、传递请求
22 | 在链上传递请求有许多不同的方法,最常见的做法要么是使用一个专门的请求对象,要么是根本不使用参数,只依靠方法自身传递消息(第6节的例子)。本节讨论第一种方法。
23 | 在图书馆中根据分类搜索图书的例子,在GenreCatalog的findBoos()方法中进行传递请求的示例
24 | 3、在现有层次中实现职责链
25 | 在现有层次体系中实现这种模式往往更容易,在此情况下它经常与组合模式搭配使用。犹豫组合模式已经建立了一个对象层次体系,因此在此基础上添加一些用来处理(或传递)请求的方法很简单。
26 | 在组合模式结合了职责链之后,方法调用就不再总是不加分辨地往下一直传到叶对象。此时每一层都要对请求进行分析,以判断当前对象应该处理它还是应该把它往下传。组合对象实际上也会承担部分工作,而不是单纯依靠叶对象执行所有操作。这样一来就不用单独实现一些对象来作链上的环节,也不用手工设定下家对象。通过规定在某些场合下一些方法调用可以再层次体系中较高的层次上得到处理,并且组织较低层次的节点和叶节点获取这些调用,可以让组合模式变得更加健壮。
27 | 职责链模式和组合模式的结合对双方都是一种优化。由于职责链是现成的,所以设置代码的数量和用于职责链的额外对象的数目都减少了。由于在组合层次体系中某个方法可能会在高层得到处理,所以在整个树上执行该方法所需的计算量也降低了。
28 | 4、事件委托
29 | Javascript语言使用了职责链模式来决定如何处理事件。事件被触发时要经历两个阶段:事件捕获阶段,事件沿HTML层次体系向下传播,直到到达被点击元素;事件冒泡阶段,在这个阶段事件会历经同一批元素升回到顶层祖先。绑定在经过的这些元素上的事件监听器既可以停止事件传播,也可以让其继续沿层次体系向上或向下传播。这里传递请求对象称为事件对象,它包含着与事件有关的所有信息。
30 | 事件模型本质是作为职责链实现的所以事件模型也可以遵循如下经验:最好在层次体系较高层次上处理请求,假设有一个无序列表包含着几打列表项。与其为其中的每一个li元素绑定一个click事件监听器,不如只为ul元素绑定一个这种事件监听器,这样脚本会运行的更快,内存消耗得更少,而且以后维护起来也更容易,这种技术称为时间委托,这是职责链方面的知识有助于优化代码的情况之一。
31 | 5、职责链模式的适用场合
32 | 如果事先不知道在几个对象中有哪些能够处理请求,那么这就属于应该适用职责链的情况;
33 | 如果这批处理器对象在开发期间不可知,而是需要动态指定的话,那么也应该使用这种模式;
34 | 该模式还可以用在对于每个请求都不止有一个对象可以对它进行处理这种情况下。
35 | 使用这种模式,可以把特定的具体类与客户端隔离开,并代之以一条由弱耦合的对象组成的链,它将隐式地对请求进行处理,这有助于提高代码的模块程度和可维护性。
36 | 6、图片库的进一步讨论
37 | 组合模式实现的 38 | 39 | 图片库代码 40 | 41 |
42 | 43 | 6.1、用职责链提高组合对象的效率 44 | 45 |
46 | 在组合对象中,hide和show方法先对本层次的一个样式属性进行设置,然后将调用传递给所有子对象。这种做法慎密但效率不高,更好的做法是将这些方法作为沿职责链传递的请求实现,hide请求根本不用传递,show则总是需要传递。
47 | 优化一:从hide方法中删除将方法调用传递给子节点的那部分代码
48 | 6.2、为图片添加标签
49 | 添加标签,提供搜索功能。
50 | 通过职责链模式在组合对象中查找包含某个标签的所有对象,分别在组合对象和也对象中添加getPhotosWithTag()方法
51 | 7、职责链模式之利
52 | 可以动态选择由哪个对象处理请求。可以比试图在开发期间静态指定处理请求的对象高效得多。可以使用这种模式消除发出请求的对象与处理请求的对象之间的耦合。
53 | 在已有现成的链或层次体系的情况下,职责链模式更加有效。与组合模式的结合使用就属于这种情况。
54 | 8、职责链模式之弊
55 | 请求与具体的处理程序被隔离开啦,因此无法保证它一定会被处理,而不是径直从链尾离开。这种模式的接收者都是隐式的,因此无法得知如果请求能够得到处理的话具体将由哪个对象处理它。可以通过创建一个通用的catch-all接收者并将其添加到所有链的尾端来解决,但这个办法很繁琐,失去随时在链尾添加新环节的灵活性。
56 | 职责链与组合对象类的搭配使用可能有点令人困惑。组合模式期望组合对象节点完全可以与叶节点互换使用,而且客户代码看不出其中的差别,所有方法调用都被组合对象往层次体系的下层传递。而职责链模式引入后,有些方法会在组合对象进行处理,而不会进行往下传。 -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Command-Pattern/1 - StopAd and StartAd classes.js: -------------------------------------------------------------------------------- 1 | /* AdCommand interface. */ 2 | // 下面展示一个典型的命令类StartAd和StopAd,它们的构造函数由另一个对象adObject作为参数 3 | // 借助命令模式,可以实现用户界面对象与广告对象的隔离 4 | // 定义一个所有命令对象都必须实现的接口AdCommand 5 | var AdCommand = new Interface('AdCommand', ['execute']); 6 | 7 | /* StopAd command class. */ 8 | // 封装广告的StopAd方法的类 9 | var StopAd = function(adObject) { // implements AdCommand 10 | this.ad = adObject; 11 | }; 12 | // execute()方法调用adObject对象的某个方法 13 | StopAd.prototype.execute = function() { 14 | this.ad.stop(); 15 | }; 16 | 17 | /* StartAd command class. */ 18 | // 封装广告StartAd方法的类 19 | var StartAd = function(adObject) { // implements AdCommand 20 | this.ad = adObject; 21 | }; 22 | // execute()方法调用adObject对象的某个方法 23 | StartAd.prototype.execute = function() { 24 | this.ad.start(); 25 | }; 26 | // 现在有了两个可用在用户界面中的类,它们具有相同的接口,你不需要也不关心adObject方法的具体实现,只需要知道它实现了start()和stop()方法就可以了。 27 | 28 | /* Implementation code. */ 29 | 30 | var ads = getAds(); 31 | for(var i = 0, len = ads.length; i < len; i++) { 32 | // Create command objects for starting and stopping the ad. 33 | var startCommand = new StartAd(ads[i]); 34 | var stopCommand = new StopAd(ads[i]); 35 | 36 | // Create the UI elements that will execute the command on click. 37 | new UiButton('Start ' + ads[i].name, startCommand); 38 | new UiButton('Stop ' + ads[i].name, stopCommand); 39 | } 40 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Command-Pattern/2 - Commands using closures.js: -------------------------------------------------------------------------------- 1 | /* Commands using closures. */ 2 | // 这种方法不需要创建一个具有execute方法的对象,而是把想要执行的方法包装在闭包中。 3 | function makeStart(adObject) { 4 | return function() { 5 | adObject.start(); 6 | }; 7 | } 8 | function makeStop(adObject) { 9 | return function() { 10 | adObject.stop(); 11 | }; 12 | } 13 | 14 | /* Implementation code. */ 15 | 16 | var startCommand = makeStart(ads[i]); 17 | var stopCommand = makeStop(ads[i]); 18 | 19 | startCommand(); // Execute the functions directly instead of calling a method. 20 | stopCommand(); 21 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Command-Pattern/3 - Using interfaces with the command pattern.js: -------------------------------------------------------------------------------- 1 | /* Command interface. */ 2 | 3 | var Command = new Interface('Command', ['execute']); 4 | 5 | /* Checking the interface of a command object. */ 6 | 7 | // Ensure that the execute operation is defined. If not, a descriptive exception 8 | // will be thrown. 9 | // 用接口检查命令对象是否实现了正确的执行操作 10 | Interface.ensureImplements(someCommand, Command); 11 | 12 | // If no exception is thrown, you can safely invoke the execute operation. 13 | someCommand.execute(); 14 | 15 | 16 | /* Checking command functions. */ 17 | // 如果用闭包来创建命令函数,那么这种检查更简单,只需要检查该命令是否为函数即可。 18 | if(typeof someCommand != 'function') { 19 | throw new Error('Command isn't a function'); 20 | } 21 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Command-Pattern/4 - Types of commands.js: -------------------------------------------------------------------------------- 1 | /* SimpleCommand, a loosely coupled, simple command class. */ 2 | // 简单命令对象 3 | // 这种情况下的命令对象所起的作用只不过是把现有接受者的操作与调用者绑定在一起 4 | // 它们与客户、接受者和调用者之间只是松散地耦合在一起 5 | var SimpleCommand = function(receiver) { // implements Command 6 | this.receiver = receiver; 7 | }; 8 | SimpleCommand.prototype.execute = function() { 9 | this.receiver.action(); 10 | }; 11 | 12 | /* ComplexCommand, a tightly coupled, complex command class. */ 13 | // 复杂命令对象 14 | // 封装这一套复杂指令的命令对象,这种命令对象实际上没有接受者,因为它自己提供了操作的具体实现。 15 | var ComplexCommand = function() { // implements Command 16 | this.logger = new Logger(); 17 | this.xhrHandler = XhrManager.createXhrHandler(); 18 | this.parameters = {}; 19 | }; 20 | ComplexCommand.prototype = { 21 | setParameter: function(key, value) { 22 | this.parameters[key] = value; 23 | }, 24 | // 把所有的实现相关操作的代码都包含在其内部,而不是把操作委托给接受者实现。 25 | execute: function() { 26 | this.logger.log('Executing command'); 27 | var postArray = []; 28 | for(var key in this.parameters) { 29 | postArray.push(key + '=' + this.parameters[key]); 30 | } 31 | var postString = postArray.join('&'); 32 | this.xhrHandler.request( 33 | 'POST', 34 | 'script.php', 35 | function() {}, 36 | postString 37 | ); 38 | } 39 | }; 40 | 41 | /* GreyAreaCommand, somewhere between simple and complex. */ 42 | // 灰色地带,有些命令对象不但封装了接收者操作,而且其execute方法也具有一些实现代码 43 | var GreyAreaCommand = function(recevier) { // implements Command 44 | this.logger = new Logger(); 45 | this.receiver = receiver; 46 | }; 47 | GreyAreaCommand.prototype.execute = function() { 48 | this.logger.log('Executing command'); 49 | this.receiver.prepareAction(); 50 | this.receiver.action(); 51 | }; 52 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Command-Pattern/6 - Undo with reversible commands.js: -------------------------------------------------------------------------------- 1 | /* ReversibleCommand interface. */ 2 | // 创建命令接口,包含undo操作 3 | var ReversibleCommand = new Interface('ReversibleCommand', ['execute', 'undo']); 4 | 5 | /* Movement commands. */ 6 | // 创建一些移动指令 7 | var MoveUp = function(cursor) { // implements ReversibleCommand 8 | this.cursor = cursor; 9 | }; 10 | MoveUp.prototype = { 11 | execute: function() { 12 | cursor.move(0, -10); 13 | }, 14 | undo: function() { 15 | cursor.move(0, 10); 16 | } 17 | }; 18 | 19 | var MoveDown = function(cursor) { // implements ReversibleCommand 20 | this.cursor = cursor; 21 | }; 22 | MoveDown.prototype = { 23 | execute: function() { 24 | cursor.move(0, 10); 25 | }, 26 | undo: function() { 27 | cursor.move(0, -10); 28 | } 29 | }; 30 | 31 | var MoveLeft = function(cursor) { // implements ReversibleCommand 32 | this.cursor = cursor; 33 | }; 34 | MoveLeft.prototype = { 35 | execute: function() { 36 | cursor.move(-10, 0); 37 | }, 38 | undo: function() { 39 | cursor.move(10, 0); 40 | } 41 | }; 42 | 43 | var MoveRight = function(cursor) { // implements ReversibleCommand 44 | this.cursor = cursor; 45 | }; 46 | MoveRight.prototype = { 47 | execute: function() { 48 | cursor.move(10, 0); 49 | }, 50 | undo: function() { 51 | cursor.move(-10, 0); 52 | } 53 | }; 54 | 55 | /* Cursor class. */ 56 | 57 | var Cursor = function(width, height, parent) { 58 | this.width = width; 59 | this.height = height; 60 | this.position = { x: width / 2, y: height / 2 }; 61 | 62 | this.canvas = document.createElement('canvas'); 63 | this.canvas.width = this.width; 64 | this.canvas.height = this.height; 65 | parent.appendChild(this.canvas); 66 | 67 | this.ctx = this.canvas.getContext('2d'); 68 | this.ctx.fillStyle = '#cc0000'; 69 | this.move(0, 0); 70 | }; 71 | Cursor.prototype.move = function(x, y) { 72 | this.position.x += x; 73 | this.position.y += y; 74 | 75 | this.ctx.clearRect(0, 0, this.width, this.height); 76 | this.ctx.fillRect(this.position.x, this.position.y, 3, 3); 77 | }; 78 | 79 | /* UndoDecorator class. */ 80 | // 下面的装饰者的作用是在执行一个命令之前先将其压栈 81 | var UndoDecorator = function(command, undoStack) { // implements ReversibleCommand 82 | this.command = command; 83 | this.undoStack = undoStack; 84 | }; 85 | UndoDecorator.prototype = { 86 | execute: function() { 87 | this.undoStack.push(this.command); 88 | this.command.execute(); 89 | }, 90 | undo: function() { 91 | this.command.undo(); 92 | } 93 | }; 94 | 95 | /* CommandButton class. */ 96 | // 用户界面类,负责生成必要的HTML元素,并且为其注册click事件监听器,这些监听器要么调用execute方法要么调用undo方法 97 | var CommandButton = function(label, command, parent) { 98 | Interface.ensureImplements(command, ReversibleCommand); 99 | this.element = document.createElement('button'); 100 | this.element.innerHTML = label; 101 | parent.appendChild(this.element); 102 | 103 | addEvent(this.element, 'click', function() { 104 | command.execute(); 105 | }); 106 | }; 107 | 108 | /* UndoButton class. */ 109 | 110 | var UndoButton = function(label, parent, undoStack) { 111 | this.element = document.createElement('button'); 112 | this.element.innerHTML = label; 113 | parent.appendChild(this.element); 114 | 115 | addEvent(this.element, 'click', function() { 116 | if(undoStack.length === 0) return; 117 | var lastCommand = undoStack.pop(); 118 | lastCommand.undo(); 119 | }); 120 | }; 121 | 122 | /* Implementation code. */ 123 | // 使用 124 | // 像UndoDecorator类一样,UndoButton类的构造函数也需要把命令栈作为参数传入。这个栈其实就是一个数组。 125 | // 调用经UndoDecorator对象装饰过的命令对象的execute方法时这个命令对象会被压入栈。 126 | // 为了执行取消操作,取消按钮会从命令栈中弹出最近的命令并调用其undo方法。这将逆转刚执行过的操作。 127 | var body = document.getElementsByTagName('body')[0]; 128 | var cursor = new Cursor(400, 400, body); 129 | var undoStack = []; 130 | 131 | var upCommand = new UndoDecorator(new MoveUp(cursor), undoStack); 132 | var downCommand = new UndoDecorator(new MoveDown(cursor), undoStack); 133 | var leftCommand = new UndoDecorator(new MoveLeft(cursor), undoStack); 134 | var rightCommand = new UndoDecorator(new MoveRight(cursor), undoStack); 135 | 136 | var upButton = new CommandButton('Up', upCommand, body); 137 | var downButton = new CommandButton('Down', downCommand, body); 138 | var leftButton = new CommandButton('Left', leftCommand, body); 139 | var rightButton = new CommandButton('Right', rightCommand, body); 140 | var undoButton = new UndoButton('Undo', body, undoStack); 141 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Command-Pattern/7 - Undo with command logging.js: -------------------------------------------------------------------------------- 1 | /* Movement commands. */ 2 | // 删除所有命令对象的undo方法,因为命令对象代表的操作现在不再可逆 3 | var MoveUp = function(cursor) { // implements Command 4 | this.cursor = cursor; 5 | }; 6 | MoveUp.prototype = { 7 | execute: function() { 8 | cursor.move(0, -10); 9 | } 10 | }; 11 | 12 | /* Cursor class, with an internal command stack. */ 13 | // 原来用来记录命令的栈undoStack变成了该类的内部属性commandStack 14 | var Cursor = function(width, height, parent) { 15 | this.width = width; 16 | this.height = height; 17 | this.commandStack = []; 18 | 19 | this.canvas = document.createElement('canvas'); 20 | this.canvas.width = this.width; 21 | this.canvas.height = this.height; 22 | parent.appendChild(this.canvas); 23 | 24 | this.ctx = this.canvas.getContext('2d'); 25 | this.ctx.strokeStyle = '#cc0000'; 26 | this.move(0, 0); 27 | }; 28 | Cursor.prototype = { 29 | move: function(x, y) { 30 | var that = this; 31 | this.commandStack.push(function() { that.lineTo(x, y); }); 32 | this.executeCommands(); 33 | }, 34 | lineTo: function(x, y) { 35 | this.position.x += x; 36 | this.position.y += y; 37 | this.ctx.lineTo(this.position.x, this.position.y); 38 | }, 39 | executeCommands: function() { 40 | this.position = { x: this.width / 2, y: this.height / 2 }; 41 | this.ctx.clearRect(0, 0, this.width, this.height); // Clear the canvas. 42 | this.ctx.beginPath(); 43 | this.ctx.moveTo(this.position.x, this.position.y); 44 | for(var i = 0, len = this.commandStack.length; i < len; i++) { 45 | this.commandStack[i](); 46 | } 47 | this.ctx.stroke(); 48 | }, 49 | undo: function() { 50 | this.commandStack.pop(); 51 | this.executeCommands(); 52 | } 53 | }; 54 | 55 | /* UndoButton class. */ 56 | // 用户界面类 57 | var UndoButton = function(label, parent, cursor) { 58 | this.element = document.createElement('button'); 59 | this.element.innerHTML = label; 60 | parent.appendChild(this.element); 61 | 62 | addEvent(this.element, 'click', function() { 63 | cursor.undo(); 64 | }); 65 | }; 66 | 67 | /* Implementation code. */ 68 | 69 | var body = document.getElementsByTagName('body')[0]; 70 | var cursor = new Cursor(400, 400, body); 71 | 72 | var upCommand = new MoveUp(cursor); 73 | var downCommand = new MoveDown(cursor); 74 | var leftCommand = new MoveLeft(cursor); 75 | var rightCommand = new MoveRight(cursor); 76 | 77 | var upButton = new CommandButton('Up', upCommand, body); 78 | var downButton = new CommandButton('Down', downCommand, body); 79 | var leftButton = new CommandButton('Left', leftCommand, body); 80 | var rightButton = new CommandButton('Right', rightCommand, body); 81 | var undoButton = new UndoButton('Undo', body, cursor); 82 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Command-Pattern/README.md: -------------------------------------------------------------------------------- 1 |

命令模式

2 | 在软件系统中,**“行为请求者”**与**“行为实现者”**通常呈现一种**“紧耦合”**。但在某些场合,比如要对行为进行“记录、撤销/重做、事务”等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将“**行为请求者**”与**“行为实现者**”解耦?将**一组行为抽象为对象,实现二者之间的松耦合**。这就是命令模式(Command Pattern)
3 | >[计算器Demo](https://github.com/wchaowu/javascript-code/blob/master/JavaScript-Design-Patterns/The-Command-Pattern/Calculator.js"计算器Demo") 4 | > 5 | ###1、命令的结构### 6 | 下面展示一个典型的命令类StartAd和StopAd,它们的构造函数由另一个对象adObject作为参数,而它们实现的execute()方法则要调用该对象的某个方法。现在有了两个可用在用户界面中的类,它们具有相同的接口,你不需要也不关心adObject方法的具体实现,只需要知道它实现了start()和stop()方法就可以了。借助命令模式,可以实现用户界面对象与广告对象的隔离。
7 | 8 | example 9 | 10 | ####1.1、用闭包创建命令对象 11 | 这种方法不需要创建一个具有execute方法的对象,而是把想要执行的方法包装在闭包中。这样做省却了作用域和this关键字的绑定这方面的烦恼。
12 |
13 | example 14 | 15 |
16 | 这些命令函数可以像命令对象一样四处传递,并且在需要的时候执行。它们是正式的对象类的简单替代品,但并不适用于需要多个命令方法的场合,比如后面实现取消功能的那个示例。
17 |

1.2、客户、调用者和接受者


18 | 客户:创建命令,StartAd StopAd
19 | 调用者:执行命令,UiButton
20 | 接受者:在命令执行时执行相应操作,adObject
21 |

1.3、在命令模式中使用接口


22 | 可以使用接口检查命令对象是否实现了正确的执行操作。果用闭包来创建命令函数,那么这种检查更简单,只需要检查该命令是否为函数即可。
23 | 24 | example 25 | 26 |
27 |

2、命令对象的类型


28 | 简单命令对象:这种情况下的命令对象所起的作用只不过是把现有接受者的操作与调用者绑定在一起。它们与客户、接受者和调用者之间只是松散地耦合在一起。
29 | 复杂命令对象:封装这一套复杂指令的命令对象,这种命令对象实际上没有接受者,因为它自己提供了操作的具体实现。
30 | 灰色地带:有些命令对象不但封装了接收者操作,而且其execute方法也具有一些实现代码。
31 | 32 | example 33 | 34 |
35 | 简单命令对象一般用来消除两个对象(接收者和调用者)之间的耦合,而复杂命令对象则一般用来封装不可分的(atomic)或事务性(transactional)的指令。本章着重讨论简单命令对象。
36 |

3、示例:菜单项


37 | 38 | example 39 | 40 |
41 | 命令模式非常适合用来构建用户界面,这是因为这种模式可以把执行具体工作的类与生成用户界面的类隔离开来。在这种模式中,甚至可以让多个用户界面元素公用同一个接受者或命令对象。
42 |

4、示例:取消操作和命令日志


43 | 像UndoDecorator类一样,UndoButton类的构造函数也需要把命令栈作为参数传入。这个栈其实就是一个数组。调用经UndoDecorator对象装饰过的命令对象的execute方法时这个命令对象会被压入栈。为了执行取消操作,取消按钮会从命令栈中弹出最近的命令并调用其undo方法。这将逆转刚执行过的操作。
44 | 创建命令接口和对象,使用数组保存操作,通过装饰者加入保存和获取操作命令的方法。
45 | 46 | example 47 | 48 |
49 |

4.1、使用命令日志实现不可逆操作的取消


50 | 前面讨论的取消擦偶偶针对的都是移动指针这类容易逆转的操作。对于那些本质上不可逆的操作,要想实现不受限制的取消就困难的多。取消这种操作的唯一办法就是清除状态,然后把之前执行过的操作依次重做一遍。即把所有执行过的命令记录在栈中,要想取消一个操作,需要做的就是从栈中弹出最近那个命令并弃之不用,然后清理画布并从头开始重新执行记录下来的所有命令。
51 | 52 | example 53 | 54 |
55 |

4.2、用于崩溃恢复的命令日志


56 | 命令日志的一个有趣用途是在程序崩溃后恢复其状态。可以用XHR把经过序列化处理的命令记录到服务器上。用户下次访问该网页的时候,系统可以找出这些命令并用其将画布上的图案精确恢复到浏览器关闭时的状态。
57 |

5、命令模式的适用场合


58 | 命令模式主要用途是把调用对象(用户界面、API和代理等)与实现操作的对象隔离开。最能体现其效用的还是那种需要对操作进行规范化处理的场合。有了这种规范化处理,一个类或调用者也能调用多种方法,而且不需要先为此了解哪些方法。许多用户界面元素都非常符合这样的特征,比如前面例子中的那种菜单。命令模式可以彻底消除用户界面元素与负责实际工作的类之间的耦合。
59 | 可以受益于命令模式的还有其他一些特别场合。这种模式可以用来封装用于XHR调用或其他延迟性调用场合的回调函数。用一个回调函数命令代替回调函数,可以把多条函数调用封装为一个单位。有了命令对象的帮助,在应用程序中实现取消机制几乎是一件不足挂齿的事。
60 |

6、命令模式之利


61 | 如果运用得当,可以提高程序的模块化程度和灵活性;
62 | 有了它,实现取消和状态恢复等复杂的有用特性非常容易。
63 | 命令对象具有的特性比普通方法引用多得多。它可以被参数化处理,而且将那些参数保存起来以供多次调用。你可以为它定义的方法不只有execute,还可以是undo等别的方法,这样一来同样的操作就可以用不同的方式执行。还可以定义与操作相关的元数据,这些元数据可用于对象内省(introspection)或事件日志等目的。命令对象是经过封装的方法调用,它因为这种封装而拥有了方法调用本身所不具备的许多特性。
64 |

7、命令模式之弊


65 | 如果一个命令对象只包装了一个方法调用,而且其唯一目的就是这层对象包装的话,那么这种做法是一种浪费。如果你不需要命令模式给予的任何额外特性,也不需要具有一致接口的类所带来的模块性,那么直接使用方法引用而不是完整的命令对象也许更恰当。如果命令对象是运行期间动态创建的而你又难以确定它包含着什么操作的话,情况尤其如此。命令对象都具有同样的接口并且可以所有更好这一点是把双刃剑。在调试复杂的应用程序时它们很难跟踪。 -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Composite-Pattern/2 - Adding operations to FormItem.js: -------------------------------------------------------------------------------- 1 | // 向FormItem添加操作 2 | var FormItem = new Interface('FormItem', ['save', 'restore']); 3 | // 在超类Field中添加实现,以供其所有子类直接继承使用 4 | Field.prototype.restore = function() { 5 | this.element.value = getCookie(this.id); 6 | }; 7 | // 为组合表单添加同样的操作 8 | CompositeForm.prototype.restore = function() { 9 | for(var i = 0, len = this.formComponents.length; i < len; i++) { 10 | this.formComponents[i].restore(); 11 | } 12 | }; 13 | 14 | addEvent(window, 'load', contactForm.restore); 15 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Composite-Pattern/3 - Adding classes to the hierarchy.js: -------------------------------------------------------------------------------- 1 | /* CompositeFieldset class. */ 2 | // 创建一个组合域集 3 | var CompositeFieldset = function(id, legendText) { // implements Composite, FormItem 4 | this.components = {}; 5 | 6 | this.element = document.createElement('fieldset'); 7 | this.element.id = id; 8 | 9 | if(legendText) { // Create a legend if the optional second 10 | // argument is set. 11 | this.legend = document.createElement('legend'); 12 | this.legend.appendChild(document.createTextNode(legendText)); 13 | this.element.appendChild(this.legend); 14 | } 15 | }; 16 | // 组合域集的add方法,添加元素 17 | CompositeFieldset.prototype.add = function(child) { 18 | Interface.ensureImplements(child, Composite, FormItem); 19 | this.components[child.getElement().id] = child; 20 | this.element.appendChild(child.getElement()); 21 | }; 22 | // 组合域集的remove方法,移除元素 23 | CompositeFieldset.prototype.remove = function(child) { 24 | delete this.components[child.getElement().id]; 25 | }; 26 | // 组合域集的getChild方法,获取对应ID的元素 27 | CompositeFieldset.prototype.getChild = function(id) { 28 | if(this.components[id] != undefined) { 29 | return this.components[id]; 30 | } 31 | else { 32 | return null; 33 | } 34 | }; 35 | // 组合域集的save方法,依次保存每一个子元素,调用了子元素的save方法 36 | CompositeFieldset.prototype.save = function() { 37 | for(var id in this.components) { 38 | if(!this.components.hasOwnProperty(id)) continue; 39 | this.components[id].save(); 40 | } 41 | }; 42 | // 组合域集的restore方法,依次还原每一个子元素,调用了子元素的restore方法 43 | CompositeFieldset.prototype.restore = function() { 44 | for(var id in this.components) { 45 | if(!this.components.hasOwnProperty(id)) continue; 46 | this.components[id].restore(); 47 | } 48 | }; 49 | // 获取组合域集的dom节点 50 | CompositeFieldset.prototype.getElement = function() { 51 | return this.element; 52 | }; 53 | 54 | 55 | /* Usage. */ 56 | 57 | var contactForm = new CompositeForm('contact-form', 'POST', 'contact.php'); 58 | 59 | var nameFieldset = new CompositeFieldset('name-fieldset'); 60 | nameFieldset.add(new InputField('first-name', 'First Name')); 61 | nameFieldset.add(new InputField('last-name', 'Last Name')); 62 | contactForm.add(nameFieldset); 63 | 64 | var addressFieldset = new CompositeFieldset('address-fieldset'); 65 | addressFieldset.add(new InputField('address', 'Address')); 66 | addressFieldset.add(new InputField('city', 'City')); 67 | addressFieldset.add(new SelectField('state', 'State', stateArray)); 68 | addressFieldset.add(new InputField('zip', 'Zip')); 69 | contactForm.add(addressFieldset); 70 | 71 | contactForm.add(new TextareaField('comments', 'Comments')); 72 | 73 | body.appendChild(contactForm.getElement()); 74 | 75 | addEvent(window, 'unload', contactForm.save); 76 | addEvent(window, 'load', contactForm.restore); 77 | 78 | addEvent('save-button', 'click', nameFieldset.save); 79 | addEvent('restore-button', 'click', nameFieldset.restore); 80 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Composite-Pattern/4 - Image gallery example.js: -------------------------------------------------------------------------------- 1 | // Interfaces. 2 | // 创建图片库和图片接口 3 | var Composite = new Interface('Composite', ['add', 'remove', 'getChild']); 4 | var GalleryItem = new Interface('GalleryItem', ['hide', 'show']); 5 | 6 | 7 | // DynamicGallery class. 8 | // 创建动态图片库类 9 | var DynamicGallery = function(id) { // implements Composite, GalleryItem 10 | this.children = []; 11 | 12 | this.element = document.createElement('div'); 13 | this.element.id = id; 14 | this.element.className = 'dynamic-gallery'; 15 | } 16 | 17 | DynamicGallery.prototype = { 18 | 19 | // Implement the Composite interface. 20 | // 添加子元素 21 | add: function(child) { 22 | Interface.ensureImplements(child, Composite, GalleryItem); 23 | this.children.push(child); 24 | this.element.appendChild(child.getElement()); 25 | }, 26 | // 移除子元素 27 | remove: function(child) { 28 | for(var node, i = 0; node = this.getChild(i); i++) { 29 | if(node == child) { 30 | this.formComponents[i].splice(i, 1); 31 | break; 32 | } 33 | } 34 | this.element.removeChild(child.getElement()); 35 | }, 36 | // 获取子元素 37 | getChild: function(i) { 38 | return this.children[i]; 39 | }, 40 | 41 | // Implement the GalleryItem interface. 42 | // 隐藏该图片库的所有子元素 43 | hide: function() { 44 | for(var node, i = 0; node = this.getChild(i); i++) { 45 | node.hide(); 46 | } 47 | this.element.style.display = 'none'; 48 | }, 49 | // 显示该图片库的所有子元素 50 | show: function() { 51 | this.element.style.display = 'block'; 52 | for(var node, i = 0; node = this.getChild(i); i++) { 53 | node.show(); 54 | } 55 | }, 56 | 57 | // Helper methods. 58 | // 获取图片库的dom节点 59 | getElement: function() { 60 | return this.element; 61 | } 62 | }; 63 | 64 | // GalleryImage class. 65 | // 图片类 66 | var GalleryImage = function(src) { // implements Composite, GalleryItem 67 | this.element = document.createElement('img'); 68 | this.element.className = 'gallery-image'; 69 | this.element.src = src; 70 | } 71 | 72 | GalleryImage.prototype = { 73 | 74 | // Implement the Composite interface. 75 | 76 | add: function() {}, // This is a leaf node, so we don't 77 | remove: function() {}, // implement these methods, we just 78 | getChild: function() {}, // define them. 79 | 80 | // Implement the GalleryItem interface. 81 | 82 | hide: function() { 83 | this.element.style.display = 'none'; 84 | }, 85 | show: function() { 86 | this.element.style.display = ''; // Restore the display attribute to its 87 | // previous setting. 88 | }, 89 | 90 | // Helper methods. 91 | 92 | getElement: function() { 93 | return this.element; 94 | } 95 | }; 96 | 97 | // Usage. 98 | 99 | var topGallery = new DynamicGallery('top-gallery'); 100 | 101 | topGallery.add(new GalleryImage('/img/image-1.jpg')); 102 | topGallery.add(new GalleryImage('/img/image-2.jpg')); 103 | topGallery.add(new GalleryImage('/img/image-3.jpg')); 104 | 105 | var vacationPhotos = new DynamicGallery('vacation-photos'); 106 | 107 | for(var i = 0; i < 30; i++) { 108 | vacationPhotos.add(new GalleryImage('/img/vac/image-' + i + '.jpg')); 109 | } 110 | 111 | topGallery.add(vacationPhotos); 112 | topGallery.show(); // Show the main gallery, 113 | vacationPhotos.hide(); // but hide the vacation gallery. 114 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Composite-Pattern/README.md: -------------------------------------------------------------------------------- 1 | 组合模式:
2 | 1、表单验证
3 | topForm对象将在其所有子对象上递归调用save方法,实际上的save操作只会发生在底层的叶对象上。组合对象只起到一个传递调用的作用。 4 |
5 | example 6 |
7 | 1、向FormItem添加操作:example
8 | 2、向层次体系中添加类:
9 | 我们可以把域组织在域集中,每一个域集都是一个实现了FormItem接口的组合对象,在域集上调用restore将导致在其所有子对象上调用restore。example
10 | 3、示例:图片库
11 | 创建一个图片库,有选择地隐藏或显示图片库的特定部分。这可能是单独的图片,也可能是图片库。example
12 | 4、组合模式之利:
13 | 简单的操作也能产生复杂的结果,只需对最顶层的对象执行操作,让每一个子对象自己传递这个操作即可。这对于那些再三执行的操作尤其有用。
14 | 在组合模式中,各个对象之间的耦合非常松散。只要它们实现了同样的接口那么改变它们的位置或互换它们只是举手之劳。着促进了代码的重用,也有利于代码重构。
15 | 每当对顶层组合对象执行一个操作时,实际上是在对整个结构进行深度优先的搜索以查找节点,而创建组合对象的程序员对这些细节一无所知。在这个层次体系中添加、删除和查找节点都非常容易。
16 | 5、组合模式之弊:
17 | 组合对象的易用性可能掩盖了它所支持的每一种操作的代价。由于组合对象调用的任何操作都会被传递到它的所有子对象如果这个层次体系很大的话,系统的性能将会受到影响。组合模式的正常运作需要用到某种形式的接口。
18 | 组合对象和节点类被用作HTML元素的包装工具时,组合对象必须遵守HTML的使用规则。例如,表格就很难转化为一个组合对象。
19 | 接口检查越严格,组合对象类也就越可靠。 -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Decorator-Pattern/1 - Structure of the decorator.js: -------------------------------------------------------------------------------- 1 | /* The Bicycle interface. */ 2 | // 自行车接口 3 | var Bicycle = new Interface('Bicycle', ['assemble', 'wash', 'ride', 'repair', 4 | 'getPrice']); 5 | 6 | /* The AcmeComfortCruiser class. */ 7 | // 创建一辆自行车 8 | var AcmeComfortCruiser = function() { // implements Bicycle 9 | //todo 10 | }; 11 | AcmeComfortCruiser.prototype = { 12 | assemble: function() { 13 | //todo 14 | }, 15 | wash: function() { 16 | //todo 17 | }, 18 | ride: function() { 19 | //todo 20 | }, 21 | repair: function() { 22 | //todo 23 | }, 24 | getPrice: function() { 25 | return 399.00; 26 | } 27 | }; 28 | 29 | /* The BicycleDecorator abstract decorator class. */ 30 | // 创建装饰者的抽象类,继承者Bicycle接口 31 | var BicycleDecorator = function(bicycle) { // implements Bicycle 32 | Interface.ensureImplements(bicycle, Bicycle); 33 | this.bicycle = bicycle; 34 | } 35 | BicycleDecorator.prototype = { 36 | assemble: function() { 37 | return this.bicycle.assemble(); 38 | }, 39 | wash: function() { 40 | return this.bicycle.wash(); 41 | }, 42 | ride: function() { 43 | return this.bicycle.ride(); 44 | }, 45 | repair: function() { 46 | return this.bicycle.repair(); 47 | }, 48 | getPrice: function() { 49 | return this.bicycle.getPrice(); 50 | } 51 | }; 52 | 53 | /* HeadlightDecorator class. */ 54 | // 创建一个装饰者 55 | var HeadlightDecorator = function(bicycle) { // implements Bicycle 56 | this.superclass.constructor(bicycle); // Call the superclass's constructor. 57 | } 58 | extend(HeadlightDecorator, BicycleDecorator); // Extend the superclass. 59 | HeadlightDecorator.prototype.assemble = function() { 60 | return this.bicycle.assemble() + ' Attach headlight to handlebars.'; 61 | }; 62 | HeadlightDecorator.prototype.getPrice = function() { 63 | return this.bicycle.getPrice() + 15.00; 64 | }; 65 | 66 | 67 | /* TaillightDecorator class. */ 68 | // 创建一个装饰者 69 | var TaillightDecorator = function(bicycle) { // implements Bicycle 70 | this.superclass.constructor(bicycle); // Call the superclass's constructor. 71 | } 72 | extend(TaillightDecorator, BicycleDecorator); // Extend the superclass. 73 | TaillightDecorator.prototype.assemble = function() { 74 | return this.bicycle.assemble() + ' Attach taillight to the seat post.'; 75 | }; 76 | TaillightDecorator.prototype.getPrice = function() { 77 | return this.bicycle.getPrice() + 9.00; 78 | }; 79 | 80 | 81 | /* Usage. */ 82 | 83 | var myBicycle = new AcmeComfortCruiser(); // Instantiate the bicycle. 84 | alert(myBicycle.getPrice()); // Returns 399.00 85 | 86 | myBicycle = new TaillightDecorator(myBicycle); // Decorate the bicycle object 87 | // with a taillight. 88 | alert(myBicycle.getPrice()); // Now returns 408.00 89 | 90 | myBicycle = new HeadlightDecorator(myBicycle); // Decorate the bicycle object 91 | // again, now with a headlight. 92 | alert(myBicycle.getPrice()); // Now returns 423.00 93 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Decorator-Pattern/3 - The role of the factory.js: -------------------------------------------------------------------------------- 1 | /* Original AcmeBicycleShop factory class. */ 2 | // 原来的AcmBicycleShop类如下 3 | var AcmeBicycleShop = function() {}; 4 | extend(AcmeBicycleShop, BicycleShop); 5 | AcmeBicycleShop.prototype.createBicycle = function(model) { 6 | var bicycle; 7 | 8 | switch(model) { 9 | case 'The Speedster': 10 | bicycle = new AcmeSpeedster(); 11 | break; 12 | case 'The Lowrider': 13 | bicycle = new AcmeLowrider(); 14 | break; 15 | case 'The Flatlander': 16 | bicycle = new AcmeFlatlander(); 17 | break; 18 | case 'The Comfort Cruiser': 19 | default: 20 | bicycle = new AcmeComfortCruiser(); 21 | } 22 | 23 | Interface.ensureImplements(bicycle, Bicycle); 24 | return bicycle; 25 | }; 26 | 27 | /* AcmeBicycleShop factory class, with decorators. */ 28 | /** 29 | * 这个类的改进版允许用户指定想为自行车配件的选件。 30 | * 在这里使用工厂模式可以统揽各种类(既包括自行车类也包括装饰者类) 31 | * 把所有这些信息保存在一个地方,用户就可以把实际的类名与客户代码隔离开, 32 | * 这样以后添加新类或修改现有类也就更容易。 33 | */ 34 | var AcmeBicycleShop = function() {}; 35 | extend(AcmeBicycleShop, BicycleShop); 36 | AcmeBicycleShop.prototype.createBicycle = function(model, options) { 37 | // Instantiate the bicycle object. 38 | var bicycle = new AcmeBicycleShop.models[model](); 39 | 40 | // Iterate through the options and instantiate decorators. 41 | for(var i = 0, len = options.length; i < len; i++) { 42 | var decorator = AcmeBicycleShop.options[options[i].name]; 43 | if(typeof decorator !== 'function') { 44 | throw new Error('Decorator ' + options[i].name + ' not found.'); 45 | } 46 | var argument = options[i].arg; 47 | bicycle = new decorator(bicycle, argument); 48 | } 49 | 50 | // Check the interface and return the finished object. 51 | Interface.ensureImplements(bicycle, Bicycle); 52 | return bicycle; 53 | }; 54 | 55 | // Model name to class name mapping. 56 | AcmeBicycleShop.models = { 57 | 'The Speedster': AcmeSpeedster, 58 | 'The Lowrider': AcmeLowrider, 59 | 'The Flatlander': AcmeFlatlander, 60 | 'The Comfort Cruiser': AcmeComfortCruiser 61 | }; 62 | 63 | // Option name to decorator class name mapping. 64 | AcmeBicycleShop.options = { 65 | 'headlight': HeadlightDecorator, 66 | 'taillight': TaillightDecorator, 67 | 'bell': BellDecorator, 68 | 'basket': BasketDecorator, 69 | 'color': FrameColorDecorator, 70 | 'lifetime warranty': LifetimeWarrantyDecorator, 71 | 'timed warranty': TimedWarrantyDecorator 72 | }; 73 | 74 | // 创建装饰的自行车对象的两种不同做法 75 | // 第一种做法不使用工厂,与客户代码紧密耦合在一起的类不下5个。 76 | var myBicycle = new AcmeSpeedster(); 77 | myBicycle = new FrameColorDecorator(myBicycle, 'blue'); 78 | myBicycle = new HeadlightDecorator(myBicycle); 79 | myBicycle = new TaillightDecorator(myBicycle); 80 | myBicycle = new TimedWarrantyDecorator(myBicycle, 2); 81 | // 第二种方法使用了工厂,与客户代码耦合在一起的只有一个类,即那个工厂本身。 82 | var alecsCruisers = new AcmeBicycleShop(); 83 | var myBicycle = alecsCruisers.createBicycle('The Speedster', [ 84 | { name: 'color', arg: 'blue' }, 85 | { name: 'headlight' }, 86 | { name: 'taillight' }, 87 | { name: 'timed warranty', arg: 2 } 88 | ]); 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Decorator-Pattern/4 - Function decorators.js: -------------------------------------------------------------------------------- 1 | // 一个简单的函数装饰者的例子 2 | function upperCaseDecorator(func) { 3 | return function() { 4 | return func.apply(this, arguments).toUpperCase(); 5 | } 6 | } 7 | // 这个装饰者可以用来创建新函数 8 | // 如下,先定义一个普通函数,然后将其装饰为一个新函数 9 | function getDate() { 10 | return (new Date()).toString(); 11 | } 12 | getDateCaps = upperCaseDecorator(getDate); 13 | 14 | alert(getDate()); // Returns Wed Sep 26 2007 20:11:02 GMT-0700 (PDT) 15 | alert(getDateCaps()); // Returns WED SEP 26 2007 20:11:02 GMT-0700 (PDT) 16 | 17 | BellDecorator.prototype.ringBellLoudly = 18 | upperCaseDecorator(BellDecorator.prototype.ringBell); 19 | 20 | var myBicycle = new AcmeComfortCruiser(); 21 | myBicycle = new BellDecorator(myBicycle); 22 | 23 | alert(myBicycle.ringBell()); // Returns 'Bell rung.' 24 | alert(myBicycle.ringBellLoudly()); // Returns 'BELL RUNG.' 25 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Decorator-Pattern/5 - Method profiler.js: -------------------------------------------------------------------------------- 1 | /* ListBuilder class. */ 2 | // 一个测试的样例类,其唯一的目的是在网页上创建一个有序列表 3 | var ListBuilder = function(parent, listLength) { 4 | this.parentEl = $(parent); 5 | this.listLength = listLength; 6 | }; 7 | ListBuilder.prototype = { 8 | buildList: function() { 9 | var list = document.createElement('ol'); 10 | this.parentEl.appendChild(list); 11 | 12 | for(var i = 0; i < this.listLength; i++) { 13 | var item = document.createElement('li'); 14 | list.appendChild(item); 15 | } 16 | } 17 | }; 18 | 19 | /* SimpleProfiler class. */ 20 | // 创建一个专用于这个ListBuilder类的装饰者,记录执行buildList方法所耗用的时间 21 | var SimpleProfiler = function(component) { 22 | this.component = component; 23 | }; 24 | SimpleProfiler.prototype = { 25 | buildList: function() { 26 | var startTime = new Date(); 27 | this.component.buildList(); 28 | var elapsedTime = (new Date()).getTime() - startTime.getTime(); 29 | console.log('buildList: ' + elapsedTime + ' ms'); 30 | } 31 | }; 32 | 33 | /* Usage. */ 34 | 35 | var list = new ListBuilder('list-container', 5000); // Instantiate the object. 36 | list = new SimpleProfiler(list); // Wrap the object in the decorator. 37 | list.buildList(); // Creates the list and displays "buildList: 298 ms". 38 | 39 | 40 | 41 | /* MethodProfiler class. */ 42 | // 对上面的装饰者进行通用化改造,使其可用于任何对象。 43 | var MethodProfiler = function(component) { 44 | this.component = component; 45 | this.timers = {}; 46 | 47 | for(var key in this.component) { 48 | // Ensure that the property is a function. 49 | if(typeof this.component[key] !== 'function') { 50 | continue; 51 | } 52 | 53 | // Add the method. 54 | var that = this; 55 | (function(methodName) { 56 | that[methodName] = function() { 57 | that.startTimer(methodName); 58 | var returnValue = that.component[methodName].apply(that.component, 59 | arguments); 60 | that.displayTime(methodName, that.getElapsedTime(methodName)); 61 | return returnValue; 62 | }; 63 | })(key); } 64 | }; 65 | MethodProfiler.prototype = { 66 | startTimer: function(methodName) { 67 | this.timers[methodName] = (new Date()).getTime(); 68 | }, 69 | getElapsedTime: function(methodName) { 70 | return (new Date()).getTime() - this.timers[methodName]; 71 | }, 72 | displayTime: function(methodName, time) { 73 | console.log(methodName + ': ' + time + ' ms'); 74 | } 75 | }; 76 | 77 | /* Usage. */ 78 | 79 | var list = new ListBuilder('list-container', 5000); 80 | list = new MethodProfiler(list); 81 | list.buildList('ol'); // Displays "buildList: 301 ms". 82 | list.buildList('ul'); // Displays "buildList: 287 ms". 83 | list.removeLists('ul'); // Displays "removeLists: 10 ms". 84 | list.removeLists('ol'); // Displays "removeLists: 12 ms". 85 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Decorator-Pattern/README.md: -------------------------------------------------------------------------------- 1 |

装饰者模式

2 | >这个模式就是为对象增加功能(或方法)。 3 | 4 | 动态地给一个对象添加一些额外的职责。就扩展功能而言,它比生成子类方式更为灵活。 5 | 实例:创建自行车的装饰者
6 | 1、接口在装饰者模式中的角色:
7 | 装饰者模式颇多得益于接口的使用。装饰者最重要的特点之一
8 | 就是它可以用来替代其组件。
9 | 接口说明了装饰者必须实现哪些方法,有助于防止开发过程中的错误。
10 | 如果装饰者对象与其组件不能互换使用,它就丧失了其功用。这是装饰者模式的关键特点,要注意防止装饰者和组件出现的接口方法的差异。 11 | 这种模式的好处之一就是可以透明的用新对象装饰现有系统中的对象, 12 | 这并不会改变代码中的其他东西,只有装饰者和组件实现了同样的接口才能做到这一点。
13 | 2、装饰者模式与组合模式的比较:
14 | 相同点:
15 | 装饰者对象和组合对象都是用来包装别的对象(组合模式:子对象,装饰者模式:组件)
16 | 都与所包装的对象实现同样的接口并且会把任何方法调用传递给这些对象
17 | 不同点:
18 | 组合模式是一种结构型模式,用于把众多子对象组织为一个整体。通常并不需要修改方法调用,而只是将其沿组合对象与子对象的链向下传递,直到到达并落实在叶对象上。 19 | 装饰者模式也是一种结构型模式,但它并非用于组织对象, 20 | 而是用于在不修改现有对象或从派生子类的前提下为其增添职责。创建装饰者的目的就在于对方法进行修改,其做法是先传递方法调用,然后修改其返回结果。
21 | 组合对象不修改方法调用,着眼在于组织对象,而装饰者存在的唯一目的就是修改方法调用而不是组织子对象,因为子对象只有一个。
22 | 3、装饰者修改其组件的方式
23 | 在方法之后添加行为
24 | 在方法之前添加行为
25 | 替换方法
26 | 有时候为了实现新行为必须对方法进行整体替换。在此情况下,组件方法不会被调用(或虽然被调用但其返回值会被抛弃)。
27 | 添加新方法
28 | 与新方法装饰组件对象想必对现有方法进行修改更容易实施,而且更不容易出错,这是因为采用后一种做法时,被装饰的对象用起来与之前没什么不同,外围代码也就不需要修改了。
29 | 话虽如此,在装饰着中添加新方法又是也是为类增加功能的一种强有力的手段。我们可以用这种装饰者为自行车对象增添一个按铃方法。这是一个新功能,没有装饰者自行车就不可能执行这个任务。(见例子)
30 | 详细的例子:example
31 | 4、工厂的角色
32 | 如果必须确保按照某种特定顺序创建装饰者,那么可以为此使用工厂对象。实际上,不管顺序是否要紧,工厂都很适合于创建装饰对象。
33 | 用工厂实例化自行车对象有许多好处。首先,不必了解自行车和装饰者的各种类名,所有这些信息都封装在AcmeBicycleShop类中。因此添加自行车型号和选件非常容易, 34 | 只要把它们添加到AcmeBicycleShp.models或AcmeBicycleShop.options数组中即可。
35 | 例子:
36 | 37 | AcmeBicycleShop类的createBicycle方法,以便用户可以指定自行车要配的选件。 38 | 39 |
40 | 通过上面例子最后创建带装饰的自行车对象的两种不同做法,可以发现,第一种方法与客户代码紧密耦合在一起的类不下5个,第二种方法使用了工厂,与客户代码耦合在一起的只有一个类,即那个工厂本身。
41 | 4、函数装饰者
42 | 装饰者并不局限于类,你也可以创建用来包装独立的函数和方法的装饰者。
43 | example
44 | 5、装饰者模式的适用场合
45 | 如果需要为类增添特性或职责,而从该类派生子类的解决办法并不实际的话,就应该适用装饰者模式。派生子类之所以会不实际,最常见的原因是需要增添的特性的数量和组合要求适用大量子类。
46 | 如果需要为对象添加特性而又不想改变使用该对象的代码的话,也可以采用装饰者模式。因为装饰者可以动态而又透明地修改对象,所以它们很适合于修改现有系统这一任务。
47 | 6、示例:方法性能分析器
48 | 本例要创建的装饰者可以用来包装任何对象以便为其提供方法性能分析功能。我们打算在每一个方法的前后添加一些代码,分别用于启动计时器和停止计时器并报告结果。
49 | example
50 | 7、装饰者模式之利
51 | 装饰者是在运行期间为对象增添特性或职责的有工具。
52 | 装饰者的运行过程是透明的,这就是说你可以用它包装其他对象,然后继续按之前使用那些对象的方法来使用它。装饰者为程序员带来了极大的灵活性。
53 | 8、装饰者模式之弊
54 | 在遇到用装饰者包装起来的对象时,那些依赖于类型检查的代码会出问题。
55 | 使用装饰者模式往往会增加架构的复杂程度。此外,实现具有动态接口的装饰者(如MethodProfiler)涉及的语法细节有时也会令人生畏。在设计一个使用了装饰者模式的架构时,你需要多花点心思,确保自己的代码有良好的文档说明,并且容易理解。 -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Facade-Pattern/1 - Some facades you probably already know about.js: -------------------------------------------------------------------------------- 1 | function addEvent(el, type, fn) { 2 | if (window.addEventListener) { 3 | el.addEventListener(type, fn, false); 4 | } 5 | else if (window.attachEvent) { 6 | el.attachEvent('on' + type, fn); 7 | } 8 | else { 9 | el['on' + type] = fn; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Facade-Pattern/2 - Facades as convenience methods.js: -------------------------------------------------------------------------------- 1 | function a(x) { 2 | // do stuff here... 3 | } 4 | function b(y) { 5 | // do stuff here... 6 | } 7 | function ab(x, y) { 8 | a(x); 9 | b(y); 10 | } 11 | 12 | 13 | 14 | var DED = window.DED || {}; 15 | DED.util = { 16 | stopPropagation: function(e) { 17 | if (ev.stopPropagation) { 18 | // W3 interface 19 | e.stopPropagation(); 20 | } 21 | else { 22 | // IE's interface 23 | e.cancelBubble = true; 24 | } 25 | }, 26 | preventDefault: function(e) { 27 | if (e.preventDefault) { 28 | // W3 interface 29 | e.preventDefault(); 30 | } 31 | else { 32 | // IE's interface 33 | e.returnValue = false; 34 | } 35 | }, 36 | /* our convenience method */ 37 | stopEvent: function(e) { 38 | DED.util.stopPropagation(e); 39 | DED.util.preventDefault(e); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Facade-Pattern/3 - Setting styles on HTML elements.js: -------------------------------------------------------------------------------- 1 | var element = document.getElementById('content'); 2 | element.style.color = 'red'; 3 | 4 | element.style.fontSize = '16px'; 5 | 6 | var element1 = document.getElementById('foo'); 7 | element1.style.color = 'red'; 8 | 9 | var element2 = document.getElementById('bar'); 10 | element2.style.color = 'red'; 11 | 12 | var element3 = document.getElementById('baz'); 13 | element3.style.color = 'red'; 14 | 15 | 16 | 17 | 18 | setStyle(['foo', 'bar', 'baz'], 'color', 'red'); 19 | 20 | function setStyle(elements, prop, val) { 21 | for (var i = 0, len = elements.length-1; I < len; ++i) { 22 | document.getElementById(elements[i]).style[prop] = val; 23 | } 24 | } 25 | 26 | setStyle(['foo'], 'position', 'absolute'); 27 | setStyle(['foo'], 'top', '50px'); 28 | setStyle(['foo'], 'left', '300px'); 29 | 30 | setCSS(['foo'], { 31 | position: 'absolute', 32 | top: '50px', 33 | left: '300px' 34 | }); 35 | 36 | function setCSS(el, styles) { 37 | for ( var prop in styles ) { 38 | if (!styles.hasOwnProperty(prop)) continue; 39 | setStyle(el, prop, styles[prop]); 40 | } 41 | } 42 | 43 | setCSS(['foo', 'bar', 'baz'], { 44 | color: 'white', 45 | background: 'black', 46 | fontSize: '16px', 47 | fontFamily: 'georgia, times, serif' 48 | }); 49 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Facade-Pattern/4 - Creating an event utility.js: -------------------------------------------------------------------------------- 1 | DED.util.Event = { 2 | getEvent: function(e) { 3 | return e || window.event; 4 | }, 5 | getTarget: function(e) { 6 | return e.target || e.srcElement; 7 | }, 8 | stopPropagation: function(e) { 9 | if (e.stopPropagation) { 10 | e.stopPropagation(); 11 | } 12 | else { 13 | e.cancelBubble = true; 14 | } 15 | }, 16 | preventDefault: function(e) { 17 | if (e.preventDefault) { 18 | e.preventDefault(); 19 | } 20 | else { 21 | e.returnValue = false; 22 | } 23 | }, 24 | stopEvent: function(e) { 25 | this.stopPropagation(e); 26 | this.preventDefault(e); 27 | } 28 | }; 29 | 30 | addEvent($('example'), 'click', function(e) { 31 | // Who clicked me. 32 | console.log(DED.util.Event.getTarget(e)); 33 | // Stop propgating and prevent the default action. 34 | DED.util.Event.stopEvent(e); 35 | }); 36 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Facade-Pattern/README.md: -------------------------------------------------------------------------------- 1 | ###外观模式 2 | **门面模式是几乎所有JavaScript库的核心原则** 3 | 4 | 子系统中的一组接口提供一个一致的界面,门面模式定义了一个高层接口,这个接口使得这一子系统更加容易使用,简单的说这是一种组织性的模式,它可以用来修改类和对象的接口,使其更便于使用。 5 | 门面模式的两个作用: 6 | >1.简化类的接口; 7 | >2.消除类与使用它的客户代码之间的耦合。 8 | 9 | 外观模式并不是适配器模式,适配器模式是一种包装器,用来对接口进行适配以便在不兼容系统中使用它。而创建外观元素则是图个方便。它并不用于达到需要特定接口的客户系统打交道这个目的,而是用于提供一个简化的接口。
10 |
11 | 12 | 一个纯粹形式化的例子 13 | 14 |
15 | 示例: 16 | 17 | 设置HTML元素的样式 18 | 19 |
20 | 示例: 21 | 22 | 设计一个事件工具 23 | 24 |
25 | 2.1、实现外观模式的一般步骤:
26 | 找准自己的应用程序中感觉适合使用门面方法的地方后,就可以着手加入便利方法了。这些函数的名称应经仔细考虑,与它们的用途要相称。对于那种由几个函数组合而成的函数,一个简单的办法就是把相关函数的名称串联成一个函数名,并采用camel大写规范,或者也可以使用thisFunctionAndThatFunction这种形式。
27 | 处理浏览器API的不一致性属于另一种情况,此时要做的就是把分支代码放在新创建的门面函数中,辅以对象检查或者浏览器嗅探等技术。
28 | 2.2、外观模式的适用场合:
29 | 判断是否应该应用外观模式的关键在于辨认那些反复成组出线的代码。如果函数b出现在函数a之后这种情况经常出现,那么也许你应该考虑添加一个把这两个函数组合起来的外观函数。
30 | 另一种情况是应对Javascript内置函数在不同浏览器中不同表现。最好的解决办法就是把这些差异抽取到外观方法中,它们可以提供一个更一致的接口。
31 | 2.3、外观模式之利:
32 | 使用外观模式的目的就是要让程序员过的更轻松一些,编写一次组合代码,然后就可以反复使用它,这有助于节省时间和精力。给一些复杂的问题提供一个简化接口。
33 | 外观方法方便了开发人员,斌共提供过了比较高层的功能,降低对外部代码的依赖程度,为应用系统的开发增加了一些额外的灵活性。通过使用外观模式,可以避免与下层子系统紧密耦合。这样就可以对这个系统进行修改而不会影响到客户代码。
34 | 2.4、外观模式之弊:
35 | 有时候外观元素也会带来一些不必要的额外负担。在实施一些套路之前应该认真掂量一下其实用性。有时相比一个庞杂的外观函数,其组成函数在力度方面更有吸引力。这是因为外观函数可能常常会执行一些你并不需要的任务。
36 | 对于简单的个人网站或少量营销网页来说,仅为工具提示和弹出式窗口这样一点增强行为就导入这个Javascript库可能并不明智。此时考虑只使用少许简单的外观元素而不是一个满是这类东西的库。
37 | 外观函数为执行各种复杂任务提供了一个简单的接口,它们使代码更容易维护和理解。它们还能弱化子系统和客户代码的耦合。把经常相伴出现的常用函数组合在一起。这个模式在DOM脚本编程这种需要面对葛洪不一致的浏览器接口的环境中很常用。 -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Factory-Pattern/1 - The simple factory.js: -------------------------------------------------------------------------------- 1 | /* BicycleShop class. */ 2 | 3 | var BicycleShop = function() {}; 4 | BicycleShop.prototype = { 5 | sellBicycle: function(model) { 6 | var bicycle; 7 | 8 | switch(model) { 9 | case 'The Speedster': 10 | bicycle = new Speedster(); 11 | break; 12 | case 'The Lowrider': 13 | bicycle = new Lowrider(); 14 | break; 15 | case 'The Comfort Cruiser': 16 | default: 17 | bicycle = new ComfortCruiser(); 18 | } 19 | Interface.ensureImplements(bicycle, Bicycle); 20 | 21 | bicycle.assemble(); 22 | bicycle.wash(); 23 | 24 | return bicycle; 25 | } 26 | }; 27 | 28 | 29 | /* The Bicycle interface. */ 30 | 31 | var Bicycle = new Interface('Bicycle', ['assemble', 'wash', 'ride', 'repair']); 32 | 33 | /* Speedster class. */ 34 | 35 | var Speedster = function() { // implements Bicycle 36 | //todo 37 | }; 38 | Speedster.prototype = { 39 | assemble: function() { 40 | //todo 41 | }, 42 | wash: function() { 43 | //todo 44 | }, 45 | ride: function() { 46 | //todo 47 | }, 48 | repair: function() { 49 | //todo 50 | } 51 | }; 52 | 53 | 54 | /* Usage. */ 55 | 56 | var californiaCruisers = new BicycleShop(); 57 | var yourNewBike = californiaCruisers.sellBicycle('The Speedster'); 58 | 59 | 60 | 61 | /* BicycleFactory namespace. */ 62 | 63 | var BicycleFactory = { 64 | createBicycle: function(model) { 65 | var bicycle; 66 | 67 | switch(model) { 68 | case 'The Speedster': 69 | bicycle = new Speedster(); 70 | break; 71 | case 'The Lowrider': 72 | bicycle = new Lowrider(); 73 | break; 74 | case 'The Comfort Cruiser': 75 | default: 76 | bicycle = new ComfortCruiser(); 77 | } 78 | 79 | Interface.ensureImplements(bicycle, Bicycle); 80 | return bicycle; 81 | } 82 | }; 83 | 84 | /* BicycleShop class, improved. */ 85 | 86 | var BicycleShop = function() {}; 87 | BicycleShop.prototype = { 88 | sellBicycle: function(model) { 89 | var bicycle = BicycleFactory.createBicycle(model); 90 | 91 | bicycle.assemble(); 92 | bicycle.wash(); 93 | 94 | return bicycle; 95 | } 96 | }; 97 | 98 | /* BicycleFactory namespace, with more models. */ 99 | 100 | var BicycleFactory = { 101 | createBicycle: function(model) { 102 | var bicycle; 103 | 104 | switch(model) { 105 | case 'The Speedster': 106 | bicycle = new Speedster(); 107 | break; 108 | case 'The Lowrider': 109 | bicycle = new Lowrider(); 110 | break; 111 | case 'The Flatlander': 112 | bicycle = new Flatlander(); 113 | break; 114 | case 'The Comfort Cruiser': 115 | default: 116 | bicycle = new ComfortCruiser(); 117 | } 118 | 119 | Interface.ensureImplements(bicycle, Bicycle); 120 | return bicycle; 121 | } 122 | }; 123 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Factory-Pattern/2 - The factory pattern.js: -------------------------------------------------------------------------------- 1 | /* BicycleShop class (abstract). */ 2 | 3 | var BicycleShop = function() {}; 4 | BicycleShop.prototype = { 5 | sellBicycle: function(model) { 6 | var bicycle = this.createBicycle(model); 7 | 8 | bicycle.assemble(); 9 | bicycle.wash(); 10 | 11 | return bicycle; 12 | }, 13 | createBicycle: function(model) { 14 | throw new Error('Unsupported operation on an abstract class.'); 15 | } 16 | }; 17 | 18 | /* AcmeBicycleShop class. */ 19 | 20 | var AcmeBicycleShop = function() {}; 21 | extend(AcmeBicycleShop, BicycleShop); 22 | AcmeBicycleShop.prototype.createBicycle = function(model) { 23 | var bicycle; 24 | 25 | switch(model) { 26 | case 'The Speedster': 27 | bicycle = new AcmeSpeedster(); 28 | break; 29 | case 'The Lowrider': 30 | bicycle = new AcmeLowrider(); 31 | break; 32 | case 'The Flatlander': 33 | bicycle = new AcmeFlatlander(); 34 | break; 35 | case 'The Comfort Cruiser': 36 | default: 37 | bicycle = new AcmeComfortCruiser(); 38 | } 39 | 40 | Interface.ensureImplements(bicycle, Bicycle); 41 | return bicycle; 42 | }; 43 | 44 | /* GeneralProductsBicycleShop class. */ 45 | 46 | var GeneralProductsBicycleShop = function() {}; 47 | extend(GeneralProductsBicycleShop, BicycleShop); 48 | GeneralProductsBicycleShop.prototype.createBicycle = function(model) { 49 | var bicycle; 50 | 51 | switch(model) { 52 | case 'The Speedster': 53 | bicycle = new GeneralProductsSpeedster(); 54 | break; 55 | case 'The Lowrider': 56 | bicycle = new GeneralProductsLowrider(); 57 | break; 58 | case 'The Flatlander': 59 | bicycle = new GeneralProductsFlatlander(); 60 | break; 61 | case 'The Comfort Cruiser': 62 | default: 63 | bicycle = new GeneralProductsComfortCruiser(); 64 | } 65 | 66 | Interface.ensureImplements(bicycle, Bicycle); 67 | return bicycle; 68 | }; 69 | 70 | 71 | /* Usage. */ 72 | 73 | var alecsCruisers = new AcmeBicycleShop(); 74 | var yourNewBike = alecsCruisers.sellBicycle('The Lowrider'); 75 | 76 | var bobsCruisers = new GeneralProductsBicycleShop(); 77 | var yourSecondNewBike = bobsCruisers.sellBicycle('The Lowrider'); 78 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Factory-Pattern/3 - XHR factory example.js: -------------------------------------------------------------------------------- 1 | /* AjaxHandler interface. */ 2 | 3 | var AjaxHandler = new Interface('AjaxHandler', ['request', 'createXhrObject']); 4 | 5 | /* SimpleHandler class. */ 6 | 7 | var SimpleHandler = function() {}; // implements AjaxHandler 8 | SimpleHandler.prototype = { 9 | request: function(method, url, callback, postVars) { 10 | var xhr = this.createXhrObject(); 11 | xhr.onreadystatechange = function() { 12 | if(xhr.readyState !== 4) return; 13 | (xhr.status === 200) ? 14 | callback.success(xhr.responseText, xhr.responseXML) : 15 | callback.failure(xhr.status); 16 | }; 17 | xhr.open(method, url, true); 18 | if(method !== 'POST') postVars = null; 19 | xhr.send(postVars); 20 | }, 21 | createXhrObject: function() { // Factory method. 22 | var methods = [ 23 | function() { return new XMLHttpRequest(); }, 24 | function() { return new ActiveXObject('Msxml2.XMLHTTP'); }, 25 | function() { return new ActiveXObject('Microsoft.XMLHTTP'); } 26 | ]; 27 | 28 | for(var i = 0, len = methods.length; i < len; i++) { 29 | try { 30 | methods[i](); 31 | } 32 | catch(e) { 33 | continue; 34 | } 35 | // If we reach this point, method[i] worked. 36 | this.createXhrObject = methods[i]; // Memoize the method. 记住该方法 37 | return methods[i](); // 原书中的代码少了一对括号,这里需要多加一对括号才可以正确返回XMLHttpRequeset 38 | } 39 | 40 | // If we reach this point, none of the methods worked. 41 | throw new Error('SimpleHandler: Could not create an XHR object.'); 42 | } 43 | }; 44 | 45 | /* Usage. */ 46 | 47 | var myHandler = new SimpleHandler(); 48 | var callback = { 49 | success: function(responseText) { alert('Success: ' + responseText); }, 50 | failure: function(statusCode) { alert('Failure: ' + statusCode); } 51 | }; 52 | myHandler.request('GET', 'script.php', callback); 53 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Factory-Pattern/4 - Specialized connection objects.js: -------------------------------------------------------------------------------- 1 | /* QueuedHandler class. */ 2 | 3 | var QueuedHandler = function() { // implements AjaxHandler 4 | this.queue = []; 5 | this.requestInProgress = false; 6 | this.retryDelay = 5; // In seconds. 7 | }; 8 | extend(QueuedHandler, SimpleHandler); 9 | QueuedHandler.prototype.request = function(method, url, callback, postVars, 10 | override) { 11 | if(this.requestInProgress && !override) { 12 | this.queue.push({ 13 | method: method, 14 | url: url, 15 | callback: callback, 16 | postVars: postVars 17 | }); 18 | } 19 | else { 20 | this.requestInProgress = true; 21 | var xhr = this.createXhrObject(); 22 | var that = this; 23 | xhr.onreadystatechange = function() { 24 | if(xhr.readyState !== 4) return; 25 | if(xhr.status === 200) { 26 | callback.success(xhr.responseText, xhr.responseXML); 27 | that.advanceQueue(); 28 | } 29 | else { 30 | callback.failure(xhr.status); 31 | setTimeout(function() { that.request(method, url, callback, postVars); }, 32 | that.retryDelay * 1000); 33 | } 34 | }; 35 | xhr.open(method, url, true); 36 | if(method !== 'POST') postVars = null; 37 | xhr.send(postVars); 38 | } 39 | }; 40 | QueuedHandler.prototype.advanceQueue = function() { 41 | if(this.queue.length === 0) { 42 | this.requestInProgress = false; 43 | return; 44 | } 45 | var req = this.queue.shift(); 46 | this.request(req.method, req.url, req.callback, req.postVars, true); 47 | }; 48 | 49 | 50 | /* OfflineHandler class. */ 51 | 52 | var OfflineHandler = function() { // implements AjaxHandler 53 | this.storedRequests = []; 54 | }; 55 | extend(OfflineHandler, SimpleHandler); 56 | OfflineHandler.prototype.request = function(method, url, callback, postVars) { 57 | if(XhrManager.isOffline()) { // Store the requests until we are online. 58 | this.storedRequests.push({ 59 | method: method, 60 | url: url, 61 | callback: callback, 62 | postVars: postVars 63 | }); 64 | } 65 | else { // Call SimpleHandler's request method if we are online. 66 | this.flushStoredRequests(); 67 | OfflineHandler.superclass.request(method, url, callback, postVars); 68 | } 69 | }; 70 | OfflineHandler.prototype.flushStoredRequests = function() { 71 | for(var i = 0, len = storedRequests.length; i < len; i++) { 72 | var req = storedRequests[i]; 73 | OfflineHandler.superclass.request(req.method, req.url, req.callback, 74 | req.postVars); 75 | } 76 | }; 77 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Factory-Pattern/5 - Choosing connection objects at run-time.js: -------------------------------------------------------------------------------- 1 | /* XhrManager singleton. */ 2 | 3 | var XhrManager = { 4 | createXhrHandler: function() { 5 | var xhr; 6 | if(this.isOffline()) { 7 | xhr = new OfflineHandler(); 8 | } 9 | else if(this.isHighLatency()) { 10 | xhr = new QueuedHandler(); 11 | } 12 | else { 13 | xhr = new SimpleHandler() 14 | } 15 | 16 | Interface.ensureImplements(xhr, AjaxHandler); 17 | return xhr 18 | }, 19 | isOffline: function() { // Do a quick request with SimpleHandler and see if 20 | //todo // it succeeds. 21 | }, 22 | isHighLatency: function() { // Do a series of requests with SimpleHandler and 23 | //todo // time the responses. Best done once, as a 24 | // branching function. 25 | } 26 | }; 27 | 28 | 29 | /* Usage. */ 30 | 31 | var myHandler = XhrManager.createXhrHandler(); 32 | var callback = { 33 | success: function(responseText) { alert('Success: ' + responseText); }, 34 | failure: function(statusCode) { alert('Failure: ' + statusCode); } 35 | }; 36 | myHandler.request('GET', 'script.php', callback); 37 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Factory-Pattern/6 - RSS reader example.js: -------------------------------------------------------------------------------- 1 | /* DisplayModule interface. */ 2 | 3 | var DisplayModule = new Interface('DisplayModule', ['append', 'remove', 'clear']); 4 | 5 | /* ListDisplay class. */ 6 | 7 | var ListDisplay = function(id, parent) { // implements DisplayModule 8 | this.list = document.createElement('ul'); 9 | this.list.id = id; 10 | parent.appendChild(this.list); 11 | }; 12 | ListDisplay.prototype = { 13 | append: function(text) { 14 | var newEl = document.createElement('li'); 15 | this.list.appendChild(newEl); 16 | newEl.innerHTML = text; 17 | return newEl; 18 | }, 19 | remove: function(el) { 20 | this.list.removeChild(el); 21 | }, 22 | clear: function() { 23 | this.list.innerHTML = ''; 24 | } 25 | }; 26 | 27 | /* Configuration object. */ 28 | 29 | var conf = { 30 | id: 'cnn-top-stories', 31 | feedUrl: 'http://rss.cnn.com/rss/cnn_topstories.rss', 32 | updateInterval: 60, // In seconds. 33 | parent: $('feed-readers') 34 | }; 35 | 36 | /* FeedReader class. */ 37 | 38 | var FeedReader = function(display, xhrHandler, conf) { 39 | this.display = display; 40 | this.xhrHandler = xhrHandler; 41 | this.conf = conf; 42 | 43 | this.startUpdates(); 44 | }; 45 | FeedReader.prototype = { 46 | fetchFeed: function() { 47 | var that = this; 48 | var callback = { 49 | success: function(text, xml) { that.parseFeed(text, xml); }, 50 | failure: function(status) { that.showError(status); } 51 | }; 52 | this.xhrHandler.request('GET', 'feedProxy.php?feed=' + this.conf.feedUrl, 53 | callback); 54 | }, 55 | parseFeed: function(responseText, responseXML) { 56 | this.display.clear(); 57 | var items = responseXML.getElementsByTagName('item'); 58 | for(var i = 0, len = items.length; i < len; i++) { 59 | var title = items[i].getElementsByTagName('title')[0]; 60 | var link = items[i].getElementsByTagName('link')[0]; 61 | this.display.append('' + 62 | title.firstChild.data + ''); 63 | } 64 | }, 65 | showError: function(statusCode) { 66 | this.display.clear(); 67 | this.display.append('Error fetching feed.'); 68 | }, 69 | stopUpdates: function() { 70 | clearInterval(this.interval); 71 | }, 72 | startUpdates: function() { 73 | this.fetchFeed(); 74 | var that = this; 75 | this.interval = setInterval(function() { that.fetchFeed(); }, 76 | this.conf.updateInterval * 1000); 77 | } 78 | }; 79 | 80 | /* FeedManager namespace. */ 81 | 82 | var FeedManager = { 83 | createFeedReader: function(conf) { 84 | var displayModule = new ListDisplay(conf.id + '-display', conf.parent); 85 | Interface.ensureImplements(displayModule, DisplayModule); 86 | 87 | var xhrHandler = XhrManager.createXhrHandler(); 88 | Interface.ensureImplements(xhrHandler, AjaxHandler); 89 | 90 | return new FeedReader(displayModule, xhrHandler, conf); 91 | } 92 | }; 93 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Factory-Pattern/README.md: -------------------------------------------------------------------------------- 1 | 工厂模式:
2 | 1.1、简单工厂:
3 | 另外使用一个类(通常是一个单例)来生成实例,示例程序;
4 | 1.2、工厂模式:
5 | 使用子类来决定一个成员变量应该是哪个具体的类的实例,示例程序;
6 | 1.3、工厂模式的适用场景:
7 | 1.3.1、动态实现:
8 | XHR的例子,使用简单工厂,注意在工厂方法中使用到的memoizing技术;
9 | 专用型连接对象:创建两个新的处理器类:
10 | QueuedHandler:在发起新的请求之前先确保所有请求都已经成功处理。
11 | OfflineHandler:在用户处于离线状态时把请求缓存起来。
12 | 示例程序
13 | 在运行时选择连接对象:示例程序
14 | 1.3.2、节省设置开销:
15 | 把设置代码放到类的构造函数中并不是一种高效的做法,这是因为即便设置工作已经完成,每次创建新实例的时候这些代码还是会执行,而且这样做会把设置代码分散到不同的类中。工厂方法非常适合于这种场合。它可以再实例化所有需要的对象之前一次性地进行设置。
16 | 1.3.3、用许多小型对象组成一个大对象:
17 | RSS阅读器:由ListDisplay, XhrHandler, conf对象组成,包括fetchFeed, parseFeed, showError, stopUpdates, startUpdates方法。这是一个阐明“用许多小型对象组成一个大对象”这个用途的绝佳示例。它使用工厂模式,先创建出所有要用到的对象,然后再生成并返回那个作为容器的FeedReader类型大对象:示例程序
18 | 1.4、优点:
19 | 弱化对象的耦合,防止代码的重复。在一个方法中进行类的实例化,可以消除重复性的代码。这是在用一个对接口的调用取代具体的实现。这些都有助于创建模块化的代码。使用工厂模式,你可以先创建一个抽象的父类,然后再子类中创建工厂方法,从而把成员对象的实例化推迟到更专门化的子类中进行。
20 | 通过使用工厂方法而不是new关键字及具体类,你可以把所有实例化代码集中在一个位置,可以大大简化更换所用的类或在运行期间动态选择所用的类的工作。
21 | 1.5、缺点:
22 | 如果不需要再运行期间在一系列可互换的类中进行选择,那就不应该使用工厂方法。大多数类最好使用new关键字和构造函数公开初始化,使代码更简单易读,一眼就看到调用的是什么构造函数而不用查看工厂方法。
23 | 如果拿不定主意,就不要用工厂模式,因为在代码重构时还有机会使用工厂模式。 -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Flyweight-Pattern/1 - Car registration example.js: -------------------------------------------------------------------------------- 1 | /* Car class, un-optimized. */ 2 | // 示例:汽车登记 3 | // 把每辆汽车表示为一个对象 4 | var Car = function(make, model, year, owner, tag, renewDate) { 5 | this.make = make; 6 | this.model = model; 7 | this.year = year; 8 | this.owner = owner; 9 | this.tag = tag; 10 | this.renewDate = renewDate; 11 | }; 12 | Car.prototype = { 13 | getMake: function() { 14 | return this.make; 15 | }, 16 | getModel: function() { 17 | return this.model; 18 | }, 19 | getYear: function() { 20 | return this.year; 21 | }, 22 | 23 | transferOwnership: function(newOwner, newTag, newRenewDate) { 24 | this.owner = newOwner; 25 | this.tag = newTag; 26 | this.renewDate = newRenewDate; 27 | }, 28 | renewRegistration: function(newRenewDate) { 29 | this.renewDate = newRenewDate; 30 | }, 31 | isRegistrationCurrent: function() { 32 | var today = new Date(); 33 | return today.getTime() < Date.parse(this.renewDate); 34 | } 35 | }; 36 | 37 | /* Car class, optimized as a flyweight. */ 38 | // 使用享元模式,把对象数据划分为内在状态和外在状态 39 | var Car = function(make, model, year) { 40 | this.make = make; 41 | this.model = model; 42 | this.year = year; 43 | }; 44 | Car.prototype = { 45 | getMake: function() { 46 | return this.make; 47 | }, 48 | getModel: function() { 49 | return this.model; 50 | }, 51 | getYear: function() { 52 | return this.year; 53 | } 54 | }; 55 | // 上面代码删除了所有的外在数据。 56 | // 所有处理登记事宜的方法都被转移到一个对象管理器中。 57 | // 因为现在对象的数据已被分为两大部分,所以必须用工厂来实例化它。 58 | /* CarFactory singleton. */ 59 | // 工厂说明:它会检查之前是否已经创建过对应于指定品牌-型号-出厂日期组合的汽车,如果存在这样的汽车那就返回它, 60 | // 否则就创建一辆新车,并把它保存起来。这就确保了对应于每个唯一的内在状态。 61 | var CarFactory = (function() { 62 | 63 | var createdCars = {}; 64 | 65 | return { 66 | createCar: function(make, model, year) { 67 | // Check to see if this particular combination has been created before. 68 | if(createdCars[make + '-' + model + '-' + year]) { 69 | return createdCars[make + '-' + model + '-' + year]; 70 | } 71 | // Otherwise create a new instance and save it. 72 | else { 73 | var car = new Car(make, model, year); 74 | createdCars[make + '-' + model + '-' + year] = car; 75 | return car; 76 | } 77 | } 78 | }; 79 | })(); 80 | 81 | /* CarRecordManager singleton. */ 82 | // 封装在管理器中的外在状态 83 | // 要完成这种优化还需要一个对象,用一个单例来做封装这些数据的管理器。 84 | var CarRecordManager = (function() { 85 | // 从Car类剥离的所有数据现在都保存在这个对象中 86 | var carRecordDatabase = {}; 87 | 88 | return { 89 | // Add a new car record into the city's system. 90 | addCarRecord: function(make, model, year, owner, tag, renewDate) { 91 | // 创建汽车 92 | var car = CarFactory.createCar(make, model, year); 93 | // 添加外在状态数据 94 | carRecordDatabase[tag] = { 95 | owner: owner, 96 | renewDate: renewDate, 97 | car: car 98 | }; 99 | }, 100 | 101 | // Methods previously contained in the Car class. 102 | transferOwnership: function(tag, newOwner, newTag, newRenewDate) { 103 | var record = carRecordDatabase[tag]; 104 | record.owner = newOwner; 105 | record.tag = newTag; 106 | record.renewDate = newRenewDate; 107 | }, 108 | renewRegistration: function(tag, newRenewDate) { 109 | carRecordDatabase[tag].renewDate = newRenewDate; 110 | }, 111 | isRegistrationCurrent: function(tag) { 112 | var today = new Date(); 113 | return today.getTime() < Date.parse(carRecordDatabase[tag].renewDate); 114 | } 115 | }; 116 | })(); 117 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Flyweight-Pattern/2 - Web calendar example.js: -------------------------------------------------------------------------------- 1 | /* CalendarItem interface. */ 2 | // 创建一个Web日历 3 | // 首先实现的是一个未经优化的、未使用享元的版本,这是个大型组合对象。 4 | var CalendarItem = new Interface('CalendarItem', ['display']); 5 | 6 | /* CalendarYear class, a composite. */ 7 | // 组合对象 8 | var CalendarYear = function(year, parent) { // implements CalendarItem 9 | this.year = year; 10 | this.element = document.createElement('div'); 11 | this.element.style.display = 'none'; 12 | parent.appendChild(this.element); 13 | 14 | function isLeapYear(y) { 15 | return (y > 0) && !(y % 4) && ((y % 100) || !(y % 400)); 16 | } 17 | 18 | this.months = []; 19 | // The number of days in each month. 20 | this.numDays = [31, isLeapYear(this.year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 21 | 31, 30, 31]; 22 | for(var i = 0, len = 12; i < len; i++) { 23 | this.months[i] = new CalendarMonth(i, this.numDays[i], this.element); 24 | } 25 | ); 26 | CalendarYear.prototype = { 27 | display: function() { 28 | for(var i = 0, len = this.months.length; i < len; i++) { 29 | // 继续向下调用子对象的display方法 30 | this.months[i].display(); // Pass the call down to the next level. 31 | } 32 | this.element.style.display = 'block'; 33 | } 34 | }; 35 | 36 | /* CalendarMonth class, a composite. */ 37 | // 组合对象 38 | var CalendarMonth = function(monthNum, numDays, parent) { // implements CalendarItem 39 | this.monthNum = monthNum; 40 | this.element = document.createElement('div'); 41 | this.element.style.display = 'none'; 42 | parent.appendChild(this.element); 43 | 44 | this.days = []; 45 | for(var i = 0, len = numDays; i < len; i++) { 46 | this.days[i] = new CalendarDay(i, this.element); 47 | } 48 | ); 49 | CalendarMonth.prototype = { 50 | display: function() { 51 | for(var i = 0, len = this.days.length; i < len; i++) { 52 | // 继续向下调用子对象的display方法 53 | this.days[i].display(); // Pass the call down to the next level. 54 | } 55 | this.element.style.display = 'block'; 56 | } 57 | }; 58 | 59 | /* CalendarDay class, a leaf (unoptimized). */ 60 | // 叶对象 61 | var CalendarDay = function(date, parent) { // implements CalendarItem 62 | this.date = date; 63 | this.element = document.createElement('div'); 64 | this.element.style.display = 'none'; 65 | parent.appendChild(this.element); 66 | }; 67 | CalendarDay.prototype = { 68 | display: function() { 69 | this.element.style.display = 'block'; 70 | this.element.innerHTML = this.date; 71 | } 72 | }; 73 | 74 | 75 | 76 | /* CalendarDay class, a flyweight leaf (optimized). */ 77 | // 把日期对象转换为享元进行优化 78 | // 把CalendarDay对象转化为享元对象的过程很简单。 79 | // 首先,修改CalendarDay类本身,除去其中保存的所有数据,让这些数据成为外在数据: 80 | var CalendarDay = function() {}; // implements CalendarItem 81 | CalendarDay.prototype = { 82 | display: function(date, parent) { 83 | var element = document.createElement('div'); 84 | parent.appendChild(element); 85 | element.innerHTML = date; 86 | } 87 | }; 88 | 89 | /* Single instance of CalendarDay */ 90 | // 创建日期对象的单个实例,所有CalendarMonth对象中都要使用这个实例。 91 | var calendarDay = new CalendarDay(); 92 | // 享元的典型工作方式:现在外在数据成了display方法的参数,而不是类的构造函数的参数。 93 | 94 | /* CalendarMonth class, a composite (optimized). */ 95 | // 修改CalendarMonth类 96 | var CalendarMonth = function(monthNum, numDays, parent) { // implements CalendarItem 97 | this.monthNum = monthNum; 98 | this.element = document.createElement('div'); 99 | this.element.style.display = 'none'; 100 | parent.appendChild(this.element); 101 | 102 | this.days = []; 103 | for(var i = 0, len = numDays; i < len; i++) { 104 | // 原来用CalendarDay类构造函数创建该,替换为calendarDay对象 105 | this.days[i] = calendarDay; 106 | } 107 | ); 108 | CalendarMonth.prototype = { 109 | display: function() { 110 | for(var i = 0, len = this.days.length; i < len; i++) { 111 | // 原本提供该给CalendarDay类构造函数的参数现在被转而提供给display方法 112 | this.days[i].display(i, this.element); 113 | } 114 | this.element.style.display = 'block'; 115 | } 116 | }; 117 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Flyweight-Pattern/3 - Tooltip example.js: -------------------------------------------------------------------------------- 1 | /* Tooltip class, un-optimized. */ 2 | // 未经优化(未使用享元模式)的Tooltip类 3 | var Tooltip = function(targetElement, text) { 4 | this.target = targetElement; 5 | this.text = text; 6 | this.delayTimeout = null; 7 | this.delay = 1500; // in milliseconds. 8 | 9 | // Create the HTML. 10 | this.element = document.createElement('div'); 11 | this.element.style.display = 'none'; 12 | this.element.style.position = 'absolute'; 13 | this.element.className = 'tooltip'; 14 | document.getElementsByTagName('body')[0].appendChild(this.element); 15 | this.element.innerHTML = this.text; 16 | 17 | // Attach the events. 18 | var that = this; // Correcting the scope. 19 | addEvent(this.target, 'mouseover', function(e) { that.startDelay(e); }); 20 | addEvent(this.target, 'mouseout', function(e) { that.hide(); }); 21 | }; 22 | Tooltip.prototype = { 23 | startDelay: function(e) { 24 | if(this.delayTimeout == null) { 25 | var that = this; 26 | var x = e.clientX; 27 | var y = e.clientY; 28 | this.delayTimeout = setTimeout(function() { 29 | that.show(x, y); 30 | }, this.delay); 31 | } 32 | }, 33 | show: function(x, y) { 34 | clearTimeout(this.delayTimeout); 35 | this.delayTimeout = null; 36 | this.element.style.left = (x) + 'px'; 37 | this.element.style.top = (y + 20) + 'px'; 38 | this.element.style.display = 'block'; 39 | }, 40 | hide: function() { 41 | clearTimeout(this.delayTimeout); 42 | this.delayTimeout = null; 43 | this.element.style.display = 'none'; 44 | } 45 | }; 46 | 47 | /* Tooltip usage. */ 48 | 49 | var linkElement = $('link-id'); 50 | var tt = new Tooltip(linkElement, 'Lorem ipsum...'); 51 | /* 如果网页上有几百个甚至几千个元素需要用到工具提示,这意味着将会出现成百上千个Tooltip类的实例, 52 | * 他们每个都有自己的属性、Dom元素和样式,这是非常低效的 53 | * 如果把Tooltip对象实现为享元,那么它只要有一个实例就行, 54 | * 可以让管理器对象把要显示的文字作为外在数据提供给它的方法。 55 | */ 56 | 57 | 58 | /* Tooltip class, as a flyweight. */ 59 | // 作为享元的Tooltip 60 | // 把Tooltip类转化为共享需要做三件事: 61 | // 把外在数据从Tooltip对象中删除 62 | // 创建一个用来实例化Tooltip的工厂 63 | // 创建一个用来保存外在数据的管理器 64 | 65 | // 把外在数据从Tooltip类中删除 66 | var Tooltip = function() { 67 | this.delayTimeout = null; 68 | this.delay = 1500; // in milliseconds. 69 | 70 | // Create the HTML. 71 | this.element = document.createElement('div'); 72 | this.element.style.display = 'none'; 73 | this.element.style.position = 'absolute'; 74 | this.element.className = 'tooltip'; 75 | document.getElementsByTagName('body')[0].appendChild(this.element); 76 | }; 77 | Tooltip.prototype = { 78 | startDelay: function(e, text) { 79 | if(this.delayTimeout == null) { 80 | var that = this; 81 | var x = e.clientX; 82 | var y = e.clientY; 83 | this.delayTimeout = setTimeout(function() { 84 | that.show(x, y, text); 85 | }, this.delay); 86 | } 87 | }, 88 | // 由于外在数据可以作为事件监听器的一部分保存,因此没有必要使用一个中心数据库 89 | show: function(x, y, text) { 90 | clearTimeout(this.delayTimeout); 91 | this.delayTimeout = null; 92 | this.element.innerHTML = text; 93 | this.element.style.left = (x) + 'px'; 94 | this.element.style.top = (y + 20) + 'px'; 95 | this.element.style.display = 'block'; 96 | }, 97 | hide: function() { 98 | clearTimeout(this.delayTimeout); 99 | this.delayTimeout = null; 100 | this.element.style.display = 'none'; 101 | } 102 | }; 103 | // 上面的Tooltip类删除了原来的构造函数的所有参数以及注册事件的处理代码, 104 | // 而startDelay和show方法则各增加了一个新的参数,这样一来, 105 | // 要显示的文字就可以作为外在数据传给它们。 106 | 107 | /* TooltipManager singleton, a flyweight factory and manager. */ 108 | // 此例用一个单例同时扮演工厂和管理器的角色 109 | var TooltipManager = (function() { 110 | var storedInstance = null; 111 | 112 | /* Tooltip class, as a flyweight. */ 113 | // 把ToolTip放到单例中,就不能在别的地方被实例化。 114 | var Tooltip = function() { 115 | //todo 116 | }; 117 | Tooltip.prototype = { 118 | //todo 119 | }; 120 | 121 | return { 122 | addTooltip: function(targetElement, text) { 123 | // Get the tooltip object. 124 | var tt = this.getTooltip(); 125 | 126 | // Attach the events. 127 | addEvent(targetElement, 'mouseover', function(e) { tt.startDelay(e, text); }); 128 | addEvent(targetElement, 'mouseout', function(e) { tt.hide(); }); 129 | }, 130 | getTooltip: function() { 131 | if(storedInstance == null) { 132 | storedInstance = new Tooltip(); 133 | } 134 | return storedInstance; 135 | } 136 | }; 137 | })(); 138 | // 139 | 140 | 141 | /* Tooltip usage. */ 142 | 143 | TooltipManager.addTooltip($('link-id'), 'Lorem ipsum...'); 144 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Flyweight-Pattern/4 - Storing instances for later reuse.js: -------------------------------------------------------------------------------- 1 | /* DisplayModule interface. */ 2 | 3 | var DisplayModule = new Interface('DisplayModule', ['show', 'hide', 'state']); 4 | 5 | /* DialogBox class. */ 6 | 7 | var DialogBox = function() { // implements DisplayModule 8 | //todo 9 | }; 10 | DialogBox.prototype = { 11 | show: function(header, body, footer) { // Sets the content and shows the 12 | //todo // dialog box. 13 | }, 14 | hide: function() { // Hides the dialog box. 15 | //todo 16 | }, 17 | state: function() { // Returns 'visible' or 'hidden'; 18 | //todo 19 | } 20 | }; 21 | 22 | /* DialogBoxManager singleton. */ 23 | // 用一个单例来包装管理器需要的部件 24 | var DialogBoxManager = (function() { 25 | // 保存所生成的对话框的数据结构 26 | var created = []; // Stores created instances. 27 | 28 | return { 29 | // 用来显示对话框的方法 30 | displayDialogBox: function(header, body, footer) { 31 | var inUse = this.numberInUse(); // Find the number currently in use. 32 | // 这种技术类似于服务器语言中的SQL连接池,仅当现有连接都在使用当中时才会创建新连接 33 | if(inUse > created.length) { 34 | created.push(this.createDialogBox()); // Augment it if need be. 35 | } 36 | created[inUse].show(header, body, footer); // Show the dialog box. 37 | }, 38 | 39 | createDialogBox: function() { // Factory method. 40 | var db = new DialogBox(); 41 | return db; 42 | }, 43 | // 检查当前网页上正在使用的对话框的数目的方法 44 | numberInUse: function() { 45 | var inUse = 0; 46 | for(var i = 0, len = created.length; i < len; i++) { 47 | if(created[i].state() === 'visible') { 48 | inUse++; 49 | } 50 | } 51 | return inUse; 52 | } 53 | }; 54 | })(); 55 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Flyweight-Pattern/README.md: -------------------------------------------------------------------------------- 1 | ###享元模式
2 | 享元模式最适合于解决因创建大量类似对象而累及性能的问题。这种模式在Javascript中尤其有用,因为复杂的Javascript代码可能很快就会用光浏览器的所有可用内存。通过把大量独立对象转化为少量共享对象,可以降低运行Web应用程序所需的资源数量。对于那些可能一连用上几天也不会重新加载的大型应用系统,任何减少内存用量的技术都有非常显著的效果。而对于那些不会在浏览器中打开那么长时间的小型网页,内存的节约就没那么重要。
3 | 示例:汽车登记
4 | 内在状态和外在状态,用工厂进行实例化内在状态,封装在管理器中的外在状态。
5 | 6 | example 7 | 8 |
9 | 用单例来做封装这些数据的管理器优化方法是以复杂性为代价的。原有的只是一个类,而现在却变成了一个类和两个单例对象。
10 | 1、管理外在状态:
11 | 使用管理器对象进行管理。这种对象有一个集中管理的数据库,用于存放外在状态及其所属的享元对象。汽车登记那个实力就采用了这种方案。其优点在于简单、容易维护。
12 | 使用组合模式进行管理。可以使用对象自身的层次体系来保存信息,而不需要另外使用一个集中管理的数据库。组合对象的叶节点全都可以是享元对象,这样一来这些享元对象可以在组合对象层次体系中的多个地方被共享。
13 | 2、示例:Web日历
14 | 创建一个Web日历,演示用组合对象保存外在状态的具体做法。
15 | 没有把日期对象转换为享元的问题:你不得不为每一年创建365个CalendarDay对象。如果对象数目太多了,会给浏览器带来资源压力。更有效的做法是无论日历要显示多少年,都只用了一个CalendarDay对象来代表所有日期。所有把日期对象转换为享元进行优化。 16 |
17 | 18 | 19 | example 20 | 21 |
22 | 3、外在数据保存在哪里
23 | 组合对象的结构本身就已经包含了所有的外在数据。由于月份对象中的日期对象依次存放在一个数组中,所以它知道每一个日期对象的状态,从CalendarDay构造函数中剔除的两种数据都已经存在于CalendarMonth对象中。
24 | 这就是组合模式与享元模式配合得如此完美的原因。组合对象通常拥有大量叶对象,它还保存着许多可作为外在数据处理的数据。叶对象通常只包含极少的内在数据,所以很容易被转化为共享资源。
25 | 4、示例: 26 | 27 | 工具提示对象 28 | 29 |
30 | 在Javascript对象需要创建HTML内容这种情况下,享元模式特别有用。那种会生成DOM元素的对象如果数目众多的话,会占用过多内存,使网页陷入泥沼。采用享元模式后,只需创建少许这种对象即可,所有需要这种对象的地方都可以共享它们。工具提示就是一个典型的例子。
31 | 5、存实例供以后重用
32 | 模式对话框是享元模式的另一个适用场合。与工具提示一样,对话框对象也封装着数据和HTML内容。
33 | 因为运行期间需要用到的实例数目无法确定,所以不能对实例的个数加以限制,只能要用多少就创建多少,然后把它们保存起来供以后使用。
34 | 35 | DialogBox的例子 36 | 37 |
38 | 6、享元模式的适用场合:
39 | 网页中必须使用了大量资源密集型对象。如果只会用到少许这类对象,这种优化并不划算。
40 | 对象中所保存的数据至少有一部分能被转化为外在数据。此外,将这些数据存储在对象外部所占用的资源应该相对较少,否则这种做法对于性能的提示实际上毫无意义。那种大量包含基础性代码和HTML内容的对象可能比较适合这种优化。
41 | 将外在数据分离出去后,独一无二的对象的数目相对较少。
42 | 7、实现享元模式的一般步骤:
43 | 将所有外在数据从目标类剥离。具体做法是尽可能多地删除该类的属性,所删除的应该是那种因实例而异的属性。构造参数也要这样处理。这些参数应该被添加到该类的各个方法。
44 | 创建一个用来控制该类的实例化的工厂。用一个对象字面量保存每一个这类对象的引用,并以用来生成这些对象的参数的唯一性组合作为它们的索引。另一种方法是对象池技术。
45 | 创建一个用来保存外在数据的管理器。外在数据被保存在管理器内的一个数据结构中。管理器随后会根据需要将这些数据提供给共享对象的方法,其效果就如同该类有许多实例一样。
46 | 8、享元模式之利:
47 | 可以把网页的资源符合降低几个数量级。即使享元模式的应用无法将实例的个数削减到一个,你仍能够从中获益不少。
48 | 这种节省不需要大量修改原有代码。在创建了管理器、工厂和享元之后,就需要对代码进行的修改只不过是从直接实例化目标类改为调用管理器对象的某个方法。
49 | 9、享元模式之弊:
50 | 如果把它用在不必要的地方,其结果反而有损代码的运行效率。这种模式在优化代码的同时,也提高了其复杂程度,这会给调试和维护造成困难。
51 | 它之所以会妨碍调试,是因为现在可能出错的地方变成了三个:管理器、工厂和享元。
52 | 这种优化也会使维护变得更加困难。现在你面对的不是由封装着数据的对象构成的清晰架构,而是一堆又碎又乱的东西。其中的数据至少分两处保存。最好注释标明内在数据和外在数据。
53 | 只有在必要的时候才应该进行这种优化。必须在运行效率和可维护性之间进行权衡。如果拿不准是否需要使用享元模式,那么你很可能并不需要它。享元模式适合的是系统资源已经用得差不多而且明显需要进行某种优化这样一类场合。
54 | 这种模式对Javascript程序员特别有用,因为它可以用来减少网页上所要使用的DOM元素的数量,要知道这些元素需要耗费许多内存。结合使用这种模式与组合模式等组织型可以开发出功能丰富的复杂Web应用系统,它们可以平稳的运行在任何现代Javascript环境中。 -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Observer-Pattern/1 - Sellsian approach.js: -------------------------------------------------------------------------------- 1 | /* From http://pluralsight.com/blogs/dbox/archive/2007/01/24/45864.aspx */ 2 | 3 | /* 4 | * Publishers are in charge of "publishing" i.e. creating the event. 5 | * They're also in charge of "notifying" (firing the event). 6 | */ 7 | /** 8 | * 下面的例子发布者处于明显的主导地位 9 | * 它们负责登记其顾客,而且有权停止为其投送 10 | * 最后,新的报纸出版后它们将其投送给顾客 11 | */ 12 | var Publisher = new Observable; 13 | 14 | /* 15 | * Subscribers basically... "subscribe" (or listen). 16 | * Once they've been "notified" their callback functions are invoked. 17 | */ 18 | var Subscriber = function(news) { 19 | // news delivered directly to my front porch 20 | }; 21 | // 以一个代表订阅者的回调函数为参数,deliver方法在调用过程中将通过这些回调函数把数据发送给每一个订阅者。 22 | Publisher.subscribeCustomer(Subscriber); 23 | 24 | /* 25 | * Deliver a paper: 26 | * sends out the news to all subscribers. 27 | */ 28 | Publisher.deliver('extre, extre, read all about it'); 29 | 30 | /* 31 | * That customer forgot to pay his bill. 32 | */ 33 | Publisher.unSubscribeCustomer(Subscriber); 34 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Observer-Pattern/2 - Newspapers and subscribers.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Newspaper Vendors 3 | * setup as new Publisher objects 4 | */ 5 | /** 6 | * 这个例子中拥有订阅和退订权的一方变成了订阅者,负责发送数据的还是发布者一方。 7 | */ 8 | var NewYorkTimes = new Publisher; 9 | var AustinHerald = new Publisher; 10 | var SfChronicle = new Publisher; 11 | 12 | 13 | /* 14 | * People who like to read 15 | * (Subscribers) 16 | * 17 | * Each subscriber is set up as a callback method. 18 | * They all inherit from the Function prototype Object. 19 | */ 20 | var Joe = function(from) { 21 | console.log('Delivery from '+from+' to Joe'); 22 | }; 23 | var Lindsay = function(from) { 24 | console.log('Delivery from '+from+' to Lindsay'); 25 | }; 26 | var Quadaras = function(from) { 27 | console.log('Delivery from '+from+' to Quadaras '); 28 | }; 29 | 30 | /* 31 | * Here we allow them to subscribe to newspapers 32 | * which are the Publisher objects. 33 | * In this case Joe subscribes to the NY Times and 34 | * the Chronicle. Lindsay subscribes to NY Times 35 | * Austin Herald and Chronicle. And the Quadaras 36 | * respectfully subscribe to the Herald and the Chronicle 37 | */ 38 | // 订阅者拥有subscribe和unsubscribe方法。订阅者只是普通的回调函数,那两个方法是通过扩展Function的prototype而加入的。 39 | Joe. 40 | subscribe(NewYorkTimes). 41 | subscribe(SfChronicle); 42 | 43 | Lindsay. 44 | subscribe(AustinHerald). 45 | subscribe(SfChronicle). 46 | subscribe(NewYorkTimes); 47 | 48 | Quadaras. 49 | subscribe(AustinHerald). 50 | subscribe(SfChronicle); 51 | 52 | /* 53 | * Then at any given time in our application, our publishers can send 54 | * off data for the subscribers to consume and react to. 55 | */ 56 | // 发布者拥有deliver方法 57 | NewYorkTimes. 58 | deliver('Here is your paper! Direct from the Big apple'); 59 | AustinHerald. 60 | deliver('News'). 61 | deliver('Reviews'). 62 | deliver('Coupons'); 63 | SfChronicle. 64 | deliver('The weather is still chilly'). 65 | deliver('Hi Mom! I\'m writing a book'); 66 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Observer-Pattern/3 - Building an observer API.js: -------------------------------------------------------------------------------- 1 | // 首先需要一个发布者的构造函数,其中定义了一个类型为数组的属性,用来保存订阅者的引用 2 | function Publisher() { 3 | this.subscribers = []; 4 | } 5 | // 接下来所有的Publisher实例都应该能够投送数据,所以在prototype中添加deliver方法 6 | Publisher.prototype.deliver = function(data) { 7 | this.subscribers.forEach( 8 | function(fn) { 9 | // subscriber实际上是函数,所以可以按如下方式使用,实现回调 10 | fn(data); 11 | } 12 | ); 13 | // 返回this用作返回值,所以可以对该方法进行链式调用 14 | return this; 15 | }; 16 | // 给予订阅者订阅的能力 17 | Function.prototype.subscribe = function(publisher) { 18 | var that = this; 19 | var alreadyExists = publisher.subscribers.some( 20 | function(el) { 21 | if ( el === that ) { 22 | return; 23 | } 24 | } 25 | ); 26 | if ( !alreadyExists ) { 27 | publisher.subscribers.push(this); 28 | } 29 | return this; 30 | }; 31 | // 退订方法,该方法供订阅者用来停止对指定发布者的观察 32 | Function.prototype.unsubscribe = function(publisher) { 33 | var that = this; 34 | publisher.subscribers = publisher.subscribers.filter( 35 | function(el) { 36 | if ( el !== that ) { 37 | return el; 38 | } 39 | } 40 | ); 41 | return this; 42 | }; 43 | // 有些订阅者在监听到某种一次性的事件之后会在回调阶段立刻退订该事件,大致做法如下 44 | var publisherObject = new Publisher; 45 | 46 | var observerObject = function(data) { 47 | // process data 48 | console.log(data); 49 | // unsubscribe from this publisher 50 | arguments.callee.unsubscribe(publisherObject); 51 | }; 52 | 53 | observerObject.subscribe(publisherObject); 54 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Observer-Pattern/4 - Animation example.js: -------------------------------------------------------------------------------- 1 | // Publisher API 2 | // 使用前面的Publisher工具实现动画的三个事件 3 | var Animation = function(o) { 4 | this.onStart = new Publisher, 5 | this.onComplete = new Publisher, 6 | this.onTween = new Publisher; 7 | }; 8 | Animation. 9 | method('fly', function() { 10 | // begin animation 11 | this.onStart.deliver(); 12 | for (var i=0;i 1、观察者模式 2 | 观察者模式又称为发布者-订阅者模式(publisher-subscriber)。用Javascript的话来说,这种模式的实质就是你可以对程序中某个对象的状态进行观察,并且在其发生改变时能够得到通知。 3 | 观察者模式存在的两个角色:观察者和被观察者。这里我们成为发布者和订阅者。 4 |

1.1、示例:报纸的投送

5 | 发行方也是投送方(deliver)。一般说来,一个发行方很可能有许多订阅者,同样,一个订阅者也很可能会订阅多家报社的报纸。问题的关键在于,这是一种多读多的关系,需要一种高级的抽象策略,以便订阅者能够彼此独立地发生改变,而发行方能够接受任何有消费意向 的订阅者。 6 |

1.1.1、推与拉的比较

7 | 推:主动把报纸发送到订阅者的家门口。 8 | 拉:规模较小的本地报社可能会在订阅者家附近的街角提供自己的数据,供订阅者“拉”。 9 | <

1.1.2、模式的实践

10 | 订阅者:可以订阅和退订。 11 | 下面是一个展示发布者和订阅者之间的互动过程的 12 | 13 | 高层示例。 14 | 15 | 下面的 16 | 17 | 例子 18 | 处理的是同一类问题,但发布者和订阅者之间的互动方式有所不同。 19 |

1.2、构建观察者API

20 | 首先需要一个发布者Publisher的构造函数,其中定义了一个类型为数组的属性,用来保存订阅者的引用; 21 | 接下来所有的Publisher实例都应该能够投送数据,所以在prototype中添加deliver方法; 22 | 给予订阅者订阅的能力; 23 | 提供unsubscribe方法供订阅者停止对指定发布者的观察; 24 | 25 | example 26 | 27 |

1.3、现实生活中的观察者

28 | 观察着模式对于那种由许多Javascript程序员合作开发的大型程序特别有用,可以提高API灵活性,并行开发的多个实现能够彼此独立的进行修改。在富用户界面应用程序中,drag、drop、movedcomplete和tabSwitch都可能是令人感兴趣的事件,它们都是在普通浏览器事件的基础上抽象出来的可观察事件,可由发布者对象向其监听者广播。 29 |

1.4、示例:动画

30 | 动画的三个时刻:开始onStart,结束onComplete和进行中onTween。 31 | 32 | example 33 | 34 |

1.5、事件监听器也是观察者

35 | 事件处理器:是一种把事件传递给与其关联的函数的手段,而且这种模型中一种事件只能指定一个毁掉方法。 36 | 监听器模式:一个事件可以与几个监听器关联,每个监听器都能能独立于其他监听器而改变。 37 | 38 | example 39 | 40 |

1.6、观察着模式的适用场合

41 | 如果希望把人的行为和应用程序的行为分开,那么观察者模式正适用于这种场合。你可以直接监听click事件,不过这需要知道监听的是哪个元素,这样做的另一个弊端是你的实现与click事件直接绑定在了一起。更好的做法是:创建一个可观察的onTabChange对象,并且在特定事件发生时通知所有观察着。 42 |

1.7、观察者模式之利

43 | 观察者模式是开发基于行为的大型应用程序的有力手段。你可以削减为事件注册监听器的次数,让可观察对象借助一个事件监听器替你处理各种行为并将信息委托给它的所有订阅者,从而降低了内存消耗和提高互动性能。 44 |

1.8、观察者模式之弊

45 | 使用这种观察着接口的一个不利之处在于创建可观察对象所带来的加载时间开销。这可以通过惰性加载技术加以化解。 -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Proxy-Pattern/1 - PublicLibrary class from Chapter 3.js: -------------------------------------------------------------------------------- 1 | /* From chapter 3. */ 2 | 3 | var Publication = new Interface('Publication', ['getIsbn', 'setIsbn', 'getTitle', 4 | 'setTitle', 'getAuthor', 'setAuthor', 'display']); 5 | var Book = function(isbn, title, author) { ... } // implements Publication 6 | 7 | /* Library interface. */ 8 | // 创建一个图书馆接口 9 | var Library = new Interface('Library', ['findBooks', 'checkoutBook', 'returnBook']); 10 | 11 | /* PublicLibrary class. */ 12 | 13 | var PublicLibrary = function(books) { // implements Library 14 | this.catalog = {}; 15 | for(var i = 0, len = books.length; i < len; i++) { 16 | this.catalog[books[i].getIsbn()] = { book: books[i], available: true }; 17 | } 18 | }; 19 | PublicLibrary.prototype = { 20 | // 查书 21 | findBooks: function(searchString) { 22 | var results = []; 23 | for(var isbn in this.catalog) { 24 | if(!this.catalog.hasOwnProperty(isbn)) continue; 25 | if(searchString.match(this.catalog[isbn].getTitle()) || 26 | searchString.match(this.catalog[isbn].getAuthor())) { 27 | results.push(this.catalog[isbn]); 28 | } 29 | } 30 | return results; 31 | }, 32 | // 借书 33 | checkoutBook: function(book) { 34 | var isbn = book.getIsbn(); 35 | if(this.catalog[isbn]) { 36 | if(this.catalog[isbn].available) { 37 | this.catalog[isbn].available = false; 38 | return this.catalog[isbn]; 39 | } 40 | else { 41 | throw new Error('PublicLibrary: book ' + book.getTitle() + 42 | ' is not currently available.'); 43 | } 44 | } 45 | else { 46 | throw new Error('PublicLibrary: book ' + book.getTitle() + ' not found.'); 47 | } 48 | }, 49 | // 还书 50 | returnBook: function(book) { 51 | var isbn = book.getIsbn(); 52 | if(this.catalog[isbn]) { 53 | this.catalog[isbn].available = true; 54 | } 55 | else { 56 | throw new Error('PublicLibrary: book ' + book.getTitle() + ' not found.'); 57 | } 58 | } 59 | }; 60 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Proxy-Pattern/2 - PublicLibraryProxy class.js: -------------------------------------------------------------------------------- 1 | /* PublicLibraryProxy class, a useless proxy. */ 2 | // 一个没有实现任何访问控制的PublicLibrary类的代理 3 | var PublicLibraryProxy = function(catalog) { // implements Library 4 | this.library = new PublicLibrary(catalog); 5 | }; 6 | PublicLibraryProxy.prototype = { 7 | findBooks: function(searchString) { 8 | return this.library.findBooks(searchString); 9 | }, 10 | checkoutBook: function(book) { 11 | return this.library.checkoutBook(book); 12 | }, 13 | returnBook: function(book) { 14 | return this.library.returnBook(book); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Proxy-Pattern/3 - PublicLibraryVirtualProxy class.js: -------------------------------------------------------------------------------- 1 | /* PublicLibraryVirtualProxy class. */ 2 | // 假设PublicLibrary的实例化很慢,不能在网页加载的时候立即完成。我们可以为其创建一个虚拟代理。 3 | // 让它把PublicLibrary的实例化推迟到必要的时候 4 | var PublicLibraryVirtualProxy = function(catalog) { // implements Library 5 | // 把构造函数的参数保存起来,知道有方法被调用时才真正执行本体的实例化 6 | this.library = null; 7 | this.catalog = catalog; // Store the argument to the constructor. 8 | }; 9 | PublicLibraryVirtualProxy.prototype = { 10 | _initializeLibrary: function() { 11 | if(this.library === null) { 12 | this.library = new PublicLibrary(this.catalog); 13 | } 14 | }, 15 | findBooks: function(searchString) { 16 | this._initializeLibrary(); 17 | return this.library.findBooks(searchString); 18 | }, 19 | checkoutBook: function(book) { 20 | this._initializeLibrary(); 21 | return this.library.checkoutBook(book); 22 | }, 23 | returnBook: function(book) { 24 | this._initializeLibrary(); 25 | return this.library.returnBook(book); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Proxy-Pattern/4 - Page statistics example.js: -------------------------------------------------------------------------------- 1 | /* Manually making the calls. */ 2 | 3 | var xhrHandler = XhrManager.createXhrHandler(); 4 | 5 | /* Get the pageview statistics. */ 6 | 7 | var callback = { 8 | success: function(responseText) { 9 | var stats = eval('(' + responseText + ')'); // Parse the JSON data. 10 | displayPageviews(stats); // Display the stats on the page. 11 | }, 12 | failure: function(statusCode) { 13 | throw new Error('Asynchronous request for stats failed.'); 14 | } 15 | }; 16 | xhrHandler.request('GET', '/stats/getPageviews/?page=index.html', callback); 17 | 18 | /* Get the browser statistics. */ 19 | 20 | var callback = { 21 | success: function(responseText) { 22 | var stats = eval('(' + responseText + ')'); // Parse the JSON data. 23 | displayBrowserShare(stats); // Display the stats on the page. 24 | }, 25 | failure: function(statusCode) { 26 | throw new Error('Asynchronous request for stats failed.'); 27 | } 28 | }; 29 | xhrHandler.request('GET', '/stats/getBrowserShare/?page=index.html', callback); 30 | 31 | 32 | 33 | /* Using a remote proxy. */ 34 | // 把这些调用包装在一个对象中,这个对象应该展现出一个用来访问数据的原生Javascript接口。 35 | // 这样就不会有前例中那样多的重复性代码。 36 | // 这个对象需要实现那个Web服务中的5个方法,每个方法都会执行对Web服务的XHR调用以获取数据, 37 | // 然后将其提供给回调函数 38 | /* PageStats interface. */ 39 | // 定义Web服务的接口。其目的在于以后有需要的时候能够换用其他类型的代理 40 | var PageStats = new Interface('PageStats', ['getPageviews', 'getUniques', 41 | 'getBrowserShare', 'getTopSearchTerms', 'getMostVisitedPages']); 42 | 43 | /* StatsProxy singleton. */ 44 | // 定义远程代理StatsProxy本身 45 | var StatsProxy = function() { // implements PageStats 46 | 47 | /* Private attributes. */ 48 | 49 | var xhrHandler = XhrManager.createXhrHandler(); 50 | var urls = { 51 | pageviews: '/stats/getPageviews/', 52 | uniques: '/stats/getUniques/', 53 | browserShare: '/stats/getBrowserShare/', 54 | topSearchTerms: '/stats/getTopSearchTerms/', 55 | mostVisitedPages: '/stats/getMostVisitedPages/' 56 | }; 57 | 58 | /* Private methods. */ 59 | 60 | function xhrFailure() { 61 | throw new Error('StatsProxy: Asynchronous request for stats failed.'); 62 | } 63 | 64 | function fetchData(url, dataCallback, startDate, endDate, page) { 65 | var callback = { 66 | success: function(responseText) { 67 | var stats = eval('(' + responseText + ')'); 68 | dataCallback(stats); 69 | }, 70 | failure: xhrFailure 71 | }; 72 | 73 | var getVars = []; 74 | if(startDate != undefined) { 75 | getVars.push('startDate=' + encodeURI(startDate)); 76 | } 77 | if(endDate != undefined) { 78 | getVars.push('endDate=' + encodeURI(endDate)); 79 | } 80 | if(page != undefined) { 81 | getVars.push('page=' + page); 82 | } 83 | 84 | if(getVars.length > 0) { 85 | url = url + '?' + getVars.join('&'); 86 | } 87 | 88 | xhrHandler.request('GET', url, callback); 89 | } 90 | 91 | /* Public methods. */ 92 | 93 | return { 94 | getPageviews: function(callback, startDate, endDate, page) { 95 | fetchData(urls.pageviews, callback, startDate, endDate, page); 96 | }, 97 | getUniques: function(callback, startDate, endDate, page) { 98 | fetchData(urls.uniques, callback, startDate, endDate, page); 99 | }, 100 | getBrowserShare: function(callback, startDate, endDate, page) { 101 | fetchData(urls.browserShare, callback, startDate, endDate, page); 102 | }, 103 | getTopSearchTerms: function(callback, startDate, endDate, page) { 104 | fetchData(urls.topSearchTerms, callback, startDate, endDate, page); 105 | }, 106 | getMostVisitedPages: function(callback, startDate, endDate) { 107 | fetchData(urls.mostVisitedPages, callback, startDate, endDate); 108 | } 109 | }; 110 | }(); 111 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Proxy-Pattern/5 - General pattern for wrapping a web service.js: -------------------------------------------------------------------------------- 1 | /* WebserviceProxy class */ 2 | // 从上一个例子中提炼出一个更加通用的Web服务包装模式。 3 | // 不是一个单例,而是一个拥有构造函数的普通类,以便以后进行扩展。 4 | var WebserviceProxy = function() { 5 | this.xhrHandler = XhrManager.createXhrHandler(); 6 | }; 7 | WebserviceProxy.prototype = { 8 | // 请求错误的处理函数 9 | _xhrFailure: function(statusCode) { 10 | throw new Error('StatsProxy: Asynchronous request for stats failed.'); 11 | }, 12 | // 发送请求的函数 13 | _fetchData: function(url, dataCallback, getVars) { 14 | var that = this; 15 | var callback = { 16 | success: function(responseText) { 17 | var obj = eval('(' + responseText + ')'); 18 | dataCallback(obj); 19 | }, 20 | failure: that._xhrFailure 21 | }; 22 | 23 | var getVarArray = []; 24 | for(varName in getVars) { 25 | getVarArray.push(varName + '=' + getVars[varName]); 26 | } 27 | if(getVarArray.length > 0) { 28 | url = url + '?' + getVarArray.join('&'); 29 | } 30 | 31 | xhrHandler.request('GET', url, callback); 32 | } 33 | }; 34 | // 使用这个通用模式时,只需从WebserviceProxy派生一个子类,然后再借助_fetchData方法实现需要的方法即可 35 | // 如果把StatsProxy类实现为WebserviceProxy的子类,其结果大致如下: 36 | /* StatsProxy class, using WebserviceProxy. */ 37 | 38 | var StatsProxy = function() {}; // implements PageStats 39 | extend(StatsProxy, WebserviceProxy); 40 | 41 | /* Implement the needed methods. */ 42 | // 借助_fetchData方法实现需要的方法 43 | StatsProxy.prototype.getPageviews = function(callback, startDate, endDate, 44 | page) { 45 | this._fetchData('/stats/getPageviews/', callback, { 46 | 'startDate': startDate, 47 | 'endDate': endDate, 48 | 'page': page 49 | }); 50 | }; 51 | StatsProxy.prototype.getUniques = function(callback, startDate, endDate, 52 | page) { 53 | this._fetchData('/stats/getUniques/', callback, { 54 | 'startDate': startDate, 55 | 'endDate': endDate, 56 | 'page': page 57 | }); 58 | }; 59 | StatsProxy.prototype.getBrowserShare = function(callback, startDate, endDate, 60 | page) { 61 | this._fetchData('/stats/getBrowserShare/', callback, { 62 | 'startDate': startDate, 63 | 'endDate': endDate, 64 | 'page': page 65 | }); 66 | }; 67 | StatsProxy.prototype.getTopSearchTerms = function(callback, startDate, 68 | endDate, page) { 69 | this._fetchData('/stats/getTopSearchTerms/', callback, { 70 | 'startDate': startDate, 71 | 'endDate': endDate, 72 | 'page': page 73 | }); 74 | }; 75 | StatsProxy.prototype.getMostVisitedPages = function(callback, startDate, 76 | endDate) { 77 | this._fetchData('/stats/getMostVisitedPages/', callback, { 78 | 'startDate': startDate, 79 | 'endDate': endDate 80 | }); 81 | }; 82 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Proxy-Pattern/6 - Directory lookup example.js: -------------------------------------------------------------------------------- 1 | /* Directory interface. */ 2 | // 首先是没有使用代理的情况 3 | var Directory = new Interface('Directory', ['showPage']); 4 | 5 | /* PersonnelDirectory class, the Real Subject */ 6 | // 在构造函数中发送一个XHR请求以获取员工数据。 7 | var PersonnelDirectory = function(parent) { // implements Directory 8 | this.xhrHandler = XhrManager.createXhrHandler(); 9 | this.parent = parent; 10 | this.data = null; 11 | this.currentPage = null; 12 | 13 | var that = this; 14 | var callback = { 15 | // _configure方法在数据返回的时候被调用 16 | success: that._configure, 17 | failure: function() { 18 | throw new Error('PersonnelDirectory: failure in data retrieval.'); 19 | } 20 | } 21 | xhrHandler.request('GET', 'directoryData.php', callback); 22 | }; 23 | PersonnelDirectory.prototype = { 24 | // 生成HTML元素并向其中填入数据 25 | _configure: function(responseText) { 26 | this.data = eval('(' + reponseText + ')'); 27 | //todo 28 | this.currentPage = 'a'; 29 | }, 30 | showPage: function(page) { 31 | $('page-' + this.currentPage).style.display = 'none'; 32 | $('page-' + page).style.display = 'block'; 33 | this.currentPage = page; 34 | } 35 | }; 36 | // 上面的类在实例化的过程中会加载大量数据,如果在网页加载的时候实例化这个类 37 | // 那么每一个用户都不得不加载这些数据,即使他根本不使用员工目录。 38 | // 代理的作用就是推迟这个实例化过程 39 | /* DirectoryProxy class, just the outline. */ 40 | // 首先勾勒出虚拟代理的大体轮廓,它包含了该类需要的所有方法。 41 | var DirectoryProxy = function(parent) { // implements Directory 42 | 43 | }; 44 | DirectoryProxy.prototype = { 45 | showPage: function(page) { 46 | 47 | } 48 | }; 49 | 50 | /* DirectoryProxy class, as a useless proxy. */ 51 | // 下一步是先将这个类实现为一个无用的代理,它的每个方法所做的只是调用本体的同名方法 52 | var DirectoryProxy = function(parent) { // implements Directory 53 | this.directory = new PersonnelDirectory(parent); 54 | }; 55 | DirectoryProxy.prototype = { 56 | showPage: function(page) { 57 | return this.directory.showPage(page); 58 | } 59 | }; 60 | 61 | /* DirectoryProxy class, as a virtual proxy. */ 62 | // 要想发挥虚拟代理的作用,需要创建一个用来实例化本体的方法,并注册一个用来触发这个实例化过程的事件监听器。 63 | var DirectoryProxy = function(parent) { // implements Directory 64 | this.parent = parent; 65 | this.directory = null; 66 | var that = this; 67 | // 一旦用户把鼠标指针移到目录的父容器上方,就调用_initialize方法实例化其本体。 68 | addEvent(parent, 'mouseover', that._initialize); // Initialization trigger. 69 | }; 70 | DirectoryProxy.prototype = { 71 | // DirectoryProxy类的构造函数不再实例化本体,而是把这个工作推迟到_initialize中进行。 72 | _initialize: function() { 73 | this.directory = new PersonnelDirectory(this.parent); 74 | }, 75 | showPage: function(page) { 76 | return this.directory.showPage(page); 77 | } 78 | }; 79 | 80 | /* DirectoryProxy class, with loading message. */ 81 | // 剩下的任务就是提示用户当前正在加载员工目录 82 | var DirectoryProxy = function(parent) { // implements Directory 83 | this.parent = parent; 84 | this.directory = null; 85 | this.warning = null; 86 | this.interval = null; 87 | this.initialized = false; 88 | var that = this; 89 | addEvent(parent, 'mouseover', that._initialize); // Initialization trigger. 90 | }; 91 | DirectoryProxy.prototype = { 92 | _initialize: function() { 93 | this.warning = document.createElement('div'); 94 | this.parent.appendChild(this.warning); 95 | this.warning.innerHTML = 'The company directory is loading...'; 96 | 97 | this.directory = new PersonnelDirectory(this.parent); 98 | var that = this; 99 | // 每隔100毫秒检查一次是否加载完毕 100 | this.interval = setInterval(that._checkInitialization, 100); 101 | }, 102 | // 判断是否加载完毕,加载完毕则设置initialized为true 103 | _checkInitialization: function() { 104 | if(this.directory.currentPage != null) { 105 | clearInterval(this.interval); 106 | this.initialized = true; 107 | // 加载完毕则移除提示信息 108 | this.parent.removeChild(this.warning); 109 | } 110 | }, 111 | showPage: function(page) { 112 | if(!this.initialized) { 113 | return; 114 | } 115 | return this.directory.showPage(page); 116 | } 117 | }; 118 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Proxy-Pattern/7 - General pattern for creating a virtual proxy.js: -------------------------------------------------------------------------------- 1 | /* DynamicProxy abstract class, incomplete. */ 2 | // 创建动态代理类的壳体以及_initialize和_checkInitialization这两个方法。 3 | var DynamicProxy = function() { 4 | this.args = arguments; 5 | this.initialized = false; 6 | }; 7 | DynamicProxy.prototype = { 8 | // 触发本体实例化过程,它可以被关联到各种触发器或条件 9 | _initialize: function() { 10 | this.subject = {}; // Instantiate the class. 11 | this.class.apply(this.subject, this.args); 12 | this.subject.__proto__ = this.class.prototype; 13 | 14 | var that = this; 15 | this.interval = setInterval(function() { that._checkInitialization(); }, 100); 16 | }, 17 | // 每隔一段预定的时间会被调用一次 18 | _checkInitialization: function() { 19 | // 调用_isInitialized方法判断是否初始化好了 20 | if(this._isInitialized()) { 21 | clearInterval(this.interval); 22 | this.initialized = true; 23 | } 24 | }, 25 | _isInitialized: function() { // Must be implemented in the subclass. 26 | throw new Error('Unsupported operation on an abstract class.'); 27 | } 28 | }; 29 | 30 | /* DynamicProxy abstract class, complete. */ 31 | // 现在正在构造函数中添加一些代码,以便针对本体类中的每一个方法为代理创建一个相应的方法。 32 | var DynamicProxy = function() { 33 | this.args = arguments; 34 | this.initialized = false; 35 | 36 | if(typeof this.class != 'function') { 37 | throw new Error('DynamicProxy: the class attribute must be set before ' + 38 | 'calling the super-class constructor.'); 39 | } 40 | 41 | // Create the methods needed to implement the same interface. 42 | for(var key in this.class.prototype) { 43 | // Ensure that the property is a function. 44 | if(typeof this.class.prototype[key] !== 'function') { 45 | continue; 46 | } 47 | 48 | // Add the method. 49 | var that = this; 50 | (function(methodName) { 51 | that[methodName] = function() { 52 | if(!that.initialized) { 53 | return 54 | } 55 | return that.subject[methodName].apply(that.subject, arguments); 56 | }; 57 | })(key); 58 | } 59 | }; 60 | DynamicProxy.prototype = { 61 | _initialize: function() { 62 | this.subject = {}; // Instantiate the class. 63 | this.class.apply(this.subject, this.args); 64 | this.subject.__proto__ = this.class.prototype; 65 | 66 | var that = this; 67 | this.interval = setInterval(function() { that._checkInitialization(); }, 100); 68 | }, 69 | _checkInitialization: function() { 70 | if(this._isInitialized()) { 71 | clearInterval(this.interval); 72 | this.initialized = true; 73 | } 74 | }, 75 | _isInitialized: function() { // Must be implemented in the subclass. 76 | throw new Error('Unsupported operation on an abstract class.'); 77 | } 78 | }; 79 | 80 | /* TestProxy class. */ 81 | // 创建TestClass的代理 82 | // 在子类中必须要做的事有4件: 83 | // 将this.class设置为本体类 84 | // 创建某种实例化触发器(本例的设计是在点击一个链接时进行实例化) 85 | // 调用超类的构造函数(就像所有子类都要做的那样) 86 | // 实现_isInitialized方法(根据本体是否已经初始化返回true或false) 87 | var TestProxy = function() { 88 | // 将this.class设置为本体类 89 | this.class = TestClass; 90 | var that = this; 91 | // 创建某种实例化触发器 92 | addEvent($('test-link'), 'click', function() { that._initialize(); }); 93 | // Initialization trigger. 94 | // 调用超类的构造函数 95 | TestProxy.superclass.constructor.apply(this, arguments); 96 | }; 97 | extend(TestProxy, DynamicProxy); 98 | // 实现_isInitialized方法 99 | TestProxy.prototype._isInitialized = function() { 100 | //todo // Initialization condition goes here. 101 | }; 102 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Singleton-Pattern/1 - Basic structure of the singleton.js: -------------------------------------------------------------------------------- 1 | /* Basic Singleton. */ 2 | 3 | var Singleton = { 4 | attribute1: true, 5 | attribute2: 10, 6 | 7 | method1: function() { 8 | 9 | }, 10 | method2: function(arg) { 11 | 12 | } 13 | }; 14 | 15 | Singleton.attribute1 = false; 16 | var total = Singleton.attribute2 + 5; 17 | var result = Singleton.method1(); 18 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Singleton-Pattern/2 - Namespacing.js: -------------------------------------------------------------------------------- 1 | /* Declared globally. 2 | * 用作命名空间:将自己的所有代码组织在一个全局变量名下,方便日后维护,示例程序 3 | * */ 4 | 5 | function findProduct(id) { 6 | //todo 7 | } 8 | 9 | 10 | 11 | // Later in your page, another programmer adds... 12 | var resetProduct = $('reset-product-button'); 13 | var findProduct = $('find-product-button'); // The findProduct function just got 14 | // overwritten. 15 | 16 | 17 | /* Using a namespace. */ 18 | 19 | var MyNamespace = { 20 | findProduct: function(id) { 21 | //todo ... 22 | } 23 | // Other methods can go here as well. 24 | }; 25 | 26 | // Later in your page, another programmer adds... 27 | var resetProduct = $('reset-product-button'); 28 | var findProduct = $('find-product-button'); // Nothing was overwritten. 29 | 30 | /* GiantCorp namespace. */ 31 | var GiantCorp = {}; 32 | 33 | GiantCorp.Common = { 34 | // A singleton with common methods used by all objects and modules. 35 | }; 36 | 37 | GiantCorp.ErrorCodes = { 38 | // An object literal used to store data. 39 | }; 40 | 41 | GiantCorp.PageHandler = { 42 | // A singleton with page specific methods and attributes. 43 | }; 44 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Singleton-Pattern/3 - Wrappers for page specific code.js: -------------------------------------------------------------------------------- 1 | /* Generic Page Object. */ 2 | 3 | Namespace.PageName = { 4 | 5 | // Page constants. 6 | CONSTANT_1: true, 7 | CONSTANT_2: 10, 8 | 9 | // Page methods. 10 | method1: function() { 11 | 12 | }, 13 | method2: function() { 14 | 15 | }, 16 | 17 | // Initialization method. 18 | init: function() { 19 | 20 | } 21 | } 22 | 23 | // Invoke the initialization method after the page loads. 24 | addLoadEvent(Namespace.PageName.init); 25 | 26 | 27 | var GiantCorp = window.GiantCorp || {}; 28 | 29 | /* RegPage singleton, page handler object. */ 30 | 31 | GiantCorp.RegPage = { 32 | 33 | // Constants. 34 | FORM_ID: 'reg-form', 35 | OUTPUT_ID: 'reg-results', 36 | 37 | // Form handling methods. 38 | handleSubmit: function(e) { 39 | e.preventDefault(); // Stop the normal form submission. 40 | 41 | var data = {}; 42 | var inputs = GiantCorp.RegPage.formEl.getElementsByTagName('input'); 43 | 44 | // Collect the values of the input fields in the form. 45 | for(var i = 0, len = inputs.length; i < len; i++) { 46 | data[inputs[i].name] = inputs[i].value; 47 | } 48 | 49 | // Send the form values back to the server. 50 | GiantCorp.RegPage.sendRegistration(data); 51 | }, 52 | sendRegistration: function(data) { 53 | // Make an XHR request and call displayResult() when the response is 54 | // received. 55 | //todo ... 56 | }, 57 | displayResult: function(response) { 58 | // Output the response directly into the output element. We are 59 | // assuming the server will send back formatted HTML. 60 | GiantCorp.RegPage.outputEl.innerHTML = response; 61 | }, 62 | 63 | // Initialization method. 64 | init: function() { 65 | // Get the form and output elements. 66 | GiantCorp.RegPage.formEl = $(GiantCorp.RegPage.FORM_ID); 67 | GiantCorp.RegPage.outputEl = $(GiantCorp.RegPage.OUTPUT_ID); 68 | 69 | // Hijack the form submission. 70 | addEvent(GiantCorp.RegPage.formEl, 'submit', GiantCorp.RegPage.handleSubmit); 71 | } 72 | }; 73 | 74 | // Invoke the initialization method after the page loads. 75 | addLoadEvent(GiantCorp.RegPage.init); 76 | 77 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Singleton-Pattern/4 - Private methods with underscores.js: -------------------------------------------------------------------------------- 1 | /* DataParser singleton, converts character delimited strings into arrays. */ 2 | 3 | GiantCorp.DataParser = { 4 | // Private methods. 5 | _stripWhitespace: function(str) { 6 | return str.replace(/\s+/, ''); 7 | }, 8 | _stringSplit: function(str, delimiter) { 9 | return str.split(delimiter); 10 | }, 11 | // Public method. 12 | stringToArray: function(str, delimiter, stripWS) { 13 | if(stripWS) { 14 | str = this._stripWhitespace(str); // 在单例模式中,推荐使用 GiantCorp.DataParser._stripWhitespace() 这种调用方式,防止弄错了this作用域 15 | } 16 | var outputArray = this._stringSplit(str, delimiter); 17 | return outputArray; 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Singleton-Pattern/5 - Private methods with closures.js: -------------------------------------------------------------------------------- 1 | /* Singleton as an Object Literal. */ 2 | 3 | MyNamespace.Singleton = {}; 4 | 5 | /* Singleton with Private Members, step 1. */ 6 | 7 | MyNamespace.Singleton = (function() { 8 | return {}; 9 | })(); 10 | 11 | /* Singleton with Private Members, step 2. */ 12 | 13 | MyNamespace.Singleton = (function() { 14 | return { // Public members. 15 | publicAttribute1: true, 16 | publicAttribute2: 10, 17 | 18 | publicMethod1: function() { 19 | //todo 20 | }, 21 | publicMethod2: function(args) { 22 | //todo 23 | } 24 | }; 25 | })(); 26 | 27 | /* Singleton with Private Members, step 3. */ 28 | 29 | MyNamespace.Singleton = (function() { 30 | // Private members. 31 | var privateAttribute1 = false; 32 | var privateAttribute2 = [1, 2, 3]; 33 | 34 | function privateMethod1() { 35 | //todo 36 | } 37 | function privateMethod2(args) { 38 | //todo 39 | } 40 | 41 | return { // Public members. 42 | publicAttribute1: true, 43 | publicAttribute2: 10, 44 | 45 | publicMethod1: function() { 46 | //todo 47 | }, 48 | publicMethod2: function(args) { 49 | //todo 50 | } 51 | }; 52 | })(); 53 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Singleton-Pattern/6 - Comparing the two techniques.js: -------------------------------------------------------------------------------- 1 | /* DataParser singleton, converts character delimited strings into arrays. */ 2 | /* Now using true private methods. */ 3 | 4 | GiantCorp.DataParser = (function() { 5 | // Private attributes. 6 | var whitespaceRegex = /\s+/; 7 | 8 | // Private methods. 9 | function stripWhitespace(str) { 10 | return str.replace(whitespaceRegex, ''); 11 | } 12 | function stringSplit(str, delimiter) { 13 | return str.split(delimiter); 14 | } 15 | 16 | // Everything returned in the object literal is public, but can access the 17 | // members in the closure created above. 18 | return { 19 | // Public method. 20 | stringToArray: function(str, delimiter, stripWS) { 21 | if(stripWS) { 22 | str = stripWhitespace(str); 23 | } 24 | var outputArray = stringSplit(str, delimiter); // 这里不再使用 this. 或 GiantCorp.DataParser. 这些前缀只用于访问单例对象的公用属性 25 | return outputArray; 26 | } 27 | }; 28 | })(); // Invoke the function and assign the returned object literal to 29 | // GiantCorp.DataParser. 30 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Singleton-Pattern/7 - Lazy instantiation.js: -------------------------------------------------------------------------------- 1 | /* Singleton with Private Members, step 3. */ 2 | /** 3 | * 惰性加载:在大型或复杂的项目中,起到了优化的作用:那些开销较大却很少用到的组件可以被包装到惰性加载单例中,示例程序: 4 | */ 5 | 6 | MyNamespace.Singleton = (function() { 7 | // Private members. 8 | var privateAttribute1 = false; 9 | var privateAttribute2 = [1, 2, 3]; 10 | 11 | function privateMethod1() { 12 | //todo 13 | } 14 | function privateMethod2(args) { 15 | //todo 16 | } 17 | 18 | return { // Public members. 19 | publicAttribute1: true, 20 | publicAttribute2: 10, 21 | 22 | publicMethod1: function() { 23 | //todo 24 | }, 25 | publicMethod2: function(args) { 26 | //todo. 27 | } 28 | }; 29 | })(); 30 | 31 | /* General skeleton for a lazy loading singleton, step 1. */ 32 | 33 | MyNamespace.Singleton = (function() { 34 | 35 | function constructor() { // All of the normal singleton code goes here. 36 | // Private members. 37 | var privateAttribute1 = false; 38 | var privateAttribute2 = [1, 2, 3]; 39 | 40 | function privateMethod1() { 41 | //todo 42 | } 43 | function privateMethod2(args) { 44 | //todo 45 | } 46 | 47 | return { // Public members. 48 | publicAttribute1: true, 49 | publicAttribute2: 10, 50 | 51 | publicMethod1: function() { 52 | //todo 53 | }, 54 | publicMethod2: function(args) { 55 | //todo 56 | } 57 | } 58 | } 59 | 60 | })(); 61 | 62 | /* General skeleton for a lazy loading singleton, step 2. */ 63 | 64 | MyNamespace.Singleton = (function() { 65 | 66 | function constructor() { // All of the normal singleton code goes here. 67 | //todo 68 | } 69 | 70 | return { 71 | getInstance: function() { 72 | // Control code goes here. 73 | } 74 | } 75 | })(); 76 | 77 | /* General skeleton for a lazy loading singleton, step 3. */ 78 | 79 | MyNamespace.Singleton = (function() { 80 | 81 | var uniqueInstance; // Private attribute that holds the single instance. 82 | 83 | function constructor() { // All of the normal singleton code goes here. 84 | //todo 85 | } 86 | 87 | return { 88 | getInstance: function() { 89 | if(!uniqueInstance) { // Instantiate only if the instance doesn't exist. 90 | uniqueInstance = constructor(); 91 | } 92 | return uniqueInstance; 93 | } 94 | } 95 | })(); 96 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Singleton-Pattern/8 - Branching.js: -------------------------------------------------------------------------------- 1 | /* Branching Singleton (skeleton). */ 2 | 3 | MyNamespace.Singleton = (function() { 4 | var objectA = { 5 | method1: function() { 6 | //todo 7 | }, 8 | method2: function() { 9 | //todo 10 | } 11 | }; 12 | var objectB = { 13 | method1: function() { 14 | //todo 15 | }, 16 | method2: function() { 17 | //todo 18 | } 19 | }; 20 | 21 | return (someCondition) ? objectA : objectB; 22 | })(); 23 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Singleton-Pattern/9 - Creating XHR objects with branching.js: -------------------------------------------------------------------------------- 1 | /* SimpleXhrFactory singleton, step 1. */ 2 | 3 | var SimpleXhrFactory = (function() { 4 | 5 | // The three branches. 6 | var standard = { 7 | createXhrObject: function() { 8 | return new XMLHttpRequest(); 9 | } 10 | }; 11 | var activeXNew = { 12 | createXhrObject: function() { 13 | return new ActiveXObject('Msxml2.XMLHTTP'); 14 | } 15 | }; 16 | var activeXOld = { 17 | createXhrObject: function() { 18 | return new ActiveXObject('Microsoft.XMLHTTP'); 19 | } 20 | }; 21 | 22 | })(); 23 | 24 | /* SimpleXhrFactory singleton, step 2. */ 25 | 26 | var SimpleXhrFactory = (function() { 27 | 28 | // The three branches. 29 | var standard = { 30 | createXhrObject: function() { 31 | return new XMLHttpRequest(); 32 | } 33 | }; 34 | var activeXNew = { 35 | createXhrObject: function() { 36 | return new ActiveXObject('Msxml2.XMLHTTP'); 37 | } 38 | }; 39 | var activeXOld = { 40 | createXhrObject: function() { 41 | return new ActiveXObject('Microsoft.XMLHTTP'); 42 | } 43 | }; 44 | 45 | // To assign the branch, try each method; return whatever doesn't fail. 46 | var testObject; 47 | try { 48 | testObject = standard.createXhrObject(); 49 | return standard; // Return this if no error was thrown. 50 | } 51 | catch(e) { 52 | try { 53 | testObject = activeXNew.createXhrObject(); 54 | return activeXNew; // Return this if no error was thrown. 55 | } 56 | catch(e) { 57 | try { 58 | testObject = activeXOld.createXhrObject(); 59 | return activeXOld; // Return this if no error was thrown. 60 | } 61 | catch(e) { 62 | throw new Error('No XHR object found in this environment.'); 63 | } 64 | } 65 | } 66 | 67 | })(); 68 | -------------------------------------------------------------------------------- /JavaScript-Design-Patterns/The-Singleton-Pattern/README.md: -------------------------------------------------------------------------------- 1 |

单例模式

2 |
3 | 应用场景
4 | 使用单例模式可以用来创建命名空间以减少全局变量的数目,而全局变量也很容易被改写,不安全。描述性的命名空间也可以增强代码的说明性,便于理解;
5 | 把相关方法和属性组织到一个不会被多次实例化的单例中,更好的维护和调试代码;
6 | 惰性加载使对象被使用的时候才创建,减少不需要使用时消耗了不必要的内存或带宽;
7 | 分支技术可创建高效的方法,根据运行时的条件确定赋予单例变量的对象字面量,可以创建出为特定环境量身定制的方法,这种方法不会再每次调用时都再次浪费时间去检查运行环境。
8 | 缺点:
9 | 提供的是一种单点访问,导致模块间的强耦合,不利于单元测试。单例最好还是留给定义命名空间和实现分支方法这些用途
-------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | Version 1.0.0 3 | -------------------------------------------------------------------------------- 4 | 5 | This folder contains the code examples from the book "Pro JavaScript Design 6 | Patterns" by Ross Harmes and Dustin Diaz. 7 | 8 | The latest version of this code can always be downloaded at: 9 | 10 | http://jsdesignpatterns.com/code.zip 11 | 12 | and at the Apress website: 13 | 14 | http://apress.com/book/view/159059908x 15 | 16 | Questions and corrections can be sent to ross@jsdesignpatterns.com and 17 | dustin@jsdesignpatterns.com. -------------------------------------------------------------------------------- /Recommend/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wchaowu/javascript/0d72512bb6457e819a52822512da95dc53b3623c/Recommend/README.md -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ​ 61 | 62 | -------------------------------------------------------------------------------- /javascript-based/crossDomain.html: -------------------------------------------------------------------------------- 1 | ajax:XMLHttpRequest();不能跨域
2 |
3 | 跨域的方法
4 | 1.document.domain="a.com"
5 | 2.服务器代理:XMLHttpRequest代理文件
6 | 3.script 标签:jsonp
7 | 4.location.hash (iframe)
8 | 5.window.name (iframe)
9 | 6 flash
10 | 7 html5 postMessage -------------------------------------------------------------------------------- /javascript-based/event.html: -------------------------------------------------------------------------------- 1 | 如果你有10个按钮在同一个div下面 2 | 请直接在这个div上面监听事件,而不要监听每个button按钮 3 | 4 | 事件的代理 5 |
6 | 
7 | 
-------------------------------------------------------------------------------- /javascript-based/namespace/README.md: -------------------------------------------------------------------------------- 1 |

javascript命名空间

2 | --------------------------- 3 | 几乎所有重要的 Javascript 程序中都会用到命名空间。除非我们只是编写简单的代码,否则尽力确保正确地实现命名空间是很有必要的。这也能避免自己的代码收到第三方代码的污染。本小节将阐述以下命名空间的作用和方式 4 | 5 | ---------- 6 | >单一全局变量 7 | > 8 | > 9 | >对象序列化的表示 10 | > 11 | >命名空间注入 12 | > 13 | >即时调用的函数表达式 14 | > 15 | >内嵌的命名空间 16 | > 17 | >嵌套空间 18 | 19 | *代码可以参考namespace1.HTML和namespace.HTML 文件* -------------------------------------------------------------------------------- /javascript-based/namespace/namespace.HTML: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 命名空间的注入 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /javascript-based/namespace/namespace1.HTML: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 自动嵌套命名空间 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 单一全局变量 13 | 23 | 24 | 前缀命名空间 25 | 32 | 33 | 我们就简单的介绍过IIFE (即时调用的函数表达式) ,它是一个未命名的函数,在它被定义之后就会立即执行。 34 | 如果听起来觉得耳熟,是因为你以前遇到过并将它称之为自动生效的(或者自动调用的)匿名函数,然而我个人更认为 Ben Alman的 IIFE 命名更准确。在JavaScript中,因为在一个作用域中显示定义的变量和函数只能在作用域中可见, 35 | 函数调用为实现隐私提供了简单的方式。 36 | 37 | 47 | 嵌套命名空间 48 | 61 | 62 | -------------------------------------------------------------------------------- /javascript-based/reference.js: -------------------------------------------------------------------------------- 1 | /*正常的情况*/ 2 | var a = 5; 3 | var b = a; 4 | 5 | b += 3; 6 | 7 | alert(b); //8 8 | alert(a); //5 9 | 10 | 11 | 12 | //对象和函数都是引用的关系 13 | var a = [1,2,3]; 14 | var b = a; 15 | 16 | b.push(4); 17 | 18 | alert(b); //1,2,3,4 19 | alert(a); //1,2,3,4*/ 20 | 21 | //数组重新写 22 | var a = [1,2,3]; 23 | var b = a; 24 | b = [1,2,3,4]; 25 | 26 | alert(b); //1,2,3,4 27 | alert(a); //1,2,3 28 | 29 | /*对象赋值*/ 30 | var obj = { 31 | a : 10 32 | }; 33 | var obj2 = obj; 34 | 35 | obj2.a = 20; 36 | 37 | alert(obj.a); //20*/ 38 | 39 | //赋值解决方案 40 | var obj = { 41 | a : 10 42 | }; 43 | 44 | function copy(obj){ //浅拷贝 45 | 46 | var newObj = {}; 47 | 48 | for(var attr in obj){ 49 | newObj[attr] = obj[attr]; 50 | } 51 | 52 | return newObj; 53 | 54 | } 55 | 56 | var obj2 = copy(obj); 57 | 58 | obj2.a = 20; 59 | 60 | alert(obj.a); //10 61 | 62 | //解决方案二 63 | function deepCopy(obj){ //深拷贝 64 | 65 | if(typeof obj != 'object'){ 66 | console.trace(); 67 | return obj; 68 | } 69 | 70 | var newObj = {}; 71 | 72 | for(var attr in obj){ 73 | newObj[attr] = deepCopy(obj[attr]); 74 | } 75 | 76 | return newObj; 77 | 78 | } 79 | 80 | var obj2 = deepCopy(obj); 81 | 82 | obj2.a.b = 20; 83 | 84 | alert(obj.a.b); //10 -------------------------------------------------------------------------------- /javascript-based/urlHelper: -------------------------------------------------------------------------------- 1 | // This function creates a new anchor element and uses location 2 | // properties (inherent) to get the desired URL data. Some String 3 | // operations are used (to normalize results across browsers). 4 | 5 | function parseURL(url) { 6 | var a = document.createElement('a'); 7 | a.href = url; 8 | return { 9 | source: url, 10 | protocol: a.protocol.replace(':',''), 11 | host: a.hostname, 12 | port: a.port, 13 | query: a.search, 14 | params: (function(){ 15 | var ret = {}, 16 | seg = a.search.replace(/^\?/,'').split('&'), 17 | len = seg.length, i = 0, s; 18 | for (;i 50) throw new Error('Book: Only 50 instances of Book can be ' 44 | + 'created.'); 45 | 46 | this.setIsbn(newIsbn); 47 | this.setTitle(newTitle); 48 | this.setAuthor(newAuthor); 49 | } 50 | })(); 51 | 52 | // Public static method. 53 | Book.convertToTitleCase = function(inputString) { 54 | //todo 55 | }; 56 | 57 | // Public, non-privileged methods. 58 | Book.prototype = { 59 | display: function() { 60 | //todo 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /object-oriented/Encapsulation-and-Information-Hiding/7 - Constants.js: -------------------------------------------------------------------------------- 1 | var Class = (function() { 2 | 3 | // Constants (created as private static attributes). 4 | var UPPER_BOUND = 100; 5 | 6 | // Privileged static method. 7 | this.getUPPER_BOUND = function(){ 8 | return UPPER_BOUND; 9 | } 10 | 11 | //todo 12 | 13 | // Return the constructor. 14 | return function(constructorArgument) { 15 | //todo 16 | } 17 | })(); 18 | 19 | 20 | /* Grouping constants together. */ 21 | 22 | var Class = (function() { 23 | 24 | // Private static attributes. 25 | var constants = { 26 | UPPER_BOUND: 100, 27 | LOWER_BOUND: -100 28 | } 29 | 30 | // Privileged static method. 31 | this.getConstant=function(name) { 32 | return constants[name]; 33 | } 34 | 35 | //todo 36 | 37 | // Return the constructor. 38 | return function(constructorArgument) { 39 | //todo 40 | } 41 | })(); 42 | 43 | 44 | /* Usage. */ 45 | 46 | Class.getConstant('UPPER_BOUND'); 47 | -------------------------------------------------------------------------------- /object-oriented/Expressive-JavaScript/1 - The flexibility of JavaScript.js: -------------------------------------------------------------------------------- 1 | /* below is five method to do the same thing */ 2 | /* a. Start and stop animations using functions. */ 3 | 4 | function startAnimation() { 5 | //todo 6 | } 7 | 8 | function stopAnimation() { 9 | //todo 10 | } 11 | 12 | 13 | 14 | /* b. Anim class. */ 15 | 16 | var Anim = function() { 17 | //todo 18 | }; 19 | Anim.prototype.start = function() { 20 | //todo 21 | }; 22 | Anim.prototype.stop = function() { 23 | //todo 24 | }; 25 | 26 | /* Usage. */ 27 | 28 | var myAnim = new Anim(); 29 | myAnim.start(); 30 | //todo 31 | myAnim.stop(); 32 | 33 | 34 | 35 | /* c. Anim class, with a slightly different syntax for declaring methods. */ 36 | 37 | var Anim = function() { 38 | //todo 39 | }; 40 | Anim.prototype = { 41 | start: function() { 42 | //todo 43 | }, 44 | stop: function() { 45 | //todo 46 | } 47 | }; 48 | 49 | 50 | 51 | /* d. Add a method to the Function class that can be used to declare methods. */ 52 | 53 | Function.prototype.method = function(name, fn) { 54 | this.prototype[name] = fn; 55 | }; 56 | 57 | /* Anim class, with methods created using a convenience method. */ 58 | 59 | var Anim = function() { 60 | //todo 61 | }; 62 | Anim.method('start', function() { 63 | //todo 64 | }); 65 | Anim.method('stop', function() { 66 | //todo 67 | }); 68 | 69 | 70 | 71 | /* e. This version allows the calls to be chained. */ 72 | 73 | Function.prototype.method = function(name, fn) { 74 | this.prototype[name] = fn; 75 | return this; 76 | }; 77 | 78 | /* Anim class, with methods created using a convenience method and chaining. */ 79 | 80 | var Anim = function() { 81 | //todo 82 | }; 83 | Anim. 84 | method('start', function() { 85 | //todo 86 | }). 87 | method('stop', function() { 88 | //todo 89 | }); 90 | -------------------------------------------------------------------------------- /object-oriented/Expressive-JavaScript/2 - Functions as first-class objects.js: -------------------------------------------------------------------------------- 1 | /* An anonymous function, executed immediately. */ 2 | 3 | (function() { 4 | var foo = 10; 5 | var bar = 2; 6 | alert(foo * bar); 7 | })(); 8 | 9 | 10 | /* An anonymous function with arguments. */ 11 | 12 | (function(foo, bar) { 13 | alert(foo * bar); 14 | })(10, 2); 15 | 16 | 17 | /* An anonymous function that returns a value. */ 18 | 19 | var baz = (function(foo, bar) { 20 | return foo * bar; 21 | })(10, 2); 22 | 23 | // baz will equal 20. 24 | 25 | 26 | /* An anonymous function used as a closure. */ 27 | 28 | var baz; 29 | 30 | (function() { 31 | var foo = 10; 32 | var bar = 2; 33 | baz = function() { 34 | return foo * bar; 35 | }; 36 | })(); 37 | 38 | baz(); // baz can access foo and bar, even though is it executed outside of the 39 | // anonymous function. 40 | -------------------------------------------------------------------------------- /object-oriented/Expressive-JavaScript/3 - The mutability of objects.js: -------------------------------------------------------------------------------- 1 | function displayError(message) { 2 | displayError.numTimesExecuted++; 3 | alert(message); 4 | }; 5 | displayError.numTimesExecuted = 0; 6 | 7 | 8 | /* Class Person. */ 9 | 10 | function Person(name, age) { 11 | this.name = name; 12 | this.age = age; 13 | } 14 | Person.prototype = { 15 | getName: function() { 16 | return this.name; 17 | }, 18 | getAge: function() { 19 | return this.age; 20 | } 21 | } 22 | 23 | /* Instantiate the class. */ 24 | 25 | var alice = new Person('Alice', 93); 26 | var bill = new Person('Bill', 30); 27 | 28 | /* Modify the class. */ 29 | 30 | Person.prototype.getGreeting = function() { 31 | return 'Hi ' + this.getName() + '!'; 32 | }; 33 | 34 | /* Modify a specific instance. */ 35 | 36 | alice.displayGreeting = function() { 37 | alert(this.getGreeting()); 38 | } 39 | -------------------------------------------------------------------------------- /object-oriented/Inheritance/1 - Classical inheritance.js: -------------------------------------------------------------------------------- 1 | /* Class Person. */ 2 | 3 | function Person(name) { 4 | this.name = name; 5 | } 6 | 7 | Person.prototype.getName = function() { 8 | return this.name; 9 | } 10 | 11 | var reader = new Person('John Smith'); 12 | reader.getName(); 13 | -------------------------------------------------------------------------------- /object-oriented/Inheritance/10 - Edit-in-place example, prototypal.js: -------------------------------------------------------------------------------- 1 | /* EditInPlaceField object. */ 2 | 3 | var EditInPlaceField = { 4 | configure: function(id, parent, value) { 5 | this.id = id; 6 | this.value = value || 'default value'; 7 | this.parentElement = parent; 8 | 9 | this.createElements(this.id); 10 | this.attachEvents(); 11 | }, 12 | createElements: function(id) { 13 | this.containerElement = document.createElement('div'); 14 | this.parentElement.appendChild(this.containerElement); 15 | 16 | this.staticElement = document.createElement('span'); 17 | this.containerElement.appendChild(this.staticElement); 18 | this.staticElement.innerHTML = this.value; 19 | 20 | this.fieldElement = document.createElement('input'); 21 | this.fieldElement.type = 'text'; 22 | this.fieldElement.value = this.value; 23 | this.containerElement.appendChild(this.fieldElement); 24 | 25 | this.saveButton = document.createElement('input'); 26 | this.saveButton.type = 'button'; 27 | this.saveButton.value = 'Save'; 28 | this.containerElement.appendChild(this.saveButton); 29 | 30 | this.cancelButton = document.createElement('input'); 31 | this.cancelButton.type = 'button'; 32 | this.cancelButton.value = 'Cancel'; 33 | this.containerElement.appendChild(this.cancelButton); 34 | 35 | this.convertToText(); 36 | }, 37 | attachEvents: function() { 38 | var that = this; 39 | addEvent(this.staticElement, 'click', function() { that.convertToEditable(); }); 40 | addEvent(this.saveButton, 'click', function() { that.save(); }); 41 | addEvent(this.cancelButton, 'click', function() { that.cancel(); }); 42 | }, 43 | 44 | convertToEditable: function() { 45 | this.staticElement.style.display = 'none'; 46 | this.fieldElement.style.display = 'inline'; 47 | this.saveButton.style.display = 'inline'; 48 | this.cancelButton.style.display = 'inline'; 49 | 50 | this.setValue(this.value); 51 | }, 52 | save: function() { 53 | this.value = this.getValue(); 54 | var that = this; 55 | var callback = { 56 | success: function() { that.convertToText(); }, 57 | failure: function() { alert('Error saving value.'); } 58 | }; 59 | ajaxRequest('GET', 'save.php?id=' + this.id + '&value=' + this.value, callback); 60 | }, 61 | cancel: function() { 62 | this.convertToText(); 63 | }, 64 | convertToText: function() { 65 | this.fieldElement.style.display = 'none'; 66 | this.saveButton.style.display = 'none'; 67 | this.cancelButton.style.display = 'none'; 68 | this.staticElement.style.display = 'inline'; 69 | 70 | this.setValue(this.value); 71 | }, 72 | 73 | setValue: function(value) { 74 | this.fieldElement.value = value; 75 | this.staticElement.innerHTML = value; 76 | }, 77 | getValue: function() { 78 | return this.fieldElement.value; 79 | } 80 | }; 81 | 82 | var titlePrototypal = clone(EditInPlaceField); 83 | titlePrototypal.configure(' titlePrototypal ', $('doc'), 'Title Here'); 84 | var currentTitleText = titlePrototypal.getValue(); 85 | 86 | /* EditInPlaceArea object. */ 87 | 88 | var EditInPlaceArea = clone(EditInPlaceField); 89 | 90 | // Override certain methods. 91 | 92 | EditInPlaceArea.createElements = function(id) { 93 | this.containerElement = document.createElement('div'); 94 | this.parentElement.appendChild(this.containerElement); 95 | 96 | this.staticElement = document.createElement('p'); 97 | this.containerElement.appendChild(this.staticElement); 98 | this.staticElement.innerHTML = this.value; 99 | 100 | this.fieldElement = document.createElement('textarea'); 101 | this.fieldElement.value = this.value; 102 | this.containerElement.appendChild(this.fieldElement); 103 | 104 | this.saveButton = document.createElement('input'); 105 | this.saveButton.type = 'button'; 106 | this.saveButton.value = 'Save'; 107 | this.containerElement.appendChild(this.saveButton); 108 | 109 | this.cancelButton = document.createElement('input'); 110 | this.cancelButton.type = 'button'; 111 | this.cancelButton.value = 'Cancel'; 112 | this.containerElement.appendChild(this.cancelButton); 113 | 114 | this.convertToText(); 115 | }; 116 | EditInPlaceArea.convertToEditable = function() { 117 | this.staticElement.style.display = 'none'; 118 | this.fieldElement.style.display = 'block'; 119 | this.saveButton.style.display = 'inline'; 120 | this.cancelButton.style.display = 'inline'; 121 | 122 | this.setValue(this.value); 123 | }; 124 | EditInPlaceArea.convertToText = function() { 125 | this.fieldElement.style.display = 'none'; 126 | this.saveButton.style.display = 'none'; 127 | this.cancelButton.style.display = 'none'; 128 | this.staticElement.style.display = 'block'; 129 | 130 | this.setValue(this.value); 131 | }; 132 | -------------------------------------------------------------------------------- /object-oriented/Inheritance/2 - The prototype chain.js: -------------------------------------------------------------------------------- 1 | /* Class Author. */ 2 | 3 | function Author(name, books) { 4 | Person.call(this, name); // Call the superclass' constructor in the scope of this. 5 | this.books = books; // Add an attribute to Author. 6 | } 7 | 8 | Author.prototype = new Person(); // Set up the prototype chain. 9 | Author.prototype.constructor = Author; // Set the constructor attribute to Author. 10 | Author.prototype.getBooks = function() { // Add a method to Author. 11 | return this.books; 12 | }; 13 | 14 | var author = []; 15 | author[0] = new Author('Dustin Diaz', ['JavaScript Design Patterns']); 16 | author[1] = new Author('Ross Harmes', ['JavaScript Design Patterns']); 17 | 18 | author[1].getName(); 19 | author[1].getBooks(); 20 | -------------------------------------------------------------------------------- /object-oriented/Inheritance/3 - The extend function.js: -------------------------------------------------------------------------------- 1 | /* Extend function. */ 2 | 3 | function extend(subClass, superClass) { 4 | var F = function() {}; 5 | F.prototype = superClass.prototype; 6 | subClass.prototype = new F(); 7 | subClass.prototype.constructor = subClass; 8 | } 9 | 10 | 11 | /* Class Person. */ 12 | 13 | function Person(name) { 14 | this.name = name; 15 | } 16 | 17 | Person.prototype.getName = function() { 18 | return this.name; 19 | } 20 | 21 | /* Class Author. */ 22 | 23 | function Author(name, books) { 24 | Person.call(this, name); 25 | this.books = books; 26 | } 27 | extend(Author, Person); 28 | 29 | Author.prototype.getBooks = function() { 30 | return this.books; 31 | }; 32 | 33 | 34 | 35 | /* Extend function, improved. */ 36 | 37 | function extend(subClass, superClass) { 38 | var F = function() {}; 39 | F.prototype = superClass.prototype; 40 | subClass.prototype = new F(); 41 | subClass.prototype.constructor = subClass; 42 | 43 | // The second way, to add the below code 44 | subClass.superclass = superClass.prototype; 45 | if(superClass.prototype.constructor == Object.prototype.constructor) { 46 | superClass.prototype.constructor = superClass; 47 | } 48 | } 49 | 50 | 51 | /* Class Author. */ 52 | 53 | function Author(name, books) { 54 | Author.superclass.constructor.call(this, name); 55 | this.books = books; 56 | } 57 | extend(Author, Person); 58 | 59 | Author.prototype.getBooks = function() { 60 | return this.books; 61 | }; 62 | 63 | Author.prototype.getName = function() { 64 | var name = Author.superclass.getName.call(this); // The second way can access superClass method like this 65 | return name + ', Author of ' + this.getBooks().join(', '); 66 | }; 67 | -------------------------------------------------------------------------------- /object-oriented/Inheritance/4 - Prototypal inheritance.js: -------------------------------------------------------------------------------- 1 | /* Person Prototype Object. */ 2 | 3 | var Person = { 4 | name: 'default name', 5 | getName: function() { 6 | return this.name; 7 | } 8 | }; 9 | 10 | var reader = clone(Person); 11 | alert(reader.getName()); // This will output 'default name'. 12 | reader.name = 'John Smith'; 13 | alert(reader.getName()); // This will now output 'John Smith'. 14 | 15 | /* Author Prototype Object. */ 16 | 17 | var Author = clone(Person); 18 | Author.books = []; // Default value. 19 | Author.getBooks = function() { 20 | return this.books; 21 | } 22 | 23 | var author = []; 24 | 25 | author[0] = clone(Author); 26 | author[0].name = 'Dustin Diaz'; 27 | author[0].books = ['JavaScript Design Patterns']; 28 | 29 | author[1] = clone(Author); 30 | author[1].name = 'Ross Harmes'; 31 | author[1].books = ['JavaScript Design Patterns']; 32 | 33 | author[1].getName(); 34 | author[1].getBooks(); 35 | -------------------------------------------------------------------------------- /object-oriented/Inheritance/5 - Asymmetrical reading and writing.js: -------------------------------------------------------------------------------- 1 | var authorClone = clone(Author); 2 | alert(authorClone.name); // Linked to the primative Person.name, which is the 3 | // string 'default name'. 4 | authorClone.name = 'new name'; // A new primative is created and added to the 5 | // authorClone object itself. 6 | alert(authorClone.name); // Now linked to the primative authorClone.name, which 7 | // is the string 'new name'. 8 | 9 | authorClone.books.push('new book'); // authorClone.books is linked to the array 10 | // Author.books. We just modified the 11 | // prototype object's default value, and all 12 | // other objects that link to it will now 13 | // have a new default value there. 14 | authorClone.books = []; // A new array is created and added to the authorClone 15 | // object itself. 16 | authorClone.books.push('new book'); // We are now modifying that new array. 17 | 18 | var CompoundObject = { 19 | string1: 'default value', 20 | childObject: { 21 | bool: true, 22 | num: 10 23 | } 24 | } 25 | 26 | var compoundObjectClone = clone(CompoundObject); 27 | 28 | // Bad! Changes the value of CompoundObject.childObject.num. 29 | compoundObjectClone.childObject.num = 5; 30 | 31 | // Better. Creates a new object, but compoundObject must know the structure 32 | // of that object, and the defaults. This makes CompoundObject and 33 | // compoundObjectClone tightly coupled. 34 | compoundObjectClone.childObject = { 35 | bool: true, 36 | num: 5 37 | }; 38 | 39 | // Best approach. Uses a method to create a new object, with the same structure and 40 | // defaults as the original. 41 | 42 | var CompoundObject = {}; 43 | CompoundObject.string1 = 'default value', 44 | CompoundObject.createChildObject = function() { 45 | return { 46 | bool: true, 47 | num: 10 48 | } 49 | }; 50 | CompoundObject.childObject = CompoundObject.createChildObject(); 51 | 52 | var compoundObjectClone = clone(CompoundObject); 53 | compoundObjectClone.childObject = CompoundObject.createChildObject(); 54 | compoundObjectClone.childObject.num = 5; 55 | -------------------------------------------------------------------------------- /object-oriented/Inheritance/6 - The clone function.js: -------------------------------------------------------------------------------- 1 | /* Clone function. */ 2 | 3 | function clone(object) { 4 | function F() {} 5 | F.prototype = object; 6 | return new F; 7 | } 8 | -------------------------------------------------------------------------------- /object-oriented/Inheritance/7 - Mixin classes.js: -------------------------------------------------------------------------------- 1 | /* Mixin class. */ 2 | 3 | var Mixin = function() {}; 4 | Mixin.prototype = { 5 | serialize: function() { 6 | var output = []; 7 | for(key in this) { 8 | output.push(key + ': ' + this[key]); 9 | } 10 | return output.join(', '); 11 | } 12 | }; 13 | 14 | augment(Author, Mixin); 15 | 16 | var author = new Author('Ross Harmes', ['JavaScript Design Patterns']); 17 | var serializedString = author.serialize(); 18 | -------------------------------------------------------------------------------- /object-oriented/Inheritance/8 - The augment function.js: -------------------------------------------------------------------------------- 1 | /* Augment function. */ 2 | 3 | function augment(receivingClass, givingClass) { 4 | for(methodName in givingClass.prototype) { 5 | if(!receivingClass.prototype[methodName]) { 6 | receivingClass.prototype[methodName] = givingClass.prototype[methodName]; 7 | } 8 | } 9 | } 10 | 11 | /* Augment function, improved. */ 12 | 13 | function augment(receivingClass, givingClass) { 14 | if(arguments[2]) { // Only give certain methods. 15 | for(var i = 2, len = arguments.length; i < len; i++) { 16 | receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]]; 17 | } 18 | } 19 | else { // Give all methods. 20 | for(methodName in givingClass.prototype) { 21 | if(!receivingClass.prototype[methodName]) { 22 | receivingClass.prototype[methodName] = givingClass.prototype[methodName]; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /object-oriented/Inheritance/9 - Edit-in-place example, classical.js: -------------------------------------------------------------------------------- 1 | /* EditInPlaceField class. */ 2 | 3 | function EditInPlaceField(id, parent, value) { 4 | this.id = id; 5 | this.value = value || 'default value'; 6 | this.parentElement = parent; 7 | 8 | this.createElements(this.id); 9 | this.attachEvents(); 10 | }; 11 | 12 | EditInPlaceField.prototype = { 13 | createElements: function(id) { 14 | this.containerElement = document.createElement('div'); 15 | this.parentElement.appendChild(this.containerElement); 16 | 17 | this.staticElement = document.createElement('span'); 18 | this.containerElement.appendChild(this.staticElement); 19 | this.staticElement.innerHTML = this.value; 20 | 21 | this.fieldElement = document.createElement('input'); 22 | this.fieldElement.type = 'text'; 23 | this.fieldElement.value = this.value; 24 | this.containerElement.appendChild(this.fieldElement); 25 | 26 | this.saveButton = document.createElement('input'); 27 | this.saveButton.type = 'button'; 28 | this.saveButton.value = 'Save'; 29 | this.containerElement.appendChild(this.saveButton); 30 | 31 | this.cancelButton = document.createElement('input'); 32 | this.cancelButton.type = 'button'; 33 | this.cancelButton.value = 'Cancel'; 34 | this.containerElement.appendChild(this.cancelButton); 35 | 36 | this.convertToText(); 37 | }, 38 | attachEvents: function() { 39 | var that = this; 40 | addEvent(this.staticElement, 'click', function() { that.convertToEditable(); }); 41 | addEvent(this.saveButton, 'click', function() { that.save(); }); 42 | addEvent(this.cancelButton, 'click', function() { that.cancel(); }); 43 | }, 44 | 45 | convertToEditable: function() { 46 | this.staticElement.style.display = 'none'; 47 | this.fieldElement.style.display = 'inline'; 48 | this.saveButton.style.display = 'inline'; 49 | this.cancelButton.style.display = 'inline'; 50 | 51 | this.setValue(this.value); 52 | }, 53 | save: function() { 54 | this.value = this.getValue(); 55 | var that = this; 56 | var callback = { 57 | success: function() { that.convertToText(); }, 58 | failure: function() { alert('Error saving value.'); } 59 | }; 60 | ajaxRequest('GET', 'save.php?id=' + this.id + '&value=' + this.value, callback); 61 | }, 62 | cancel: function() { 63 | this.convertToText(); 64 | }, 65 | convertToText: function() { 66 | this.fieldElement.style.display = 'none'; 67 | this.saveButton.style.display = 'none'; 68 | this.cancelButton.style.display = 'none'; 69 | this.staticElement.style.display = 'inline'; 70 | 71 | this.setValue(this.value); 72 | }, 73 | 74 | setValue: function(value) { 75 | this.fieldElement.value = value; 76 | this.staticElement.innerHTML = value; 77 | }, 78 | getValue: function() { 79 | return this.fieldElement.value; 80 | } 81 | }; 82 | To create a field, instantiate the class: 83 | var titleClassical = new EditInPlaceField('titleClassical', $('doc'), 'Title Here'); 84 | var currentTitleText = titleClassical.getValue(); 85 | 86 | /* EditInPlaceArea class. */ 87 | 88 | function EditInPlaceArea(id, parent, value) { 89 | EditInPlaceArea.superclass.constructor.call(this, id, parent, value); 90 | }; 91 | extend(EditInPlaceArea, EditInPlaceField); 92 | 93 | // Override certain methods. 94 | 95 | EditInPlaceArea.prototype.createElements = function(id) { 96 | this.containerElement = document.createElement('div'); 97 | this.parentElement.appendChild(this.containerElement); 98 | 99 | this.staticElement = document.createElement('p'); 100 | this.containerElement.appendChild(this.staticElement); 101 | this.staticElement.innerHTML = this.value; 102 | 103 | this.fieldElement = document.createElement('textarea'); 104 | this.fieldElement.value = this.value; 105 | this.containerElement.appendChild(this.fieldElement); 106 | 107 | this.saveButton = document.createElement('input'); 108 | this.saveButton.type = 'button'; 109 | this.saveButton.value = 'Save'; 110 | this.containerElement.appendChild(this.saveButton); 111 | 112 | this.cancelButton = document.createElement('input'); 113 | this.cancelButton.type = 'button'; 114 | this.cancelButton.value = 'Cancel'; 115 | this.containerElement.appendChild(this.cancelButton); 116 | 117 | this.convertToText(); 118 | }; 119 | EditInPlaceArea.prototype.convertToEditable = function() { 120 | this.staticElement.style.display = 'none'; 121 | this.fieldElement.style.display = 'block'; 122 | this.saveButton.style.display = 'inline'; 123 | this.cancelButton.style.display = 'inline'; 124 | 125 | this.setValue(this.value); 126 | }; 127 | EditInPlaceArea.prototype.convertToText = function() { 128 | this.fieldElement.style.display = 'none'; 129 | this.saveButton.style.display = 'none'; 130 | this.cancelButton.style.display = 'none'; 131 | this.staticElement.style.display = 'block'; 132 | 133 | this.setValue(this.value); 134 | }; 135 | -------------------------------------------------------------------------------- /object-oriented/Interfaces/1 - Describing interfaces with comments.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | interface Composite { 4 | function add(child); 5 | function remove(child); 6 | function getChild(index); 7 | } 8 | 9 | interface FormItem { 10 | function save(); 11 | } 12 | 13 | */ 14 | 15 | var CompositeForm = function(id, method, action) { // implements Composite, FormItem 16 | //todo 17 | }; 18 | 19 | // Implement the Composite interface. 20 | 21 | CompositeForm.prototype.add = function(child) { 22 | //todo 23 | }; 24 | CompositeForm.prototype.remove = function(child) { 25 | //todo 26 | }; 27 | CompositeForm.prototype.getChild = function(index) { 28 | //todo 29 | }; 30 | 31 | // Implement the FormItem interface. 32 | 33 | CompositeForm.prototype.save = function() { 34 | //todo 35 | }; 36 | -------------------------------------------------------------------------------- /object-oriented/Interfaces/2 - Emulating interfaces with attribute checking.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | interface Composite { 4 | function add(child); 5 | function remove(child); 6 | function getChild(index); 7 | } 8 | 9 | interface FormItem { 10 | function save(); 11 | } 12 | 13 | */ 14 | 15 | var CompositeForm = function(id, method, action) { 16 | this.implementsInterfaces = ['Composite', 'FormItem']; 17 | //todo 18 | }; 19 | 20 | 21 | function addForm(formInstance) { 22 | if(!implements(formInstance, 'Composite', 'FormItem')) { 23 | throw new Error("Object does not implement a required interface."); 24 | } 25 | //todo 26 | } 27 | 28 | // The implements function, which checks to see if an object declares that it 29 | // implements the required interfaces. 30 | 31 | function implements(object) { 32 | for(var i = 1; i < arguments.length; i++) { // Looping through all arguments 33 | // after the first one. 34 | var interfaceName = arguments[i]; 35 | var interfaceFound = false; 36 | for(var j = 0; j < object.implementsInterfaces.length; j++) { 37 | if(object.implementsInterfaces[j] == interfaceName) { 38 | interfaceFound = true; 39 | break; 40 | } 41 | } 42 | 43 | if(!interfaceFound) { 44 | return false; // An interface was not found. 45 | } 46 | } 47 | return true; // All interfaces were found. 48 | } 49 | -------------------------------------------------------------------------------- /object-oriented/Interfaces/3 - Emulating interfaces with duck typing.js: -------------------------------------------------------------------------------- 1 | // Interfaces. 2 | 3 | var Composite = new Interface('Composite', ['add', 'remove', 'getChild']); 4 | var FormItem = new Interface('FormItem', ['save']); 5 | 6 | // CompositeForm class 7 | 8 | var CompositeForm = function(id, method, action) { 9 | //todo 10 | }; 11 | 12 | //todo 13 | 14 | function addForm(formInstance) { 15 | ensureImplements(formInstance, Composite, FormItem); 16 | // This function will throw an error if a required method is not implemented. 17 | //todo 18 | } 19 | -------------------------------------------------------------------------------- /object-oriented/Interfaces/4 - The interface implementation for this book.js: -------------------------------------------------------------------------------- 1 | // Interfaces. 2 | 3 | var Composite = new Interface('Composite', ['add', 'remove', 'getChild']); 4 | var FormItem = new Interface('FormItem', ['save']); 5 | 6 | // CompositeForm class 7 | 8 | var CompositeForm = function(id, method, action) { // implements Composite, FormItem 9 | //todo 10 | }; 11 | 12 | //todo 13 | 14 | function addForm(formInstance) { 15 | Interface.ensureImplements(formInstance, Composite, FormItem); 16 | // This function will throw an error if a required method is not implemented, 17 | // halting execution of the function. 18 | // All code beneath this line will be executed only if the checks pass. 19 | //todo 20 | } 21 | -------------------------------------------------------------------------------- /object-oriented/Interfaces/5 - The Interface class.js: -------------------------------------------------------------------------------- 1 | // Constructor. 2 | 3 | var Interface = function(name, methods) { 4 | if(arguments.length != 2) { 5 | throw new Error("Interface constructor called with " + arguments.length 6 | + "arguments, but expected exactly 2."); 7 | } 8 | 9 | this.name = name; 10 | this.methods = []; 11 | for(var i = 0, len = methods.length; i < len; i++) { 12 | if(typeof methods[i] !== 'string') { 13 | throw new Error("Interface constructor expects method names to be " 14 | + "passed in as a string."); 15 | } 16 | this.methods.push(methods[i]); 17 | } 18 | }; 19 | 20 | // Static class method. 21 | 22 | Interface.ensureImplements = function(object) { 23 | if(arguments.length < 2) { 24 | throw new Error("Function Interface.ensureImplements called with " + 25 | arguments.length + "arguments, but expected at least 2."); 26 | } 27 | 28 | for(var i = 1, len = arguments.length; i < len; i++) { 29 | var interface = arguments[i]; 30 | if(interface.constructor !== Interface) { 31 | throw new Error("Function Interface.ensureImplements expects arguments " 32 | + "two and above to be instances of Interface."); 33 | } 34 | 35 | for(var j = 0, methodsLen = interface.methods.length; j < methodsLen; j++) { 36 | var method = interface.methods[j]; 37 | if(!object[method] || typeof object[method] !== 'function') { 38 | throw new Error("Function Interface.ensureImplements: object " 39 | + "does not implement the " + interface.name 40 | + " interface. Method " + method + " was not found."); 41 | } 42 | } 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /object-oriented/Interfaces/6 - When to use the Interface class.js: -------------------------------------------------------------------------------- 1 | var DynamicMap = new Interface('DynamicMap', ['centerOnPoint', 'zoom', 'draw']); 2 | 3 | function displayRoute(mapInstance) { 4 | Interface.ensureImplements(mapInstace, DynamicMap); 5 | mapInstance.centerOnPoint(12, 34); 6 | mapInstance.zoom(5); 7 | mapInstance.draw(); 8 | //todo 9 | } 10 | -------------------------------------------------------------------------------- /object-oriented/Interfaces/7 - An example illustrating the use of the Interface class.js: -------------------------------------------------------------------------------- 1 | // ResultFormatter class, before we implement interface checking. 2 | 3 | var ResultFormatter = function(resultsObject) { 4 | if(!(resultsObject instanceOf TestResult)) { 5 | throw new Error('ResultsFormatter: constructor requires an instance ' 6 | + 'of TestResult as an argument.'); 7 | } 8 | this.resultsObject = resultsObject; 9 | }; 10 | 11 | ResultFormatter.prototype.renderResults = function() { 12 | var dateOfTest = this.resultsObject.getDate(); 13 | var resultsArray = this.resultsObject.getResults(); 14 | 15 | var resultsContainer = document.createElement('div'); 16 | 17 | var resultsHeader = document.createElement('h3'); 18 | resultsHeader.innerHTML = 'Test Results from ' + dateOfTest.toUTCString(); 19 | resultsContainer.appendChild(resultsHeader); 20 | 21 | var resultsList = document.createElement('ul'); 22 | resultsContainer.appendChild(resultsList); 23 | 24 | for(var i = 0, len = resultsArray.length; i < len; i++) { 25 | var listItem = document.createElement('li'); 26 | listItem.innerHTML = resultsArray[i]; 27 | resultsList.appendChild(listItem); 28 | } 29 | 30 | return resultsContainer; 31 | }; 32 | 33 | 34 | // ResultSet Interface. 35 | 36 | var ResultSet = new Interface('ResultSet', ['getDate', 'getResults']); 37 | 38 | // ResultFormatter class, after adding Interface checking. 39 | 40 | var ResultFormatter = function(resultsObject) { 41 | Interface.ensureImplements(resultsObject, ResultSet); 42 | this.resultsObject = resultsObject; 43 | }; 44 | 45 | ResultFormatter.prototype.renderResults = function() { 46 | //todo 47 | }; 48 | -------------------------------------------------------------------------------- /object-oriented/Introduction/Interface.js: -------------------------------------------------------------------------------- 1 | // Constructor. 2 | 3 | var Interface = function(name, methods) { 4 | if(arguments.length != 2) { 5 | throw new Error("Interface constructor called with " + arguments.length 6 | + "arguments, but expected exactly 2."); 7 | } 8 | 9 | this.name = name; 10 | this.methods = []; 11 | for(var i = 0, len = methods.length; i < len; i++) { 12 | if(typeof methods[i] !== 'string') { 13 | throw new Error("Interface constructor expects method names to be " 14 | + "passed in as a string."); 15 | } 16 | this.methods.push(methods[i]); 17 | } 18 | }; 19 | 20 | // Static class method. 21 | 22 | Interface.ensureImplements = function(object) { 23 | if(arguments.length < 2) { 24 | throw new Error("Function Interface.ensureImplements called with " + 25 | arguments.length + "arguments, but expected at least 2."); 26 | } 27 | 28 | for(var i = 1, len = arguments.length; i < len; i++) { 29 | var interface = arguments[i]; 30 | if(interface.constructor !== Interface) { 31 | throw new Error("Function Interface.ensureImplements expects arguments " 32 | + "two and above to be instances of Interface."); 33 | } 34 | 35 | for(var j = 0, methodsLen = interface.methods.length; j < methodsLen; j++) { 36 | var method = interface.methods[j]; 37 | if(!object[method] || typeof object[method] !== 'function') { 38 | throw new Error("Function Interface.ensureImplements: object " 39 | + "does not implement the " + interface.name 40 | + " interface. Method " + method + " was not found."); 41 | } 42 | } 43 | } 44 | }; 45 | 46 | 47 | /* 48 | 49 | 50 | // Example usage: 51 | 52 | // Interfaces. 53 | 54 | var Composite = new Interface('Composite', ['add', 'remove', 'getChild']); 55 | var FormItem = new Interface('FormItem', ['save']); 56 | 57 | // CompositeForm class 58 | 59 | var CompositeForm = function(id, method, action) { // implements Composite, FormItem 60 | ... 61 | }; 62 | 63 | ... 64 | 65 | function addForm(formInstance) { 66 | Interface.ensureImplements(formInstance, Composite, FormItem); 67 | // This function will throw an error if a required method is not implemented, halting execution. 68 | // All code beneath this line will be executed only if the checks pass. 69 | ... 70 | } 71 | 72 | */ 73 | -------------------------------------------------------------------------------- /question/Demo1.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created with JetBrains WebStorm. 3 | * User: cwwu 4 | * Date: 13-8-8 5 | * Time: 下午5:40 6 | * 方法前面加操作符号 7 | */ 8 | 9 | +function fun(){ 10 | 11 | } 12 | -function fun(){ 13 | 14 | } 15 | !function fun(){ 16 | 17 | } -------------------------------------------------------------------------------- /question/bind_demo: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Document 10 | 11 | 12 | 13 | 14 | 15 | 24 | 25 | 26 | --------------------------------------------------------------------------------