模塊化你的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. 模塊文件中的內容會被當作好像包含在做用域閉包中同樣來處理,就和函數閉包模塊同樣;

相關文章
相關標籤/搜索