MyException - 我的异常网
当前位置:我的异常网» JavaScript » JS之命名空间方式解析

JS之命名空间方式解析

www.MyException.Cn  网友分享于:2013-10-08  浏览:0次
JS之命名空间模式解析

简介

在SF上看到这样一个提问:

如题,因为不得已的原因,需要写若干个全局函数。但又不想这样:

window.a = function(){}
window.b = function(){}
window.c = function(){}

题主问有什么好的写法?

解答:

如果你用 jQuery,你可以这样写

$.extend(window, {
    a: function() {},
    b: function() {},
    c: function() {}
});

如果你不用 jQuery,可以直接实现类似的 extend:

(() => {
    var defining = {
        a: function() { },
        b: function() { },
        c: function() { }
    };

    Object.keys(defining).forEach(key => {
        window[key] = defining[key];
    });
})();

在JavaScript中,命名空间可以帮助我们防止与全局命名空间下的其他对象或变量产生冲突。命名空间也有助于组织代码,有更强的可维护性和可读性。本文旨在探讨JavaScript里的几种常见命名空间模式,为我们提供一个思路。

一般命名空间的实现都以window为根。当向window申请a.b.c的命名空间时,首先在window中查看是否存在a这个成员,如果没有则在 window下新建一个名为a的空关联数组,如果已经存在a,则继续在window.a中查看b是否存在,以此类推。下面分别是Atlas和YUI中的实 现方法。

Atlas命名空间的实现方法:

Function.registerNamespace =function(namespacePath){
    //以window为根
    var rootObject =window;
    //对命名空间路径拆分成数组
    var namespaceParts =namespacePath.split('.');
    for (var i =0;i <namespaceParts.length;i++) {
        var currentPart =namespaceParts[i];
        //如果当前命名空间下不存在,则新建一个Object对象,等效于一个关联数组。
        if(!rootObject[currentPart])      {
           rootObject[currentPart]=new Object();
        }
        rootObject =rootObject[currentPart];
    }
}

另外一种实现方式:

namespace = function(){
    var argus = arguments;
    for(var i = 0; i < argus.length; i++){
        var objs = argus[i].split(".");
        var obj = window;
        for(var j = 0; j < objs.length; j++){
            obj[objs[j]] = obj[objs[j]] || {};
            obj = obj[objs[j]];
        }
    }
    return obj;
};

namespace("tools.base");

我们常常利用js对象字面量的方式来实现js的命名空间:

var school = {
    addClass:function(classnum){
        console.log(classnum);
    },
    addStudent:function(stuId){
        console.log(stuId);
    }
}
school.addClass("2");

在全局作用域中声明的任何变量和函数都是window对象的属性,当名称有冲突时,就会产生一些不可控的问题。全局变量会带来以下问题:

  • 命名冲突
  • 代码的脆弱性
  • 难以测试

在编程开发中合理的使用命名空间,可以避免相同的变量或对象名称产生的冲突。而且,命名空间也有助于组织代码,有更强的可维护性和可读性。JavaScript中虽然没有提供原生的命名空间支持,但我们可以使用其他的方法(对象和闭包)实现类似的效果。下面就是一些常见的命名空间模式:

1.单一全局变量

JavaScript中一个流行的命名空间模式是选择一个全局变量作为主要的引用对象。因为每个可能的全局变量都成为唯一全局变量的属性,也就不用再创建多个全局变量,那么也就避免了和其他声明的冲突。

单一全局变量模式已经在不少的JavaScript类库中使用,如:

  • YUI定义了唯一的YUI全局对象
  • jQuery定义了$和jQuery,$由其他类库使用时使用jQuery
  • Dojo定义了一个Dojo全局变量
  • Closure类库定义了一个goog全局对象
  • Underscore类库定义了一个_ 全局对象

示例如下:

var myApplication = (function() {
    var count = 1;
     function funcur() {
        console.log(count);
     };
     return {
        funcur:funcur
     }
 })();
myApplication.funcur();

虽然单一全局变量模式适合某些情况,但其最大的挑战是确保单一全局变量在页面中是唯一使用的,不会发生命名冲突

2.命名空间前缀

命名空间前缀模式其思路非常清晰,就是选择一个独特的命名空间,然后在其后面声明声明变量、方法和对象。示例如下:

var = myApplication_propertyA = {};
var = myApplication_propertyA = {};

function myApplication_myMethod() {
    // ***
}

从某种程度上来说,它确实减少了命名冲突的发生概率,但其并没有减少全局变量的数目。当应用程序规模扩大时,就会产生很多的全局变量。在全局命名空间内,这种模式对其他人都没有使用的这个前缀有很强的依赖,而且有些时候也不好判断是否有人已经使用某个特殊前缀,在使用这种模式时一定要特别注意。

3.对象字面量表示法

对象字面量模式可以认为是包含一组键值对的对象,每一对键和值由冒号分隔,键也可以是代码新的命名空间。示例如下:

var myApplication = {
    // 可以很容易的为对象字面量定义功能
    getInfo:function() {
        // ***
    },
    
    // 可以进一步支撑对象命名空间
    models:{},
    views:{
        pages:{}
    },
    collections:{}
};

与为对象添加属性一样,我们也可以直接将属性添加到命名空间。对象字面量方法不会污染全局命名空间,并在逻辑上协助组织代码和参数。并且,这种方式可读性和可维护性非常强,当然我们在使用时应当进行同名变量的存在性测试,以此来避免冲突。下面是一些常用的检测方法:

var myApplication = myApplication || {};

if(!myApplication) {
    myApplication  = {};
}

window.myApplication || (window.myApplication || {});

// 针对jQuery
var myApplication = $.fn.myApplication = function() {};

var myApplication = myApplication === undefined ? {} :myApplication;

对象字面量为我们提供了优雅的键/值语法,我们可以非常便捷的组织代码,封装不同的逻辑或功能,而且可读性、可维护性、可扩展性极强。

4.嵌套命名空间

嵌套命名空间模式可以说是对象字面量模式的升级版,它也是一种有效的避免冲突模式,因为即使一个命名空间存在,它也不太可能拥有同样的嵌套子对象。示例如下:

var myApplication = myApplication || {};
 
 // 定义嵌套子对象
 myApplication.routers = myApplication.routers || {};
 myApplication.routers.test = myApplication.routers.test || {};

当然,我们也可以选择声明新的嵌套命名空间或属性作为索引属性,如:

myApplication['routers'] = myApplication['routers'] || {};

使用嵌套命名空间模式,可以使代码易读且有组织性,而且相对安全,不易产生冲突。其弱点是,如果我们的命名空间嵌套过多,会增加浏览器的查询工作量,我们可以把要多次访问的子对象进行局部缓存,以此来减少查询时间。

5.立即调用的函数表达式

立即调用函数(IIFE)实际上就是匿名函数,被定义后立即被调用。在JavaScript中,由于变量和函数都是在这样一个只能在内部进行访问的上下文中被显式地定义,函数调用提供了一种实现私有变量和方法的便捷方式。IIFE是用于封装应用程序逻辑的常用方法,以保护它免受全局名称空间的影响,其在命名空间方面也可以发挥其特殊的作用。示例如下:

;(function (namespace, undefined) {

    // 私有属性
    var foo = "foo";
        bar = "bar";

    // 公有方法和属性
    namespace.foobar = "foobar";
    namespace.sayHello = function () {
        say("Hello World!");
    };

    // 私有方法
    function say(str) {
        console.log("You said:" + str);
    };
})(window.namespace = window.namespace || {});
console.log(namespace.foobar);
//foobar

可扩展性是任何可伸缩命名空间模式的关键,使用IIFE可以轻松实现这一目的,我们可以再次使用IIFE给命名空间添加更多的功能。

