JavaScript学习记录——《学用 JavaScript 设计模式》学习笔记(1)

来源:互联网 发布:ucloud 阿里云 编辑:程序博客网 时间:2024/06/05 07:35

一、设计模式的分类:

Creational                           根据创建对象的概念分成下面几类。
Class
Factory Method(工厂方法)         通过将数据和事件接口化来构建若干个子类。
Object 
Abstract Factory(抽象工厂)       建立若干族类的一个实例,这个实例不需要具体类的细节信息。(抽象类)
Builder (建造者)                     将对象的构建方法和其表现形式分离开来,总是构建相同类型的对象。
Prototype(原型)                      一个完全初始化的实例,用于拷贝或者克隆。
Singleton(单例)                      一个类只有唯一的一个实例,这个实例在整个程序中有一个全局的访问点。
Structural                             根据构建对象块的方法分成下面几类。
Class                                    
Adapter(适配器)                       将不同类的接口进行匹配,调整,这样尽管内部接口不兼容但是不同的类还是可以协同工作的。
Bridge(桥接模式)                      将对象的接口从其实现中分离出来,这样对象的实现和接口可以独立的变化。
Composite(组合模式)                通过将简单可组合的对象组合起来,构成一个完整的对象,这个对象的能力将会超过这些组成部分的能力的总和,即会有新的能力产生。
Decorator(装饰器)                    动态给对象增加一些可替换的处理流程。
Facada(外观模式)                     一个类隐藏了内部子系统的复杂度,只暴露出一些简单的接口。
Flyweight(享元模式)                  一个细粒度对象,用于将包含在其它地方的信息 在不同对象之间高效地共享。
Proxy(代理模式)                       一个充当占位符的对象用来代表一个真实的对象。
Behavioral                            基于对象间作用方式来分类。
Class
Interpreter(解释器)                  将语言元素包含在一个应用中的一种方式,用于匹配目标语言的语法。
Template Method(模板方法)       在一个方法中为某个算法建立一层外壳,将算法的具体步骤交付给子类去做。
Object         
Chain of Responsibility(响应链)              一种将请求在一串对象中传递的方式,寻找可以处理这个请求的对象。
Command(命令)                                  封装命令请求为一个对象,从而使记录日志,队列缓存请求,未处理请求进行错误处理 这些功能称为可能。
Iterator(迭代器)                                  在不需要直到集合内部工作原理的情况下,顺序访问一个集合里面的元素。
Mediator(中介者模式)                           在类之间定义简化的通信方式,用于避免类之间显式的持有彼此的引用。
Observer(观察者模式)                          用于将变化通知给多个类的方式,可以保证类之间的一致性。
State(状态)                                        当对象状态改变时,改变对象的行为。
Strategy(策略)                                    将算法封装到类中,将选择和实现分离开来。
Visitor(访问者)                                    为类增加新的操作而不改变类本身。


二、对象字面值

在对象字面值的标记里,一个对象被描述为一组以逗号分隔的名称/值对括在大括号({})的集合。对象内部的名称可以是字符串或是标记符后跟着一个冒号":"。在对象里最后一个名称/值对不应该以","为结束符,因为这样会导致错误。


对象字面值不要求使用新的操作实例,但是不能够在结构体开始使用,因为打开"{"可能被解释为一个块的开始。在对象外新的成员会被加载,使用分配如下:smyModule.property = "someValue";


下面是一个更完整的使用对象字面值定义一个模块的例子:


