为何要运用模块形式?
因为在全局作用域中声明的变量和函数都自动成为全局对象Window的属性,这经常会致使定名争执,还会致使一些非常重要的可维护性困难,全局变量越多,引入毛病BUG的几率就越大!所以我们应该尽量少地运用全局变量,模块化的目标之一就是为了处置惩罚该题目的!
零全局变量形式
该形式运用场景较少,经由过程一个IIFE(马上实行的匿名函数),将一切代码包装起来,如许一来一切的变量、函数都被隐藏在该函数内部,不会污染全局。
运用情形:
当该代码不会被别的代码所依靠时;
当不须要在运行时不停的扩大或修正该代码时;
当代码较短,且无需和别的代码发生交互时;
单全局变量形式
基础定义
单全局变量形式即只建立一个全局变量(或尽量少地建立全局变量),且该全局变量的称号必需是举世无双的,不会和如今、将来的内置API发生争执,将一切的功用代码都挂载到这个全局变量上。
它已被普遍运用于种种盛行的类库中,如:
YUI定义了唯一的YUI全局对象
JQuery定义了两个全局对象,$和JQuery
Dojo定义了一个dojo全局对象
Closure定义了一个goog全局对象
例子:
var Mymodule= {};
Mymodule.Book = function(){...};
Mymodule.Book.prototype.getName = function(){....};
Mymodule.Car = function(){...};
Mymodule.Car.prototype.getWheels = function(){....};
一个模块的定义
模块是一种通用的功用片断,它并没有建立新的全局变量或定名空间,相反,一切的代码都存放于一个单函数中,可以用一个称号来示意这个模块,一样这个模块可以依靠其他模块。
function CoolModule(){
var something = 'cool';
var another = [1,2,3];
function doSomething(){
console.log( something);
}
function doAnother(){
console.log(another.join('!'));
}
return {
doSomething: doSomething,
doAnother: doAnother
};
}
var foo = CoolModule();
foo.doSomething(); //cool
foo.doAnother(); //1!2!3
这里的CoolModule 就是一个模块,不过它只是一个函数,这里挪用CoolModule函数来建立一个模块的实例foo,此时就构成了闭包(因为CoolModule返回一个对象,个中的一个属性援用了内部函数),模块CoolModule返回的对象就是该模块的大众API(也可以直接返回一个内部函数)
所以,模块形式须要具有两个必要条件:
必需有外部的关闭函数,且该函数必需至少被挪用一次(每次挪用都邑建立一个新的模块实例),如CoolModule
关闭函数必需至少有一个内部函数被返回,如许内部函数才能在私有作用域中构成闭包,而且可以接见或修正私有的状况
单例模块形式的完成:
var foo = ( function CoolModule(){
...//代码同上例
})();
foo.doSomething();
foo.doAnother();
还可以经由过程在模块内部保存对大众API对象的内部援用,如许就可以在内部对模块实例举行修正,包含增加、删除要领和属性
function CoolModule(){
var something = 'cool';
var another = [1,2,3];
function change() {
pubicAPI.doSomething = doAnother;
}
function doSomething(){
console.log( something);
}
function doAnother(){
console.log(another.join('!'));
}
var pubicAPI = {
change: change,
doSomething: doSomething
};
return pubicAPI;
}
var foo = CoolModule();
foo.doSomething(); //cool
foo.change();
foo.doSomething(); //1!2!3
var foo1 = CoolModule();
foo1.doSomething(); //cool
当代的模块机制
定名空间是简朴的经由过程在全局变量中增加属性来示意的功用性分组。
将差别功用依据定名空间举行分组,可以让你的单全局变量变得有条有理,同时可以让团队成员可以晓得新功用应该在哪一个部份中定义,或许去哪一个部份查找已有功用。
比方:定义一个全局变量Y,Y.DOM下的一切要领都是和操纵DOM相干的,Y.Event下的一切要领都是和事宜相干的。
罕见的用法是为每一个零丁的JS文件建立一个新的全局变量来声明本身的定名空间;
每一个文件都须要给一个定名空间挂载功用;这时候就须要起首保证该定名空间是已存在的,可以在单全局变量中定义一个要领来处置惩罚该使命:该要领在建立新的定名空间时不会对已有的定名空间形成损坏,运用定名空间时也不须要再去推断它是不是存在。
var MyGolbal = {
namespace: function (ns) {
var parts = ns.split('.'),
obj = this,
i, len = parts.length;
for(i=0;i<len;i++){
if(!obj[parts[i]]){
obj[parts[i]] = {}
}
obj = obj[parts[i]];
}
return obj;
}
};
MyGolbal.namespace('Book'); //建立Book
MyGolbal.Book; //读取
MyGolbal.namespace('Car').prototype.getWheel = function(){...}
大多数模块依靠加载器或治理器,本质上都是将这类模块定义封装进一个友爱的API
var MyModules = (function Manager() {
var modules = {};
function define(name, deps, impl) {
for(var i=0; i<deps.length; i++){
deps[i] = modules[deps[i]];
}
modules[name] = impl.apply(impl,deps);
}
function get(name) {
return modules[name];
}
return {
define: define,
get: get
};
})();
以上代码的中心是modules[name] = impl.apply(impl,deps);
,为了模块的定义引入了包装函数(可以传入任何依靠),而且将模块的API存储在一个依据名字来治理的模块列表modules对象中;
运用模块治理器MyModules来治理模块:
MyModules.define('bar',[],function () {
function hello(who) {
return 'let me introduce: '+who;
}
return{
hello: hello
};
});
MyModules.define('foo',['bar'],function (bar) {
var hungry = 'hippo';
function awesome() {
console.log(bar.hello(hungry).toUpperCase());
}
return {
awesome: awesome
};
});
var foo = MyModules.get('foo');
foo.awesome();//LET ME INTRODUCE: HIPPO
异步模块定义(AMD):
define('my-books', ['dependency1','dependency2'],
function (dependency1, dependency2) {
var Books = {};
Books.author = {author: 'Mr.zakas'};
return Books; //返回大众接口API
}
);
经由过程挪用全局函数define(),并给它传入模块名字、依靠列表、一个工场要领,依靠列表加载完成后实行这个工场要领。AMD模块形式中,每一个依靠都邑对应到自力的参数传入到工场要领里,即每一个被定名的依靠末了都邑建立一个对象被传入到工场要领内。模块可所以匿名的(即可以省略第一个参数),因为模块加载器可以依据JavaScript文件名来当作模块名字。
要运用AMD模块,须要经由过程运用与AMD模块兼容的模块加载器,如RequireJS、Dojo来加载AMD模块
requre(['my-books'] , function(books){
books.author;
...
}
)
以上所说的模块都是是基于函数的模块,它并非一个能被稳固辨认的形式(编译器没法辨认),它们的API语义只是在运行时才会被斟酌进来。因而可以在运行时修正一个模块的API
将来的模块机制
ES6为模块增加了一级语法支撑,每一个模块都可以导入别的模块或模块的特定API成员,一样也可以导出本身的API成员;ES6的模块没有‘行内’花样,必需被定义在自力的文件中(一个文件一个模块)
ES6的模块API越发稳固,因为编译器可以辨认,在编译时就检核对导入的API成员的援用是不是实在存在。若不存在,则编译器会在运行时就抛出‘初期’毛病,而不会像平常一样在运行期采纳动态的处置惩罚方案;
bar.js
function hello(who) {
return 'let me introduce: '+who;
}
export hello; //导出API: hello
foo.js
//导入bar模块的hello()
import hello from 'bar';
var hungry = 'hippo';
function awesome() {
console.log(hello(hungry).toUpperCase());
}
export awesome;//导出API: awesome
baz.js
//完全导入foo和bar模块
module foo from 'foo';
module bar from 'bar';
foo.awesome();
import可以将一个模块中的一个或多个API导入到当前作用域中,并离别绑定在一个变量上;
module会将全部模块的API导入并绑定到一个变量上;
export会将当前模块的一个标识符(变量、函数)导出为大众API;
模块文件中的内容会被当作彷佛包含在作用域闭包中一样来处置惩罚,就和函数闭包模块一样;