JavaScript 模块
JavaScript 模块
- 一般来讲, 模块是一个独立的JavaScript文件
- 模块文件可以包含一个类定义、 一组相关的类、 一个实用函数库或者是一些待执行的代码
- 以模块的形式编写代码,任何符合模块编码形式的JavaScript代码段,都可当做一个模块
- JavaScript中未定义支持模块的语言结构, 所以在JavaScript中编写模块化的代码,其实是遵循某一种编码约定
- 模块化支持大规模的程序开发, 处理分散源中代码的组装,确保代码正确运行, 即便其中包含不期望出现的模块代码, 也可正确执行
- 不同的模块,应避免修改全局执行上下文
- 已使后续模块应当在其所期望运行的原始(或接近原始)上下文中执行
- 模块应当尽可能少地定义全局标识,理想状况是,所有模块都不应当定义超过一个全局标识
1 用做命名空间的对象
- 在模块创建过程中避免污染全局变量的一种方法是使用一个对象作为命名空间
- 它将函数和值作为命名空间对象属性存储起来(可以通过全局变量引用)
- 不定义全局函数和变量
- 使用对象作为命名空间,常用做法是在对象空间内创建类
//定义sets对象作为空间命名对象 //将Set类的构造函数、成员等作为其属性 var sets = { Set:function(){}; Set.prototype:{ constructor:Set, m1:function(){}, m2:function(){} } }; //使用时,需要通过命名空间来调用所需的构造函数 var S = new sets.Set(); //如果频繁使用,也可先导入Set类到全局空间 var Ss = sets.Set; var SS = new Ss();//不在使用前缀
- 多重命名空间嵌套,如果sets模块是另外一个组更大的模块集合,命名空间是collections.sets,模块代码写法示例
//sets模块开始写法推荐: var collections;//声明(或重新声明)这个全局变量 if (!collections){ collections={};//如果它原本不存在,则创建一个顶层的命名空间对象 } collections.sets= {}//将sets命名空间创建在它的内部 collections.sets.AbstractSet = function(){}//在collections.sets内定义set类
- 最顶层的命名空间往往用来标识创建模块的作者或组织,并避免命名空间的命名冲突。推荐方法时常用互联网域名组成部分反转来命名
- 这样创建的命名空间前缀是全局唯一的,一般不会被其他模块作者采用
- 比如网站myweb.com,命名空间就是com.myweb.collections.sets
- 使用很长的命名空间来导入模块的方式非常重要,一般应将整个模块导入全局命名空间,而不是导入(命名空间中的某个)单独的类
- var sets = com.myweb.collections.sets;
- 按照约定,模块的文件名应当和命名空间匹配。
- sets模块应当保存在文件sets.js中
- 如模块使用命名空间collections.sets,sets.js文件应当保存在目录collections/下(这个目录还应当包含另一个文件maps.js)
- 使用命名空间com.myweb.collections.sets的模块应当在文件com/myweb/collections/sets.js中
2 作为私有命名空间的函数
- 将函数作用域用做模块的私有命名空间(有时称为"模块函数")
- 让代码在一个私有命名空间中运行, 仅需将这段代码封装到一个立即执行的匿名函数中
- 立即执行的匿名函数:
- (function(){ /*需要运行在私有命名空间的代码段*/ }())
- 开始的左圆括号确保这是一个函数表达式, 而不是函数定义语句
- 可通过添加函数名让代码变得更加清晰。如 “invocation”强调函数在定义后应立即执行, “namespace“强调这个函数被用做命名空间
//声明全局变量Set,使用一个函数的返回值给它赋值 //它的返回值将赋值给Set,而不是将这个函数赋值给Set //这是一个函数表达式,不是一条语句,所以函数名invocation并未创建全局变量 var Set = (function invocation(){function Set(){//这个构造函数是局部变量 this.values={};//这个对象的属性用来保存这个集合 this.n = 0;//集合中值的个数 this.add.apply{this, arguments);//将所有的参数都添加至集合中 //给Set.prototype定义实例方法 Set.profotype.contains = function(value) { return this.values.hasOwnProperty(v2s(value)); }; Set.prototype.size = function(){return this.n;}; Set.prototype.add = function(){}; Set. prototype. remove = function(){}; //这里是上面的方法用到的一些辅助函数和变量 //它们不属于模块的共有API,但它们都隐藏在这个函数作用域内 //因此我们不必将它们定义为Set的属性或使用下划线作为其前缀 function v2s(val) {} function object!d(o) {} var nextid = 1; //这个模块的共有API是Set()构造函数 //我们需要把这个函数从私有命名空间中导出来 //以便在外部也可以使用它,我们通过返回这个构造函数来导出它 //它变成第一行代码所指的表达式的值 return Set; }());//定义函数后立即执行
- 一旦将模块代码封装进一个函数,就需要一些方法来导出API,以便在模块函数的外部调用API
- 上例模块函数返回一个构造函数, 并赋值给一个全局变量。 将值返回,使API导出在函数作用域之外
- 如果模块API包含多个单元, 则它可以返回命名空间对象
- 将模块函数当做构造函数,通过new来调用,并将它们赋值给this来将其导出,不需要返回任何内容
- 如果定义了全局命名空间对象,模块函数可直接设置那个对象的属性,不需要返回任何内容
//通过返回命名空间对象将API导出 //创建一个全局变量用来存放集合相关的模块 var collections; if (!collections) collections = {}; //定义sets模块 collections. sets = (function namespace(){ //在这里定义多种”集合”类,使用局部变量和函数 //这里省略了具体代码 //通过返回命名空间对象将API导出 return { //导出的属性名和局部变量名字 AbstractSet: AbstractSet, NotSet: NotSet, AbstractEnumerableSet: AbstractEnumerableSet, SingletonSet: SingletonSet, AbstractWritableSet: AbstractWritableSet, ArraySet: ArraySet }()); //将模块函数动作构造函数,通过new来调用,并将它们赋值给this导出API var collections; if (!collections) collections = {}; collections.sets = (new function namespace(){ //这里省略很多代码… //将API导出至this对象 this.AbstractSet = AbstractSet; this. Not Set = Not Set; //注意,这里没有返回值 }()); //如果定义了全局命名空间对象,模块函数可直接设置那个对象的属性 //不用返回任何内容 var collections; if (!collections) collections = {}; collections.sets = (new function namespace(){ //将共用API导出到上面创建的命名空间对象上 collections.sets.AbstractSet = AbstractSet; collections.sets.NotSet = NotSet; //导出的操作已经执行,就不需要return语句了 }());