1. 程式人生 > >使用requirejs編寫模組化程式碼

使用requirejs編寫模組化程式碼

寫在前面

最早接觸javascript的時候,javascript程式碼直接扔在script標籤裡面就完事了。

反正程式碼不多,互動簡單,邏輯不難,和HTML混在一起也未嘗不可。

後來互動越來越複雜,程式碼越多越多了,我們就開始把JS程式碼獨立到了單獨的JS檔案中。

公共的庫引用在前,自己的邏輯程式碼引用在後,全域性變數定義在HTML內部,在獨立JS檔案中直接使用變數就好。

我們會經常看到下面這種程式碼:

  <script src="1.js"></script>
  <script src="2.js"></script>
  <script
src="3.js">
</script>   <script src="4.js"></script>   <script src="5.js"></script>   <script src="6.js"></script>

通過script標籤順序去js管理依賴關係。

首先,載入的時候,瀏覽器會停止網頁渲染,載入檔案越多,網頁失去響應的時間就會越長;

其次,由於js檔案之間存在依賴關係,因此必須嚴格保證載入順序(比如上例的1.js要在2.js的前面),依賴性最大的模組一定要放到最後載入.

當依賴關係很複雜的時候,程式碼的編寫和維護都會變得困難。

而requirejs的誕生便是為了解決這個問題。

官網把requirejs 下載回來之後。使用一般的方法引入:

<script src="js/require.js"></script>

但是這樣的方法,還是可能在載入require.js的時候導致網頁失去響應。解決方案一般有兩種:

  1. 把上面的程式碼放到網頁底部

  2. 使用非同步的方法載入,如下:

<script src="js/require.js" defer async="true" ></script>

async屬性 表明這個檔案需要非同步載入,避免網頁失去響應。

不過IE下不支援這個屬性,只支援defer,所以可以把defer也寫上。

載入主模組

在上一步,我們已經引入了require了,那麼require怎麼知道我們究竟要載入什麼東西呢?答案是使用data-main屬性。
假設我們的主模組為js/home.js,引入程式碼應該如下:

 <script src="js/require.js" data-main="js/home"></script>
  //require.js預設檔案字尾為js,所以home.js可以寫成home。

接下來我使用58HouseSearch 的程式碼來講解一下require的使用。

在此專案裡面,重構前大概就是JS變數漫天飛,js檔案裡面各種函式到處亂放。一開始用起來還沒什麼,後來加入了更多功能的時候,JS程式碼維護起來就疼不欲生了。因此託了個小夥伴幫忙使用模組化思想重構了一下JS程式碼。

上面說了,我們首先需要建立我們的模組,在這個專案裡面,主模組叫home.js。

home.js中我們需要配置一下require.config.

require.config({
    baseUrl: '/DomainJS/',
    paths: {
        jquery: "lib/jquery-1.11.3.min",
        "AMUI": "lib/amazeui.2.7.1.min",
        "jquery.range": "lib/jquery.range",
        "es5": "lib/es5",
        "mapController": "mapController",
        "addToolbar": "addToolbar",
    },
    shim: {
        "addToolbar": {
            deps: ["jquery"]
        },
        "jquery.range": {
            deps: ["jquery"]
        }
    }
});

在這裡我主要配置了一下baseURL(所有模組的查詢根路徑),paths(名稱對映),shim(
為那些沒有使用define()來宣告依賴關係、設定模組的”瀏覽器全域性變數注入”型指令碼做依賴和匯出配置。)

關於require.config的詳細內容可以看下下面這些文章:

配置做完了,我們也可以開始真正寫我們的邏輯程式碼了,我們使用require來載入我們需要的庫。
程式碼如下:

