天天看點

子產品化你的JS代碼

子產品化你的JS代碼

為什麼要使用子產品模式?

因為在全局作用域中聲明的變量和函數都自動成為全局對象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對象的内部引用,這樣就可以在内部對子產品執行個體進行修改,包括添加、删除方法和屬性

    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; 

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()); 

        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;

子產品檔案中的内容會被當做好像包含在作用域閉包中一樣來處理,就和函數閉包子產品一樣;

作者:蘇福

來源:51cto

繼續閱讀