专栏首页xingoo, 一个梦想做发明家的程序员基于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重构完页面,再绑定才行。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【AngularJS】—— 5 表单

    这部分,我们写一个表单程序,使用angularjs的检测并完成表单属性的获取与拷贝。   在AngularJS中,也支持html5中多种控件的自动检测,如...

    用户1154259
  • RequireJS实例分析

      随着JS越来越庞大,已经不仅仅是以前复制粘贴做特效的时代了,JS越来越偏向于业务逻辑与应用。恰逢Node的流行,JS在web开发中占有越来越重要的地位。由...

    用户1154259
  • 二叉排序树1

    二叉排序树,是一种规整的二叉树。每个节点的左子树都小于他,每个节点的右子树都大于他。 二叉树的遍历: void InOrderTree(BTree *b){ ...

    用户1154259
  • 响铃:入局双11营销大战,搜狗翻译宝Pro的“新生活方式”宣言

    米家、欧瑞博等AI智能硬件厂商大幅度折扣,腾讯也跑到对头阿里的天猫开设了旗舰店,售卖“听听”智能音箱。此外,智能翻译硬件也盯上了双11,搜狗翻译宝Pro推出了“...

    曾响铃
  • python的抽象基类

    与jvm上的语言不一样,python的语言没有interface关键字,而且除了抽象基类,每个类都有相应的接口:类实现或继承的公开属性(方法或数据类型)

    哒呵呵
  • 如何向Hive表加载数据

    使用追加的方式将test_user表中id大于3并且小于5的数据插入到my_table表中,执行结果如下:

    Fayson
  • privoxy自动请求转发到多个网络

    主配置文件 /etc/privoxy/config 增加一条配置: actionsfile usr.proxy 例如:

    旺财的城堡
  • Python:Dom生成XML文件(写X

    在python中解析XML文件也有Dom和Sax两种方式,这里先介绍如何是使用Dom解析XML,这一篇文章是Dom生成XML文件,下一篇文章再继续介绍Dom解析...

    py3study
  • 教程 | 如何使用JavaScript实现GPU加速神经网络

    选自Towards Data Science 作者:Sebastian Kwiatkowski 机器之心编译 参与:Nurhachu Null、路雪 本文作者 ...

    机器之心
  • SDS趋势之一:软件定义存储目前趋势

    序言:软件定义世界,数据驱动未来。正如Netscape创始人、硅谷著名投资人马克•安德森(Marc Andreessen)在《软件正在吞噬整个世界》所述,从生活...

    DevinGeng

扫码关注云+社区

领取腾讯云代金券