require(['domready!', 'jquery', 'AMUI', 'mapController', 'city', 'commuteGo'], 

function (doc, $, AMUI, mapController, city, commuteGo) {
    city.initAllCityInfo();
    mapController.init();

    $("input[name='locationType']").bind('click', 
    mapController.locationMethodOnChange)

    $("input[name='vehicle']").bind('click', commuteGo.go)

    $('#Get58Data').bind('click', function(e) {
        e.preventDefault();

        mapController.Get58DataClick();
        e.stopPropagation();
    });


    $.ajax({
        type: "post",
        url: "../Commom/GetPVCount",
        data: { },
        success: function (result)
        {
            if (result.IsSuccess){
                $("#lblPVCount").text(result.PVCount);
            }else {
                $("#lblPVCount").text(0);
                console.log(result.Error);
            }
        }
    });

    $('#search-offcanvas').offCanvas({ effect: 'overlay' });

    $(".amap-sug-result").css("z-index", 9999);
})

忽略function裡面的具體邏輯,載入如下:

require(['domready!', 'jquery', 'AMUI', 'mapController', 'city', 'commuteGo'], 
function (doc, $, AMUI, mapController, city, commuteGo){

//todo

});

第一個引數為一個數組,表示所依賴的模組,此處為[‘domready!’, ‘jquery’, ‘AMUI’, ‘mapController’, ‘city’, ‘commuteGo’];

第二個引數為回撥函式,當前面指定的模組都全部載入成功之後,便呼叫此函式。載入的模組會以引數形式傳入此函式,從而在回撥函式內部就可以使用這些模組啦。

require()非同步載入所需模組的時候,此時瀏覽器並不會失去響應;當前面的模組載入成功之後,執行回撥函式才會執行我們的邏輯程式碼,因此解決了依賴性問題。

講完了模組載入,我們下面講一下模組編寫。

AMD模組編寫

require.js載入的模組的採用的AMD規範。所以我們的模組必須按照AMD的規定來寫。

模組有兩個情況,不依賴其他模組和依賴其他模組。

不依賴其他模組

直接define定義,使用function回撥。

define(function () {

    //獲取URL中的引數
    var getQueryString=  function (name) {
        var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
        var r = window.location.search.substr(1).match(reg);
        if (r != null) return unescape(r[2]); return null;
    }
    return {
        getQueryString: getQueryString,
    };
})

依賴其他模組

define中如同require一樣,用陣列表明需要載入的模組,function回撥。

define(['mapSignleton', 'city', 'transfer'], 
function(mapSignleton, city, transfer) {
    var _map = mapSignleton.map;
    var _workMarker = null;
    var _markerArray = [];
    var load = function(x, y, locationName) {
        _workMarker = new AMap.Marker({
            map: _map,
            title: locationName,
            icon: 'http://webapi.amap.com/theme/v1.3/markers/n/mark_r.png',
            position: [x, y]
        });
    }

    var add = function(address, rent, href, markBG) {
        new AMap.Geocoder({
            city: city.name,
            radius: 1000
        }).getLocation(address, function(status, result) {

            if (status === "complete" && result.info === 'OK') {
                var geocode = result.geocodes[0];
                var rentMarker = new AMap.Marker({
                    map: _map,
                    title: address,
                    icon: markBG ? 'IMG/Little/' +
                    markBG : 'http://webapi.amap.com/theme/v1.3/markers/n/mark_b.png',
                    position: [geocode.location.getLng(), geocode.location.getLat()]
                });
                _markerArray.push(rentMarker);

                rentMarker.content = "<div><a target = '_blank' href='" 
                + href + "'>房源:" + address + "  租金:" + rent + "</a><div>"
                rentMarker.on('click', function(e) {
                    transfer.add(e, address);
                });
            }
        })
    };

    var clearArray = function() {
        if (_markerArray && _markerArray.length > 0) 
        _map.remove(_markerArray);
        _markerArray = [];
    }

    var clear = function() {
        if (_workMarker) {
            _map.remove(_workMarker);
        }
    }

    return {
        load: load,
        add: add,
        clearArray: clearArray,
        clear: clear
    };
});

這樣的話,一個供require呼叫的模組也就寫好了。

最後感謝小夥伴Larry Sean 幫忙重構程式碼。

全文完。