好程序员分享JavaScript命名空间模式实例详解。本文描述了JavaScript命名空间模式,如下: 命名空间可以被认为是代码逻辑分组下的唯一标识符。为什么会出现命名空间的概念?因为可用的字数太少,不同人写的程序不可能所有变量都没有重名。在JavaScript中,命名空间帮助我们防止与全局命名空间中的其他对象或变量发生冲突。命名空间还有助于组织代码以提高可维护性和可读性。本文旨在探索JavaScript中几种常见的命名空间模式,并为我们提供一个思路。 JavaScript的执行环境有很多独特之处,全局变量和函数的使用就是其中之一。JavaScript执行环境由各种全局变量组成,这些变量在函数执行环境之前创建。这些全局变量都挂载在“全局对象”下。在浏览器中,窗口对象相当于全局对象。然后,在全局范围内声明的任何变量和函数都是窗口对象的属性。当名称发生冲突时,就会出现一些不可控的问题。全局变量会带来以下问题:命名冲突代码的脆弱性难以测试。在编程开发中合理使用命名空间,可以避免相同的变量或对象名称引起的冲突。此外,命名空间还有助于组织代码,更易于维护和阅读。虽然JavaScript不提供原生命名空间支持,但我们可以使用其他方法(对象和闭包)来实现类似的效果。以下是一些常见的命名空间模式:1.单一全局变量JavaScript中流行的命名空间模式是选择一个全局变量作为主要引用对象。因为每一个可能的全局变量都成为唯一全局变量的一个属性,所以不需要创建多个全局变量,也避免了与其他声明的冲突。单全局变量模式已经在很多JavaScript类库中使用,比如:YUI定义了唯一的YUI全局对象jQuery定义了jQuery,jQuery定义了一个Dojo全局变量Closure类在其他类库使用时使用jQueryDojo库定义了一个goog全局对象。Underscore类库定义了一个_全局对象。一个例子如下:varmyApplication=(function(){function(){//***},return{//**}})();虽然单一全局变量模式适用于某些情况,但它最大的挑战是确保单一全局变量在页面中唯一使用,而不会出现命名冲突。2、命名空间前缀命名空间前缀模式的思路很清晰,就是选择一个唯一的命名空间,然后在其后面声明变量、方法和对象。一个例子如下:var=myApplication_propertyA={};var=myApplication_propertyA={};functionmyApplication_myMethod(){//*}某种程度上确实减少了命名冲突的概率,但是并没有减少全局变量的数量。当应用规模扩大时,会产生很多全局变量。在全局命名空间中,这种模式对其他人没有使用的前缀有很强的依赖性,有时不容易判断是否有人使用了特殊的前缀,所以在使用这种模式时一定要特别注意。3.对象字面量表示法对象字面量模式可以认为是一个包含一组键值对的对象,每对键值对用冒号分隔,键也可以是代码的新命名空间。示例如下:varmyApplication={//可以轻松地为对象字面量定义函数getInfo:function(){//***},//可以进一步支持对象命名空间models:{},views:{pages:{}},集合:{}};就像给对象添加属性一样,我们也可以直接给命名空间添加属性。对象字面量方法不会污染全局命名空间,有助于逻辑地组织代码和参数。而且,这种方法的可读性和可维护性都很强。当然我们在使用的时候要测试是否存在同名变量,避免冲突。下面介绍一些常用的检测方法:varmyApplication=myApplication||{};if(!myApplication){myApplication={};}window.myApplication||(window.myApplication||{});//对于jQueryvarmyApplication=$.fn.myApplication=function(){};varmyApplication=myApplication===undefined?{}:我的应用程序;对象字面量为我们提供了优雅的key/value语法,我们可以非常方便的组织代码,封装不同的逻辑或功能,可读性、可维护性、可扩展性极强。4、嵌套命名空间嵌套命名空间模式可以说是对象字面量模式的升级版。这也是一种有效的避免冲突模式,因为即使一个命名空间存在,也不太可能有相同的嵌套子对象。示例如下:varmyApplication=myApplication||{};//定义嵌套子对象myApplication.routers=myApplication.routers||{};myApplication.routers.test=myApplication.routers.test||{};当然,我们也可以选择将新的嵌套命名空间或属性声明为索引属性,如:myApplication['routers']=myApplication['routers']||{};使用嵌套命名空间模式可以使代码具有可读性和有意义的组织性,并且相对安全,不易发生冲突。它的弱点是如果我们的命名空间嵌套过多,会增加浏览器的查询工作量。我们可以将需要多次访问的子对象缓存在本地,以减少查询时间。5、立即调用函数表达式立即调用函数(IIFE)实际上是一个匿名函数,定义后立即调用。在JavaScript中,由于变量和函数是在只能在内部访问的上下文中显式定义的,因此函数调用提供了一种实现私有变量和方法的便捷方式。IIFE是封装应用程序逻辑以保护它不受全局命名空间影响的常用方法,在这里它也可以发挥其特殊作用。示例如下://namespace和undefined作为参数传递,确保://1.namespace可以在本地修改,无需重写函数的外部上下文//2.undefined的参数值保证是undefined,避免了ES5规范中的定义undefined(function(namespace,undefined){//私有属性varfoo="foo";bar="bar";//公共方法和属性.sayHello=function(){say("HelloWorld!");};//私有方法函数say(str){console.log("Yousaid:"+str);};})(window.namespace=窗口命名空间||{});可扩展性是任何可扩展命名空间模式的关键,这可以使用IIFE轻松实现,我们可以再次使用它来向命名空间添加更多功能。6.命名空间注入命名空间注入是IIFE的另一种变体,它从函数包装器中“注入”特定命名空间的方法和属性,将其用作命名空间代理。这种模式的优点是功能行为可以应用于多个对象或命名空间。示例如下:varmyApplication=myApplication||{};myApplication.utils={};(function(){varvalue=5;this.getValue=function(){returnvalue;}//定义一个新的子命名空间this.tools={};})。apply(myApplication.utils);(function(){this.diagnose=function(){return"diagnose";}}).apply(myApplication.utils.tools);//同普通上扩展函数的方式IIFE,只是将context作为参数传入并修改,而不是仅仅使用this还有一种方式是使用API??实现context和参数的自然分离,这种模式感觉更像是modulecreator,但是作为module它还提供了包装解决方案。例子如下:varns=ns||{},ns1=ns1||{};//模块,命名空间创建者varcreator=function(val){varval=val||0;this.next=function(){returnval++;};this.reset=function(){val=0;}}creator.call(ns);//ns.next,ns.reset已经存在于这次creator.call(ns1,5000);//ns1包含了相同的方法,但是值改写为5000。命名空间注入用于为多个模块或命名空间指定一组类似的基本功能,但最好在声明私有变量或方法再次使用它,其他时候使用嵌套命名空间就足够了。7、自动嵌套命名空间嵌套命名空间模式可以为代码单元提供一个有组织的结构层次,但是我们每次创建一个层次时,我们也必须确保它有一个对应的父层次。当层数较多时,会给我们带来很多麻烦,无法快速方便地创建我们想要创建的层。那么如何解决这个问题呢?StoyanStefanov建议创建一个方法,该方法接收一个字符串参数作为一个嵌套,对其进行解析,并使用所需的对象自动填充基本命名空间。下面是这个模式的一个实现:functionextend(ns,nsStr){varparts=nsStr.split("."),parent=ns,pl;pl=parts.length;for(vari=0;i
