RequireJS和JQuery的模塊化編程


基於RequireJS和JQuery的模塊化編程

由於js的代碼邏輯越來越重,一個js文件可能會有上千行,十分不利於開發與維護。最近正在把邏輯很重的js拆分成模塊,在一頓糾結是使用requirejs還是seajs的時候,最終還是偏向於requirejs。畢竟官方文檔比較專業嘛...
不過即便是有完整的官方文檔,仍然遇到不少的問題,比如jquery-ui的使用。
下面就循序漸進的講解一下我遇到的問題,以及解決的辦法。

關於AMD和CMD的理解

AMD(異步模塊定義)的典型就是requirejs,而CMD(通用模塊定義)的典型是淘寶的seajs。

他們的相同點是,都會異步的加載js。但是不同點是,require.js加載完會立即執行;而seajs則是等到進入主函數需要執行時才執行。

如果使用seajs初始的加載執行效率會比較高,但是在使用的過程中可能會取執行js,因此可能會出現卡頓,影響用戶體驗(由於我也沒試過,要是說錯了,別見怪)。而requirejs則是在一開始就把所有加載的js都執行,這時,如果你的模塊中有一些執行方法,它們可能並不會按照你想的順序執行。

因此,如果已經習慣了異步編程,並且希望有完善的文檔推薦使用requirejs;如果是想對執行順序有特殊要求,又方便開發,那么也可以使用seajs。

如何解決requirejs中循環依賴問題

如果你定義的某個a模塊使用到了b模塊,而b模塊又使用了a模塊,那么就會拋出循環依賴的異常。

比如,我這里寫了一個循環依賴的例子。
主頁面:

<!DOCTYPE html> <html> <head> </head> <body> <script data-main="test.js" src="lib/require.js"></script> </body> </html>

主方法:

requirejs.config({
    baseUrl: './' }); requirejs(['js/a'],function (a){ console.log("in test"); a.testfromb(); });

a.js模塊中,atest()方法提供b調用、testfromb()方法調用b的方法

define(function(require){ var b = require("js/b"); console.log("in a"); return { atest:function(){ console.log("test in a"); }, testfromb:function(){ console.log("testfromb in a"); b.btest(); } } });

b模塊中,調用了a的方法。

define(function(require){ var a = require("js/a"); console.log("in b"); return { btest:function(){ console.log("test in b"); a.atest(); } } });

這樣相當於a調用了b的方法,但是b的方法依賴於a的方法,這就造成了循環依賴。瀏覽器會提示錯誤:

Uncaught Error: Module name "js/a" has not been loaded yet for context: _

按照官方文檔的說法,這種屬於設計的問題,應該盡量避免。那么如果避免不了該怎么辦呢?可以這樣修改b模塊:

define(function(require){ // var a = require("js/a"); console.log("in b"); return { btest:function(){ console.log("test in b"); require("js/a").atest(); } } });

這里是等到執行atest()方法時,才加載a模塊。這時,a模塊很顯然已經加載完了 。可以看到輸出的信息:

in b a.js:3 in a test.js:6 in test a.js:9 testfromb in a b.js:6 test in b a.js:6 test in a

同樣的方式,修改a可能就不好使了。這時因為模塊加載的順序是從b開始的。

關於循環依賴的源碼可以參考雲盤

如何在requirejs中使用jquery

如果想要使用jquery比較簡單,直接在main.js中添加對應的依賴即可:

requirejs.config({
    baseUrl: './', paths:{ 'jquery':'lib/jquery' } }); requirejs(['jquery'], function ($){ $('#test').html('test'); });

如何在requirejs中使用jquery插件

對於jquery的插件,比較常見的做法都是傳入一個jquery的對象,在這個jquery對象的基礎上添加插件對應的方法。

首先需要添加jquery插件的依賴,這里用兩個插件舉例子——jquery-ui和jquery-datatables

requirejs.config({
    baseUrl: './', paths:{ 'jquery':'lib/jquery', 'jquery-ui':'lib/jquery-ui', 'jquery-dataTables':'lib/jquery.dataTables' }, shim:{ 'jquery-ui':['jquery'], 'jquery-dataTables':['jquery'] } }); requirejs(['jquery','jquery-ui','jquery-dataTables'], function ($){ .... });

由於jquery插件都需要依賴於jquery,因此可以在shim中指定依賴關系。
除了上面這種使用方法,也可以使用commonJS風格的調用:

define(function(require){ var $ = require('jquery'); require('jquery-ui'); require('jquery-dataTables'); //下面都是測試,可以忽略 var _test = $('#test'); _test.selectmenu({ width : 180, change : function(event, ui) { console.log('change'); } }); return { test:function(){ //測試jquery-ui _test.append($('<option>test1</option><option>test1</option>')); _test.selectmenu("refresh"); //測試jquery-datatables var _table = $('table'); _table.dataTable(); } } });

不過,執行上面的代碼,會報一個異常:

Uncaught TypeError: _table.dataTable is not a function

這是因為,dataTables並不是一個require風格的模塊,因此直接這樣引入,並不會執行它內部的匿名函數。可以修改它的匿名函數,傳入$對象,在最后一行:

     */

    return $.fn.dataTable; //}));原來是這樣 }($)));//這里增加執行這個匿名函數,並且傳入$對象。 }(window, document));

這也是在網上搜的方法,原理奈何經驗不足....

樣例代碼可以參考雲盤,由於引入的資源不是很全,所以會報錯,可以直接忽略,因為能執行UI插件就表示已經成功了。

requirejs使用jquery-ui的問題

由於requirejs加載js文件后會立即執行,如果你的jquery ui 插件需要刷新DOM頁面,那么可能會導致頁面的事件失效。

比如,你的模塊在加載后,對頁面的某個元素$('#test')綁定了click事件。但是使用了某個UI插件,這個插件會重新渲染DOM元素,test對應的click事件就失效了。

解決辦法:

  • 把事件綁定推遲到DOM元素渲染完后再手動觸發綁定;
  • 也可以使用事件捕獲代替DOM元素的事件綁定(太麻煩了...不推薦)。

比如在DOM重構的JS模塊中,執行渲染的代碼下面:

require("xxx").initEvents();

常見場景:

比如我在頁面中使用了jquery-steps這個UI插件,它會對頁面進行重新渲染。這就導致我最開始綁定的事件都失效了....只有推遲到這個js重構完頁面,再綁定才行。

 
分類:  JavaScript


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM