javascript模块化【RequireJS 】

巴斯光年

一、模块化的由来

在没有模块化思想之前,我们总是将大量的逻辑代码写在一起,这样的代码杂乱无章,没有条理性,不便于维护,不利用复用。并且很多代码重复,逻辑重复。甚至造成全局变量污染,也不方便保护私有数据。
为了解决上面的问题,模块化的编程思想应运而生。
模块化的基本思想就是:==闭包自调用函数==
对闭包了解不够的同学,请先查看《 JS闭包全面解析》一文。


二、模块规范

想要了解模块化就要先知道JS中3个模块规范。
JS中的模块规范(CommonJS,AMD,CMD),如果你听过模块化这个东西,那么你就应该听过或CommonJS、AMD、CMD这些规范,我也听过,但之前也真的是听听而已。直到最近项目中使用到了才有了一定的理解, 现在就看看吧,这些规范到底是啥东西,怎么用的。(本文对CommonJS及CMD做一个大概的说明,对AMD中RequireJS做较为全面的讲解)

1.CommonJS

CommonJS API定义很多普通应用程序(主要指非浏览器的应用)使用的API,也是Node中使用的模块化解决方案。

2.CMD

CMD:common module define,CMD其实是阿里一位大神编写的seajs中提出的模块化解决方案。
==其实CMD可以看成是CommonJS的前端实现==。
在前几年非常火,不过随着前端框架的崛起,vue、react、angular都集成了各自的模块化。并且es6、webpack等都提供了模块化的解决方案。使得seajs出场机会越来越少,作者也停止了更新。seajs也渐渐退出了历史的舞台。

3.AMD

AMD:async module define:异步模块定义。
AMD其实就是requireJS实现的模块化解决方案,下面我会着重的介绍AMD规范中的requireJS。
链接>>>RequireJS中文网


三、RequireJS

1.基本用法

例如在一个电商网站中,购物车和商品的逻辑会在需要场景应用,所以我们就可以将两者抽出作为模块开发,使用的时候直接引用,直接上代码。
首先我们先创建一个cart.js文件

define([],function(){
    console.log('cart模块');
})

然后创建一个product.js文件

define([],function(){
    console.log('product模块');
})

然后在首页index.html中调用模块

<!-- 首先在官网下载requirejs源文件,通过script标签导入 -->
<script src="../js/require.js"></script>
<script>
// 将之前定义好的cart和product模块导入首页模块中
require(["cart","product"],function(){
    console.log('这里是首页模块');
})
</script>
2.动态加载模块&模块返回值

还是上面的栗子,再加一些代码
cart.js

define([],function(){
    // 将函数作为模块的返回值
    return function(){
        console.log('购物车模块初始化');
    }
})

product.js

define([],function(){
    // 模块不仅可以返回函数,也可返回对象
    return {
        init() {
            console.log('商品模块初始化');
        }
    }
})

index.html

// 这里我们给require的回调函数添加形参,接收前面对应模块的返回值,要与数组顺序一致
require(['cart','product'],function(cart,product) {
    // 这里我们不想一进入就加载cart和product模块,而是等点击按钮再去加载模块
    // 这里也可以理解成按需加载模块
    var btn = document.getElementById('btn1');
    btn.onclick(function(){
        // 在按钮的点击事件中去加载模块
        cart();
        product.init();
    })
})

==注意==:

  • 回调函数中的形参一定要与,数组中导入模块的顺序一致
  • 没有返回值的模块尽量放到最后导入(数组最后),当然es6中可以写成param1,,,param2
  • 大部分模块都是按需加载的
3.入口文件

一般将模块的入口也定义在一个单独的js文件中,如main.js。
这样引用入口文件处就可以简写为:

<script data-main="./main" src="../js/require.js"></script>
4.入口文件配置---path

通过在入口文件中的一些配置可以让我们在使用模块时更加便捷。
如我们想在模块中使用jQuery,我们要这样写

define([jquery-3.3.1],function($){
})

这里可能有人不理解为什么每个模块引用jq,都要引用jq模块。因为:

  • 防止全局变量污染(zepto:$)
  • 使用amd方式在每个模块导入一下,$就是一个局部变量

设想一下,如果有几十个模块,每个模块都这样引入jq,如果jq的文件目录发生改变亦或是jq版本改变,那将会是一个非常大的工程。
这时我们就可以利用path来解决这个问题
main.js

require.config({
    path:{
        jquery:"lib/jquery-3.3.1", // 文件
        bootstrap:"assets/bootstrap/js/bootstrap.min", // 文件
        service:"../service" // 文件夹
    }
})

当然,不是所有的模块都需要配置在这里的,一般来说常用的模块、文件夹才需要配置。
这样当需要用到jq的时候,只需要导入入口文件中配置好的jquery即可,后续的任何修改,每个引用的模块都会同步。

// 指定文件的可以直接导入文件,指定文件夹的可以通过配置的文件夹目录找到对应文件
define(["jquery","service/xxxxService","bootstrap"],function($,xxxxService){
})

为什么jq可以像我们编写的其他模块一样被导入使用,是因为jq中已经注册了amd模块。虽说jq的设计并不是像我们写的amd模块那样,但是在内部已经做了兼容,许多第三方库都是这么兼容amd的,通过源码可以看到:

define([],function() {
    // 定义一个模块,将jq对象返回,这样我们在导入模块后拿到的参数$就是这个jq对象
    return jQuery;
})
5.入口文件配置---baseUrl

一个模块化的项目目录都会比较复杂,如创建两个js文件,cart.js、cartDetail.js。存放的目录为~/js/cart/中,那么想要在cart模块中倒入cartDetail模块就要这样去写:
cart.js

define(["js/cart/cartDetail"],function(cartDetail){
})

虽然两个模块同处一个文件夹中,但是模块的导入是根据入口文件所在的路径去查找的,如果入口文件放在根路径下,那么导入模块的路径也是根路径。
利用baseUrl简化路径查找:
main.js

require.config({
    baseUrl:"js/"
})

改造后我们再导入模块可以这样去写:
cart.js

define(["cart/cartDetail"],function(cartDetail){
})

==注意==:path里面的配置也是相对于baseUrl的

6.requirejs中的循环依赖

场景:a模块依赖b模块,但是b模块也需要a模块,如果按常理去写会造成循环依赖,导致报错。

  • 这时我们要在b模块中添加require模块的依赖,然后再添加a的依赖
  • ==但是一定不要去通过回调函数形参的形式获取返回值。==
  • 在需要执行a模块代码的时候通过require调用。
define(["require","a"],function(require){
    require("a")();
})

另外还需要==注意==的一点是:一个模块被不同模块引用若干次,但是他们获取到的都是该模块同一个引用(闭包数据共享),模块代码不会重新执行,节省性能。

7.检测第三方库是否支持AMD规范

这个方式也是jQuery中使用的。

  if ( typeof define === "function" && define.amd ) {
        define([], function() {
            return jQuery;
        } );
    }

四、总结

学习模块化,重要的不是学习具体的实现,而是学习一种思想。只有真正的领悟了模块化的思想才能把模块化更好的应用到开发中,并且在使用其他框架时才能更加得心应手。

阅读 1.1k
164 声望
9 粉丝
0 条评论
你知道吗?

164 声望
9 粉丝
宣传栏