之前在《JavaScript小特性-面向对象》里面介绍过JavaScript面向对象的特性了,有了面向对象之后,自然就会想——那是不是还有设计模式呢?由于js面向对象的方式是基于原型(prototype)的,而不是传统基于类型(class),所以js的设计模式也和经典的设计模式有些差异。
关于设计模式
先说说什么是设计模式吧。很多人都觉得“设计模式”这东西很玄乎,把Gof四人帮的《Design Patterns》奉为编程圣经,而我却觉得大可不必。设计模式说白了,就是在特定环境下解决某类常见问题的一种套路,按着这种套路去做就会很得心应手。但是,这并不代表这些设计模式是放之四海皆准,更不是说套着设计模式做的东西就一定是最好维护、性能最佳、至高无上的了。就算你不知道什么设计模式,只要思路正确,也能写出设计模式来。
有大师说,设计模式其实是对语言缺陷的一种弥补。这里有两层意思,一是说设计模式是基于语言的,因为语言存在缺陷,能力有所不足,所以需要设计模式去提供一种通用的解决方式去弥补这些不足;二是说,如果脱离具体语言及具体环境,生搬硬套设计模式甚至会是一种束缚,设计模式并非绝对正确。
了解了上面的观点之后,下面就通过js的单例模式来看看,JavaScript的设计模式是怎么回事。
单例模式(Singleton)
传统的单例模式大概是这么回事(Java版):
public class Singleton{
private static Singleton instance;
private int property;//某些属性
private Singleton(){
//通过私有的构造函数来保证只有一个实例
}
public static synchronized Singleton getInstance(){
if (instance == null) {
instance = new Singleton();
}
return instance;
}
public void method(){//某些方法}
}
传统单例模式的特点就是利用私有构造函数来保证只有一个实例,使不同地方调用的都是同一个实例对象。然而这一点对于js来说就有点弱爆了,因为js是允许全局变量的,解决单例的办法很简单,定义一个全局对象就完事了(是不是觉得很原始,完全没什么模式可言):
var jsSingleton = {
property:"something",
method:function(){
console.log('hello world');
}
}
调用也很简单,无需顾及什么类名,任何地方访问jsSingleton都是同一个对象,完全不用担心。如果你气急败坏的跟我说,你这只是饿汉式的,懒汉式单例呢?如果真的创建对象时很耗费资源,或者是你对性能洁癖到一定程度,那也只能让js东施效颦的学一下传统的方式了:
var jsLazySingleton =(function(){
var instance;
//利用闭包来解决私有构造函数的问题(后面有解释)
function init (){
return {
property: 'some thing',
method: function(){}
}
}
return {
getInstance :function(){
//如果instance存在则返回,不存在则调用init()
return instance || instance = init();
}
}
})();
模块模式(Module)
在js的懒汉式单例模式中,其实也用到了另一种设计模式,即模块模式。在传统软件工程中,模块模式被定义为给类提供私有和公共封装的一种方法,也就是我们常说的“模块化”。在Java里面Class就是一种模块,解决了属性、方法的封装问题,它的模块模式直接就融合到语言特性里面了,因此也没什么模块模式的说法;然而对于JavaScript这种过于灵活的语言,这种最基本的私有化封装却需要用一个设计模式来解决了。
在JavaScript中,“private”是作为保留字,而不是关键字的,也就是说,JavaScript没有私有化这一功能(纠结吧)。解决解决这问题有两种方法,一是定义变量的时候在前面加上下划线“_”,也就是告诉其他开发者,不要动这个变量哟;另一种则是利用闭包。第一种方法并不是真正的私有,只是一种规范,如果要做到真正的私有,还是要用第二种方法——闭包。
我们创建一个匿名函数,然后立即运行它,此匿名函数中的所有代码都存在于一个“闭包”之中,从而得到私有性,并在特定作用域中保持可被访问。方法如下:
(function () {
//此作用域的所有变量、函数依旧可在特定作用域中被访问
})();
先用括号把函数定义括起来,从而得到该函数对象,然后后面的括号是立即运行它。这种形式可以在很多js库中见到,例如jQuery:
(function( window, undefined ) {
......
//最后一行
window.jQuery = window.$ = jQuery;
})(window);
我们看到jQuery把window这个全局变量传进匿名函数中,然后把内部定义的jQuery赋值给了window,从而在全局作用域中都可以通过“$”符号来访问匿名函数中的内容(想了解更多关于闭包的内容,可以看看《jQuery自运行匿名函数》《闭包与作用域链》)。
弄清楚这些之后,我们就来看看js模块模式的基本样子吧:
var Module = (function () {
var my={},
privateVar = 8;//私有属性
function privateFun() {//私有方法
return ++privateVar;
};
my.publicVar = 1;//公共属性
my.moduleFun = function () {//公共方法
return privateFun();
};
return my;
}());
console.log(Module.publicVar);//1
console.log(Module.publicFun());//9
在匿名函数中我们返回了一个my变量给Module作为外部访问闭包内容的接口,除闭包内my之外的内容都得到了私有性保护,闭包的数据在Module变量的作用域中保持可以访问。
好了,模块模式解决了JavaScript私有化的问题,我们可以利用它来定义命名空间、单例、拥有私有化封装的对象等等。然而模块模式也并非尽善尽美。例如,我们定义私有、公共变量的方法是不同的,当开发过程中我们需要改变某个变量的可见性的时候,就不得不在它所有出现过的地方进行修改;并且JavaScript作为动态编译的语言,我们可以随时给对象添加属性、方法,然而我们在闭包之外定义的方法是无法直接访问私有数据的。
更高级的模块模式
虽然前面提到的模块模式对于大多数开发者来说已经足够了,但是模块模式还可以被改进的更加强大、更加易于扩展。
扩展性
之前所说的模板模式有一个限制,就是整个模板必须定义在一个文件中。曾面对一大堆代码工作的人肯定明白将它划分为多个文件的意义。好在,有个巧妙的方法来扩展我们的Modules。首先,在匿名函数的参数中导入Module,然后给它添加属性,然后再导出它。下面这个例子是在另一个文件中对上面提到的Module进行扩展(必须是全局作用域的情况):
var Module = (function (my) {
my.anotherFun = function () {
// do someting...
};
return my;
}(Module||{}));//注意,这里还有个“{}”
我们重新用var关键字定义Module以保证一致性,并且如果之前Module没有被创建的话,在这里会自动的被创建为空对象“{}”。当这段代码运行结束之后,我们的Module又有了一个新的公共方法Module.anotherFun()。每个此扩展的Module都有自己独有的私有属性、方法。由于导入的时候可以自动创建,这些包含Module定义的文件之间可以以任意顺序加载(如果存在依赖关系,必须按次序加载的话,那你按次序就好了)。
跨文件私有属性共享
目前的多文件扩展Module还有个一严重的限制,那就是每个文件的Module都只保持自己的私有状态,无法访问其他文件的私有属性。当然,这也可以被解决。下面是解决多文件私有属性共享的一个方法:
var MODULE = (function () {
//将所有的私有属性、方法都定义在_private对象中
//每个扩展Module都可以通过my._private来访问
var my = {},
_private = my._private = {},
_seal = function (){
//密封,删除所有私有数据的可访问性
delete my._private;
},
_unseal = function (){
//解封,让私有数据重新可访问
my._private = _private;
};
my.extend = function(otherModules){
//必须通过此方法来添加扩展Module文件
_unseal();
//add other modules
_seal();//异步调用,此处只是示意,真正的代码并非如此
}
return my;
}());
上面的Module文件必须第一个被加载,然后利用Module.extend来加载其他的扩展。my.extend实际上是通过类似Labjs的工具来并行加载其他的Module。每个扩展Module都通过my._private来传递私有性,并且都需要将传来的my._private保存为自己的私有属性,所有对my._private的修改都将反应到其他扩展Module中去。此方法参考于Ben Cherry的《JavaScript Module Pattern: In-Depth》。
小结一下
看完了JavaScript的单例模式和模块模式,是不是有点感叹与JavaScript设计模式的不伦不类呢?这些就是语言特性的体现,设计模式是依赖于语言特性的,是对语言能力的一种补充。JavaScript的设计模式并非不伦不类,很多JavaScript也有很多优雅的设计模式,可以让传统设计模式相形见绌的。
接下来还打算写几篇关于JavaScript设计模式的文章,敬请期待。
相关推荐
本文实例讲述了JavaScript设计模式之单例模式。分享给大家供大家参考,具体如下: 一、单例模式概念 单例就是保证一个类只有一个实例,实现方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再...
本文实例讲述了JS 设计模式之:单例模式定义与实现方法。分享给大家供大家参考,具体如下: 良好的设计模式可以显著提高代码的可读性,降低复杂度和维护成本。笔者打算通过几篇文章通俗地讲一讲常见的或者实用的设计...
JavaScript设计模式 这是《》一书中描述的。 随意进行分叉和更新。 以下DP已实现: 模块模式 观察者模式 单例模式
通过设计模式实现 ,从而允许所有事物保持解耦,并在一个唯一的根位置连接应用程序组件和配置。 通过将实例重新导出到类和工厂的导出,将依赖项注入替换为单例反模式。 获取一个纯JavaScript非教义的跨transpiller...
Efesto.js Efesto.js 是一个 JavaScript 框架,它为您提供模式、实用程序、路由、dinamyc 加载器、带驱动程序的数据管理器... 设计模式实现,如单例、模块、观察者、工厂……; 文件加载器工具; smarty 模板引擎;
XSS过滤以及单例模式 博客系统表结构讲解 博客系统功能讲解 CMDB介绍 CMDB实现的三种方式 CMDB Agent客户端示例 第26周 今日内容概要以及CMDB介绍 前端插件定制之表头 前端插件定制之表内容 第27周 前端插件定制...
5.7.2 单例模式(Singleton Pattern) 81 5.7.3 工厂模式(Factory Pattern) 83 5.8 接口与抽象类 86 5.8.1 接口的定义 86 5.8.2 单一接口的实现 87 5.8.3 多重接口的实现 87 5.8.4 抽象类 88 5.9 小结 90 第6章 ...
(1)针对多窗口类浏览器模式问题,指出并分析了该问题存在的原因,利用Activity的运行机制,通过Fragment栈对主要模块的Webview进行管理,实现对不同模块之间切换的控制。 (2)针对跨域数据交互问题,指出并分析了...
通常,他们遵循酒吧子设计模式安装使用npm npm install signal-eventyarn add signal-event用法signal-event导出一个函数,该函数在用构造函数调用时将每次生成相同的实例。 从而将您的所有活动都集中在一个地方。 ...
项目介绍这毕竟是个小游戏,本身其实没有什么技术难点,主要在于项目的设计,分为如下几个小模块1、玩家 (Player)玩家类我用了单例模式,继承自CCSprite,因为贯穿游戏一直会有玩家存在,在这个版本里也不会有第二个...
实例038 动态输出JavaScript代码 69 实例039 当数字遇到了字符串 70 实例040 PHP程序员的基础——变量的应用 72 实例041 打印系统环境变量信息print_r($_ENV) 73 实例042 使用可变变量输出“I Like PHP!” 73 实例...
实例038 动态输出JavaScript代码 69 实例039 当数字遇到了字符串 70 实例040 PHP程序员的基础——变量的应用 72 实例041 打印系统环境变量信息print_r($_ENV) 73 实例042 使用可变变量输出“I Like PHP!” 73 实例...