模块化你的JS代码

为何要使用模块模式?

由于在全局做用域中声明的变量和函数都自动成为全局对象Window的属性,这常常会致使命名冲突,还会致使一些很是重要的可维护性难题,全局变量越多,引入错误BUG的几率就越大!因此咱们应当尽量少地使用全局变量,模块化的目的之一就是为了解决该问题的!闭包

零全局变量模式

该模式应用场景较少,经过一个IIFE(当即执行的匿名函数),将全部代码包装起来,这样一来全部的变量、函数都被隐藏在该函数内部,不会污染全局。app

使用情景:异步

  1. 当该代码不会被其它代码所依赖时;模块化

  2. 当不须要在运行时不断的扩展或修改该代码时;函数

  3. 当代码较短,且无需和其它代码产生交互时;ui

单全局变量模式

基本定义

单全局变量模式即只建立一个全局变量(或尽量少地建立全局变量),且该全局变量的名称必须是独一无二的,不会和如今、未来的内置API产生冲突,将全部的功能代码都挂载到这个全局变量上。this

它已经被普遍应用于各类流行的类库中,如:spa

  1. YUI定义了惟一的YUI全局对象prototype

  2. JQuery定义了两个全局对象,$和JQuerycode

  3. Dojo定义了一个dojo全局对象

  4. 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(也能够直接返回一个内部函数)

因此,模块模式须要具有两个必要条件:

  1. 必须有外部的封闭函数,且该函数必须至少被调用一次(每次调用都会建立一个新的模块实例),如CoolModule

  2. 封闭函数必须至少有一个内部函数被返回,这样内部函数才能在私有做用域中造成闭包,而且能够访问或修改私有的状态

单例模块模式的实现:

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下的全部方法都是和事件相关的。

  1. 常见的用法是为每个单独的JS文件建立一个新的全局变量来声明本身的命名空间;

  2. 每一个文件都须要给一个命名空间挂载功能;这时就须要首先保证该命名空间是已经存在的,能够在单全局变量中定义一个方法来处理该任务:该方法在建立新的命名空间时不会对已有的命名空间形成破坏,使用命名空间时也不须要再去判断它是否存在。

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();
  1. import能够将一个模块中的一个或多个API导入到当前做用域中,并分别绑定在一个变量上;

  2. module会将整个模块的API导入并绑定到一个变量上;

  3. export会将当前模块的一个标识符(变量、函数)导出为公共API;

  4. 模块文件中的内容会被当作好像包含在做用域闭包中同样来处理,就和函数闭包模块同样;

相关文章
相关标签/搜索