本文深入解析RequireJS模块化开发中遇到的核心问题,包括AMD与CMD加载机制差异、模块间循环依赖的延迟加载解决方案,以及jQuery及其插件(如jquery-ui、datatables)在RequireJS环境下的正确集成方法,特别是针对DOM重渲染导致事件失效的处理策略。
RequireJS模块化开发背景与选型
随着前端JavaScript代码逻辑日益复杂,单个JS文件可能包含上千行代码,这给开发与维护带来了巨大挑战。近期,笔者尝试将逻辑复杂的JS代码拆分为独立模块,在对比RequireJS与Sea.js后,最终选择了RequireJS,主要因其官方文档更为详尽且专业。
尽管拥有完善的官方文档,在实际开发过程中仍会遇到诸多具体问题,例如jQuery UI插件的集成与使用便是其中之一。
下文将逐步梳理笔者在开发中遇到的具体问题及其对应的解决方案。
AMD与CMD规范对比
关于AMD和CMD的理解
AMD(异步模块定义)规范的典型实现是RequireJS,而CMD(通用模块定义)规范的典型代表则是淘宝开源的Sea.js。
两者的共同点在于均采用异步方式加载JS文件。然而,它们的执行时机存在显著差异:RequireJS在模块加载完成后会立即执行;而Sea.js则遵循延迟执行策略,即直到主函数运行到需要该模块时才执行。
若使用Sea.js,初始加载阶段的执行效率相对较高,但在后续使用模块时可能会触发代码执行,从而导致页面卡顿,影响用户体验(注:此点基于理论分析,笔者未实际验证,若有误请见谅)。相比之下,RequireJS会在初始化阶段执行所有已加载的JS模块,这可能导致模块内的某些执行方法并非按照开发者预期的顺序运行。
因此,如果开发者已习惯异步编程模式,且看重完善的文档支持,推荐使用RequireJS;若对模块执行顺序有严格要求,且希望开发过程更加便捷,则可以考虑使用Sea.js。
解决RequireJS循环依赖问题
如何解决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();
}
}
});
上述修改将a模块的加载推迟至atest()方法执行时。此时,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及插件
如何在requirejs中使用jquery
若要在RequireJS中使用jQuery,只需在main.js中配置相应的依赖即可:
requirejs.config({
baseUrl: './',
paths:{
'jquery':'lib/jquery'
}
});
requirejs(['jquery'],
function ($){
$('#test').html('test');
});
如何在requirejs中使用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并非符合RequireJS规范的模块,直接引入不会执行其内部的匿名函数。可以通过修改其匿名函数,传入$对象,并在最后一行添加如下代码:
*/
return $.fn.dataTable;
//}));原来是这样
}($)));//这里增加执行这个匿名函数,并且传入$对象。
}(window, document));
这是网上搜索到的方法,由于笔者经验不足,对其原理理解尚浅。
样例代码可参考云盘。由于引入的资源可能不全,运行时会报错,可直接忽略,只要UI插件能正常执行即表示集成成功。
jQuery UI插件与DOM重渲染处理
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重构页面完成后再进行事件绑定。
以上所述是小编针对RequireJS和JQuery的模块化编程日常问题解析的全部叙述,希望对大家有所帮助!