6.命名空间注入

命名空间注入是IIFE的另一个变体,从函数包装器内部为一个特定的命名空间“注入”方法和属性,使用this作为命名空间代理。这种模式的优点是可以将功能行为应用到多个对象或命名空间。示例如下:

var myApplication = myApplication || {};
myApplication.utils = {};

;(function () {
    var value = 5;
    
    this.getValue = function () {
        return value;
    }

    // 定义新的子命名空间
    this.tools = {};
}).apply(myApplication.utils);

(function () {
    this.diagnose = function () {
        return "diagnose";
    }
}).apply(myApplication.utils.tools);

// 同样的方式在普通的IIFE上扩展功能,仅仅将上下文作为参数传递并修改,而不是仅仅使用this

还有一种使用API来实现上下文和参数自然分离的方法,该模式感觉更像是一个模块的创建者,但作为模块,它还提供了一个封装解决方案。示例如下:

var ns = ns || {},
    ns1 = ns1 || {};

// 模块、命名空间创建者
var creator = function (val) {
    var val = val || 0;
    
    this.next = function () {
        return val ++ ;
    };

    this.reset = function () {
        val = 0;
    }
}

creator.call(ns);
// ns.next, ns.reset 此时已经存在

creator.call(ns1, 5000);
// ns1包含相同的方法,但值被重写为5000了

console.log(ns.next()); //0
console.log(ns1.next());//5000

命名空间注入是用于为多个模块或命名空间指定一个类似的功能基本集,但最好是在声明私有变量或者方法时再使用它,其他时候使用嵌套命名空间已经足以满足需要了。

7.自动嵌套的命名空间

嵌套命名空间模式可以为代码单元提供有组织的结构层级,但每次创建一个层级时,我们也得确保其有相应的父层级。当层级数量很大时,会给我们带来很大的麻烦,我们不能快速便捷的创建想创建的层级。那么如何解决这个问题呢?Stoyan Stefanov提出,创建一个方法,其接收字符串参数作为一个嵌套,解析它,并自动用所需的对象填充基本名称空间。下面是这种模式的一种实现:

function extend(ns, nsStr) {
    var parts = nsStr.split("."),
        parent = ns,
        pl;

    pl = parts.length;

    for (var i = 0; i < pl; i++) {
        // 属性如果不存在,则创建它
        if (typeof parent[parts[i]] === "undefined") {
            parent[prats[i]] = {};
        }
        parent = parent[parts[i]];
    }
    return parent;
}

// 用法
var myApplication = myApplication || {};
var mod = extend(myApplication, "module.module2");

以前我们必须为其命名空间将各种嵌套显式声明为对象,现在用上述更简洁、优雅的方式就实现了。

参考地址:http://www.cnblogs.com/syfwhu/p/4885628.html

 

文章评论

写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
那些争议最大的编程观点
那些争议最大的编程观点
中美印日四国程序员比较
中美印日四国程序员比较
旅行,写作,编程
旅行,写作,编程
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
 程序员的样子
程序员的样子
我的丈夫是个程序员
我的丈夫是个程序员
鲜为人知的编程真相
鲜为人知的编程真相
老程序员的下场
老程序员的下场
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
总结2014中国互联网十大段子
总结2014中国互联网十大段子
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
Java程序员必看电影
Java程序员必看电影
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
我是如何打败拖延症的
我是如何打败拖延症的
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
代码女神横空出世
代码女神横空出世
程序员都该阅读的书
程序员都该阅读的书
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
如何成为一名黑客
如何成为一名黑客
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
程序员的鄙视链
程序员的鄙视链
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
一个程序员的时间管理
一个程序员的时间管理
程序员必看的十大电影
程序员必看的十大电影
程序员和编码员之间的区别
程序员和编码员之间的区别
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
编程语言是女人
编程语言是女人
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有