之前在《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设计模式——单例模式详解** 在JavaScript编程中,设计模式是一种被广泛接受和使用的解决方案,用于解决常见的编程问题,提升代码的质量和可维护性。单例模式是设计模式中的一种,它确保一个类只有一个...
总结来说,JavaScript中的单例模式是一种强大的设计模式,它可以提高代码的组织性和可维护性,特别是在处理资源管理和全局状态时。理解并熟练运用单例模式,对于提升JavaScript开发水平和编写高质量代码至关重要。
常见的JavaScript设计模式包括单例模式、工厂模式、观察者模式、发布-订阅模式等。每种模式都有其适用场景和特点,掌握它们可以让开发者在面对不同的编程问题时,更加高效地应对。 单例模式保证一个类只有一个实例...
单例模式是一种能够保证类实例唯一性的设计模式,在JavaScript中实现时可以利用闭包等特性,但也需谨慎使用以避免影响代码的可测试性和可维护性。通过深入理解单例模式及其在JavaScript中的应用,开发者可以更加合理...
在JavaScript编程中,"单例模式"是一种设计模式,它保证了类只有一个实例,并提供一个全局访问点。这种模式在JavaScript中的应用广泛,特别是在大型应用和模块化开发中,用于管理资源、创建复杂的对象结构或者确保...
**JavaScript设计模式——第二版** 在编程领域,设计模式是一种被广泛接受的解决方案,它针对特定问题或场景提供了经过时间考验的最佳实践。JavaScript,作为互联网上最常用的语言之一,其设计模式同样至关重要,有...
JavaScript中的单例模式是一种设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个唯一实例。这种模式在JavaScript中尤其有用,因为它可以用来限制对象的创建,避免资源的浪费,同时提供一个共享的...
在深入探讨《JavaScript设计模式系统讲解与应用》的内容之前,我们先来了解一下设计模式的基本概念以及为什么它对于前端开发人员尤为重要。设计模式是一套被反复使用的、经过分类编目的、包含结构化的解决方案,用于...
本文实例讲述了JS 设计模式之:单例模式定义与实现方法。分享给大家供大家参考,具体如下: 良好的设计模式可以显著提高代码的可读性,降低复杂度和维护成本。笔者打算通过几篇文章通俗地讲一讲常见的或者实用的设计...
JavaScript设计模式是编程实践中的一种重要思想,它提供了一套经过时间考验的最佳实践,用来解决常见的编程问题和提高代码的可维护性、可扩展性和可复用性。在JavaScript这种动态类型的脚本语言中,设计模式尤其重要...
JavaScript设计模式与开发实践是深入理解并提升JavaScript编程能力的关键领域。设计模式是对在软件设计中经常出现的问题的解决方案的描述,它代表了最佳实践,是经验丰富的开发者们经过时间检验后总结出来的解决常见...
JavaScript设计模式覆盖了从传统的软件工程设计模式到特定于前端的MV*模式。例如,构造器模式、模块化模式、单例模式、观察者模式等,都是JavaScript中常见的模式,每种模式都旨在解决特定的设计问题。 现代...
JavaScript设计模式是前端开发中非常重要的一个概念。它不仅可以提高代码的复用性,还能提高系统的可维护性和扩展性。在曾探所著的《JavaScript设计模式与开发实践》一书中,作者详细介绍了JavaScript中的各种设计...
单例模式是一种广泛应用于JavaScript编程中的设计模式,它确保一个类仅有一个实例,并提供一个全局访问点。单例模式不仅有助于保持代码的整洁,还能通过限制类的实例数量来减少内存占用,有助于提高系统的性能和资源...
单例模式是软件设计模式中的一种,它保证一个类只有一个实例,并提供一个全局访问点。在JavaScript中,由于其动态特性和全局作用域的特点,实现单例模式有多种方法。让我们深入探讨一下JavaScript中的单例模式及其...