var myModule = {  myProperty: "someValue",  // 对象字面值包含了属性和方法(properties and methods).  // 例如,我们可以定义一个模块配置进对象:  myConfig: {    useCaching: true,    language: "en"  },  // 非常基本的方法  myMethod: function () {    console.log( "Where in the world is Paul Irish today?" );  },  // 输出基于当前配置(<span>configuration</span>)的一个值  myMethod2: function () {    console.log( "Caching is:" + ( this.myConfig.useCaching ) ? "enabled" : "disabled" );  },  // 重写当前的配置(configuration)  myMethod3: function( newConfig ) {    if ( typeof newConfig === "object" ) {      this.myConfig = newConfig;      console.log( this.myConfig.language );    }  }};// 输出: Where in the world is Paul Irish today?myModule.myMethod();// 输出: enabledmyModule.myMethod2();// 输出: frmyModule.myMethod3({  language: "fr",  useCaching: false});

三、模块化模式

模块化模式最初被定义为一种对传统软件工程中的类提供私有和公共封装的方法。
在JavaScript中,模块化模式用来进一步模拟类的概念,通过这样一种方式:我们可以在一个单一的对象中包含公共/私有的方法和变量,从而从全局范围中屏蔽特定的部分。这个结果是可以减少我们的函数名称与在页面中其他脚本区域定义的函数名称冲突的可能性。

模块模式使用闭包的方式来将"私有信息",状态和组织结构封装起来。提供了一种将公有和私有方法,变量封装混合在一起的方式,这种方式防止内部信息泄露到全局中,从而避免了和其它开发者接口发生冲图的可能性。在这种模式下只有公有的API 会返回,其它将全部保留在闭包的私有空间中。
这种方法提供了一个比较清晰的解决方案,在只暴露一个接口供其它部分使用的情况下,将执行繁重任务的逻辑保护起来。这个模式非常类似于立即调用函数式表达式,但是这种模式返回的是对象,而立即调用函数表达式返回的是一个函数。


需要注意的是,在javascript事实上没有一个显式的真正意义上的"私有性"概念,因为与传统语言不同,javascript没有访问修饰符。从技术上讲,变量不能被声明为公有的或者私有的,因此我们使用函数域的方式去模拟这个概念。在模块模式中,因为闭包的缘故,声明的变量或者方法只在模块内部有效。在返回对象中定义的变量或者方法可以供任何人使用。


下面这个例子通过创建一个自包含的模块实现了模块模式。

var testModule = (function () {  var counter = 0;  return {    incrementCounter: function () {      return counter++;    },    resetCounter: function () {      console.log( "counter value prior to reset: " + counter );      counter = 0;    }  };})();// Usage:// Increment our countertestModule.incrementCounter();// Check the counter value and reset// Outputs: 1testModule.resetCounter();

在这里我们看到,其它部分的代码不能直接访问我们的incrementCounter() 或者 resetCounter()的值。counter变量被完全从全局域中隔离起来了,因此其表现的就像一个私有变量一样,它的存在只局限于模块的闭包内部,因此只有两个函数可以访问counter。我们的方法是有名字空间限制的,因此在我们代码的测试部分,我们需要给所有函数调用前面加上模块的名字(例如"testModule")。


当使用模块模式时,我们会发现通过使用简单的模板,对于开始使用模块模式非常有用。下面是一个模板包含了命名空间,公共变量和私有变量。

var myNamespace = (function () {  var myPrivateVar, myPrivateMethod;  // A private counter variable  myPrivateVar = 0;  // A private function which logs any arguments  myPrivateMethod = function( foo ) {      console.log( foo );  };  return {    // A public variable    myPublicVar: "foo",    // A public function utilizing privates    myPublicFunction: function( bar ) {      // Increment our private counter      myPrivateVar++;      // Call our private method using bar      myPrivateMethod( bar );    }  };})();

看一下另外一个例子,下面我们看到一个使用这种模式实现的购物车。这个模块完全自包含在一个叫做basketModule 全局变量中。模块中的购物车数组是私有的,应用的其它部分不能直接读取。只存在与模块的闭包中,因此只有可以访问其域的方法可以访问这个变量。

var basketModule = (function () {  // privates  var basket = [];   function doSomethingPrivate() {    //...  }  function doSomethingElsePrivate() {    //...  }  // Return an object exposed to the public  return {     // Add items to our basket    addItem: function( values ) {      basket.push(values);    },    // Get the count of items in the basket    getItemCount: function () {      return basket.length;    },    // Public alias to a  private function    doSomething: doSomethingPrivate,    // Get the total value of items in the basket    getTotal: function () {      var q = this.getItemCount(),          p = 0;      while (q--) {        p += basket[q].price;      }      return p;    }  };}());

在模块内部,你可能注意到我们返回了应外一个对象。这个自动赋值给了basketModule 因此我们可以这样和这个对象交互。

// basketModule returns an object with a public API we can usebasketModule.addItem({  item: "bread",  price: 0.5});basketModule.addItem({  item: "butter",  price: 0.3});// Outputs: 2console.log( basketModule.getItemCount() );// Outputs: 0.8console.log( basketModule.getTotal() );// However, the following will not work:// Outputs: undefined// This is because the basket itself is not exposed as a part of our// the public APIconsole.log( basketModule.basket ); // This also won't work as it only exists within the scope of our// basketModule closure, but not the returned public objectconsole.log( basket ); 

上面的方法都处于basketModule 的名字空间中。


请注意在上面的basket模块中 域函数是如何在我们所有的函数中被封装起来的,以及我们如何立即调用这个域函数,并且将返回值保存下来。这种方式有以下的优势:
1、可以创建只能被我们模块访问的私有函数。这些函数没有暴露出来(只有一些API是暴露出来的),它们被认为是完全私有的。
2、当我们在一个调试器中,需要发现哪个函数抛出异常的时候,可以很容易的看到调用栈,因为这些函数是正常声明的并且是命名的函数。
3、正如过去 T.J Crowder 指出的,这种模式同样可以让我们在不同的情况下返回不同的函数。我见过有开发者使用这种技巧用于执行UA(尿检,抽样检查)测试,目的是为了在他们的模块里面针对IE专门提供一条代码路径,但是现在我们也可以简单的使用特征检测达到相同的目的。

模块模式的变体

1、Import mixins(导入混合)
这个变体展示了如何将全局(例如 jQuery, Underscore)作为一个参数传入模块的匿名函数。这种方式允许我们导入全局,并且按照我们的想法在本地为这些全局起一个别名。

// Global modulevar myModule = (function ( jQ, _ ) {      function privateMethod1(){        jQ(".container").html("test");    }    function privateMethod2(){      console.log( _.min([10, 5, 100, 2, 1000]) );    }        return{        publicMethod: function(){            privateMethod1();                        }                };   // Pull in jQuery and Underscore}( jQuery, _ ));myModule.publicMethod();  

2、Exports(导出)
这个变体允许我们声明全局对象而不用使用它们,同样也支持在下一个例子中我们将会看到的全局导入的概念。

// Global modulevar myModule = (function () {    // Module object   var module = {},    privateVariable = "Hello World";    function privateMethod() {    // ...  }  module.publicProperty = "Foobar";  module.publicMethod = function () {    console.log( privateVariable );  };    return module;}());


模块模式的缺点是因为我们采用不同的方式访问公有和私有成员,因此当我们想要改变这些成员的可见性的时候,我们不得不在所有使用这些成员的地方修改代码。
我们也不能在对象之后添加的方法里面访问这些私有变量。也就是说,很多情况下,模块模式很有用,并且当使用正确的时候,潜在地可以改善我们代码的结构。
其它缺点包括不能为私有成员创建自动化的单元测试,以及在紧急修复bug时所带来的额外的复杂性。根本没有可能可以对私有成员打补丁。相反地,我们必须覆盖所有的使用存在bug私有成员的公共方法。开发者不能简单的扩展私有成员,因此我们需要记得,私有成员并非它们表面上看上去那么具有扩展性。


(Revealing Module Pattern)暴露式模块模式
既然我们对模块模式已经有一些了解了,让我们看一下改进版本 - Christian Heilmann 的启发式模块模式。
启发式模块模式来自于,当Heilmann对这样一个现状的不满,即当我们想要在一个公有方法中调用另外一个公有方法,或者访问公有变量的时候,我们不得不重复主对象的名称。他也不喜欢模块模式中,当想要将某个成员变成公共成员时,修改文字标记的做法。
因此他工作的结果就是一个更新的模式,在这个模式中,我们可以简单地在私有域中定义我们所有的函数和变量,并且返回一个匿名对象,这个对象包含有一些指针,这些指针指向我们想要暴露出来的私有成员,使这些私有成员公有化。 


下面给出一个如何使用暴露式模块模式的例子:

var myRevealingModule = function () {        var privateVar = "Ben Cherry",            publicVar  = "Hey there!";        function privateFunction() {            console.log( "Name:" + privateVar );        }                function publicSetName( strName ) {            privateVar = strName;        }        function publicGetName() {            privateFunction();        }        // Reveal public pointers to          // private functions and properties        return {            setName: publicSetName,            greeting: publicVar,            getName: publicGetName        };    }();myRevealingModule.setName( "Paul Kinlan" );

这个模式可以用于将私有函数和属性以更加规范的命名方式展现出来。

var myRevealingModule = function () {        var privateCounter = 0;        function privateFunction() {            privateCounter++;        }        function publicFunction() {            publicIncrement();        }        function publicIncrement() {            privateFunction();        }        function publicGetCount(){          return privateCounter;        }        // Reveal public pointers to         // private functions and properties               return {            start: publicFunction,            increment: publicIncrement,            count: publicGetCount        };    }();myRevealingModule.start();

优势
这个模式是我们脚本的语法更加一致。同样在模块的最后关于那些函数和变量可以被公共访问也变得更加清晰,增强了可读性。


缺点
这个模式的一个缺点是如果私有函数需要使用公有函数,那么这个公有函数在需要打补丁的时候就不能被重载。因为私有函数仍然使用的是私有的实现,并且这个模式不能用于公有成员,只用于函数。
公有成员使用私有成员也遵循上面不能打补丁的规则。
 因为上面的原因,使用暴露式模块模式创建的模块相对于原始的模块模式更容易出问题,因此在使用的时候需要小心。


《学用 JavaScript 设计模式》学习资料来源,感谢各位专家们的翻译!
http://www.oschina.net/translate/learning-javascript-design-patterns






0 0