犀牛书学习笔记(10):模块和命名空间
貌似WEB开发初期,相关脚本语言都是没有考虑到模块化和命名空间的,比如javascript、asp、php等,随着WEB开发规模越来越大,编程语言体系越来越成熟,模块化和命名空间成为必须的语言特性。遗憾的是,javascript目前还没有从语言层面对模块化和命名空间进行支持,但基于语言的灵活性,可以变通的实现。
注:commonJS规范提出了模块化和命名空间规范,NodeJS实现了此规范。
什么是模块?
一般来讲,模块是一个独立的JS文件。模块文件可以包含一个类定义、一组相关类、一个实用函数库或者一些待执行的代码。模块化的目标是支持大规模的程序开发,处理分散源中代码的组装,并且能让代码正确运行,哪怕包含了不需要的模块代码,也可以正确执行代码。
理想状态下,所有模块都不应当定义超过一个全局标识。通过把模块定义在某个函数的内部来实现,定义的变量和函数都属于该函数的局部变量,在函数外不可见。实际上,可以将这个函数作用域用做模块的命名空间(模块函数)。而函数都是作为全局对象的一个属性,而javascript代码模块化,所必须遵守的最重要的规则就是:避免定义全局变量。因为,当定义一个全局变量时,都有被其它模块覆盖的危险。
那么该如何做呢?
var ModuleClass = {}; ModuleClass.函数名1=function(){ 函数体;//这个函数看起来是一个对象的方法。对,可以利用对象作为一个名字空间 } ModuleClass.函数名2=function(){ 函数体; }
ModuleClass是一个对象,使用对象作为一个名字空间,将所有的函数及变量都放在其中。即使函数或变量重名( 即不同对象中有相同函数名),它们不在一个名字空间中,这样就不会有被覆盖的危险了。 ModuleClass其实就是在全局变量中定义了一个标示符,为了避免重名,我们应该保证对象名称的唯一性。
命名空间
单独一个对象名称唯一性保证是非常困难的,在javasript中可以有两个办法来避免这种冲突。
目录方式:将同名对象放到不同目录,比如放到util目录中“util/ModuleClass”,代码应该这样写:
var util; if(!util) util = {};//第一级域名 util.ModuleClass = {};//第二级域名 util.ModuleClass.函数名1=function(){ 函数体; } util.ModuleClass.函数名2=function(){ 函数体; }
这是一级目录的形式,如果是多级目录呢,就编程了JAVA包命名方式了。
类JAVA包名方式:com.公司名.项目名.util.ModuleCalss,相应的目录路径是:com/公司目录/项目目录/util/ModuelClass.js
var com; if(!com) com={};//如果com不存在,则新生成一个 else if(typeof com!="object"){//如果已存在,但不是一个对象,则抛出一个异常 throw new Error("com already exists and is not an object"); } if(!com.util) com.util={};//如果com.util不存在则新生成一个 else if(typeof com.util!="object"){//如果com存在,但不是一个对象,则抛出一个异常 throw new Error("com.util already exists and is not an object"); } if(!com.util.ModuleClass){//如果com.util.ModuleClass存在,则直接抛出异常 throw new Error("com.util.ModuleClass already exists"); } com.util.ModuleClass = {//在com.util.ModuleClass不存在的情况下,我们才能正常使用在此命名空间下定义的代码 函数1:function(){ 函数体; }, 函数2:function(){ 函数体; } };
JAVA中包命名和目录路径的对应关系本身就非常不灵活,JVM类加载机制依赖目录路径来查找类,并加载。在动态语言中,应该像python一样,只需将模块文件放到一个目录,然后在类中引入即可,带来相对路径名称。改变目录,只需要修改类的引入申明代码,而不需要修改模块文件。
从语言角度,这非常的不合理且弱智,期待所有EMCAcript实现都实现CommonJS规范。
犀牛书提供了一个工具类,帮助程序员来创建命名空间。
代码/* == Module and NameSpace tool-func == * author : hongru.chen * date : 2010-12-05 */ var Module; //check Module --> make sure 'Module' is not existed if (!!Module && (typeof Module != 'object' || Module.NAME)) throw new Error("NameSpace 'Module' already Exists!"); Module = {}; Module.NAME = 'Module'; Module.VERSION = 0.1; Module.EXPORT = ['require', 'importSymbols']; Module.EXPORT_OK = ['createNamespace', 'isDefined', 'modules', 'globalNamespace']; Module.globalNamespace = this; Module.modules = {'Module': Module}; // create namespace --> return a top namespace Module.createNamespace = function (name, version) { if (!name) throw new Error('name required'); if (name.charAt(0) == '.' || name.charAt(name.length-1) == '.' || name.indexOf('..') != -1) throw new Error('illegal name'); var parts = name.split('.'); var container = Module.globalNamespace; for (var i=0; i<parts.length; i++) { var part = parts[i]; if (!container[part]) container[part] = {}; container = container[part]; } var namespace = container; if (namespace.NAME) throw new Error('module "'+name+'" is already defined'); namespace.NAME = name; if (version) namespace.VERSION = version; Module.modules[name] = namespace; return namespace; }; // check name is defined or not Module.isDefined = function (name) { return name in Module.modules; }; // check version Module.require = function (name, version) { if (!(name in Module.modules)) throw new Error('Module '+name+' is not defined'); if (!version) return; var n = Module.modules[name]; if (!n.VERSION || n.VERSION < version) throw new Error('version '+version+' or greater is required'); }; // import module Module.importSymbols = function (from) { if (typeof form == 'string') from = Module.modules[from]; var to = Module.globalNamespace; //dafault var symbols = []; var firstsymbol = 1; if (arguments.length>1 && typeof arguments[1] == 'object' && arguments[1] != null) { to = arguments[1]; firstsymbol = 2; } for (var a=firstsymbol; a<arguments.length; a++) { symbols.push(arguments[a]); } if (symbols.length == 0) { //default export list if (from.EXPORT) { for (var i=0; i<from.EXPORT.length; i++) { var s = from.EXPORT[i]; to[s] = from[s]; } return; } else if (!from.EXPORT_OK) { // EXPORT array && EXPORT_OK array both undefined for (var s in from) { to[s] = from[s]; return; } } } if (symbols.length > 0) { var allowed; if (from.EXPORT || form.EXPORT_OK) { allowed = {}; if (from.EXPORT) { for (var i=0; i<form.EXPORT.length; i++) { allowed[from.EXPORT[i]] = true; } } if (from.EXPORT_OK) { for (var i=0; i<form.EXPORT_OK.length; i++) { allowed[form.EXPORT_OK[i]] = true; } } } } //import the symbols for (var i=0; i<symbols.length; i++) { var s = symbols[i]; if (!(s in from)) throw new Error('symbol '+s+' is not defined'); if (!!allowed && !(s in allowed)) throw new Error(s+' is not public, cannot be imported'); to[s] = form[s]; } }
使用方式
//为模块创建名称空间 Module.createNamespace("com.davidflannagan.Class"); //填充名称空间 com.davidflanagan.Class.define=function(data){}; com.davidflanagan.Class.provides=functon(o,c){}; //检查模块指定版本是否存在 Module.require("com.davidflanagan.Class",1.0); //使用模块名称标示符导入模块 Module.importSymbols(Module); importSymbols(com.davidflanagan.Class); var Class={}; importSymbols(com.davidflanagan.Class,Clas,"define");
至此,javascript核心语法基本介绍完毕。