Fork me on GitHub

你必需知道的模块加载器之一CMD

说到CMD(通用模块加载器),那么不得不使用的它的库seaJs。最近工作中要求使用模块加载器进行项目中,所以对于初学者肯定会遇到很多问题,在这里总结一下,希望能对自己或者他人都是一份收获。


模块加载器使用的好处

  1. 模块化思想是可以让开发更高效
  2. 实现模块化需要解决一个任务,这个任务就是依赖关系
  3. 浏览器端js是天然不能实现模块
  4. 有一些库弥补了浏览器端JS的一些缺陷,实现了模块化并解决了依赖关系
    以seajs为例
    在前端开发领域,一个模块,可以是JS 模块,也可以是 CSS 模块,或是 Template 等模块。 而 Sea.js 则专注于 JS 文件模块:
    1.模块是一段 JavaScript 代码,具有统一的 基本书写格式
    2.模块之间通过基本 交互规则 ,能彼此引用,协同工作
  • SeaJS 是一个适用于浏览器环境的 JavaScript 模块加载器
    一个库文件,类似于 jQuery
    使用这个库提供的规范的模块化的方式来编写 JavaScript 代码
    只关心 JavaScript 文件代码模块如何组织
    只关心 JavaScript 文件之间如何相互协议、引用、依赖
  • SeaJS 的作者是阿里巴巴支付宝前端架构师,花名:玉伯,玉伯也叫射雕
    1
    2
    3
    4
    5
    在页面中引入sea.js
    通过define()方法来定义模块
    通过sea.use()方法来加载、执行模块
    通过require()引入模块
    通过exports/module.exports暴露模块功能

seajs具体的使用方法(API解释)

1、seajs.use

加载模块,启动模块系统。

* 加载一个模块 seajs.use('id')
* 加载一个模块,在加载完成时,执行回调 seajs.use('id', callback)
* 加载多个模块,加载完成时,执行回调 seajs.use(['id1','id2',...],callback)
* 注意:
   · 在调用 seajs.use 之前,需要先引入 sea.js 文件
   · seajs.use 与 DOM ready 事件没有任何关系。如果某些操作要确保在 DOM ready 后执行,需要使用 jquery 等类库来保证
   · seajs.use 理论上只用于加载启动,不应该出现在 define 中的模块代码里

2、define(factory)

define 是一个全局函数,用来定义模块。
define 接受 factory 参数,factory 可以是一个函数,也可以是一个对象或字符串。
factory 为对象、字符串时,表示模块的接口就是该对象、字符串。

* factory 是一个对象时
    · define({})
* factory 是一个字符串时
    · define('hello')
* factory 是一个函数时
    · define(function(require, exports, module){})

3、require

require 用来加载一个 js 文件模块, require 用来获取指定模块的接口对象 module.exports。
require 在加载和执行的时候,js 会按照同步的方式和执行。
使用注意:
·正确拼写

    * 模块 factory 构造方法的第一个参数 必须 命名为 require
·不要修改
    * 不要重命名 require 函数,或在任何作用域中给 require 重新赋值
·使用字符串直接量
    * require 的参数值 必须 是字符串直接量

Tips: 把 require 看做是语法关键字就好啦

4、模块标识

模块标识是一个字符串,用来标识模块。

  • 模块标识可以不包含文件后缀名,比如 .js
    • seajs 推荐不加 .js 文件模块后缀
  • 模块标识可以是 相对 或 顶级 标识
  • 相对标识
    相对标识以 . 开头,永远相对于当前模块所处的路径来解析。
  • 顶级标识
    顶级标识不以 . 或 / 开始,会相对模块系统的基础路径(base路径,默认是 sea.js 文件所属的路径)。 可以手动配置 base 路径。

    1
    2
    3
    seajs.config({
    base: './js'
    })
  • 普通路径
    除了相对和顶级标识之外的标识都是普通路径。 普通路径的解析规则,会相对当前页面解析。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 假设当前页面是 http://example.com/path/to/page/index.html

    // 绝对路径是普通路径:
    require.resolve('http://cdn.com/js/a');
    // => http://cdn.com/js/a.js

    // 根路径是普通路径:
    require.resolve('/js/b');
    // => http://example.com/js/b.js

    // use 中的相对路径始终是普通路径:
    seajs.use('./c');
    // => 加载的是 http://example.com/path/to/page/c.js

    seajs.use('../d');
    // => 加载的是 http://example.com/path/to/d.js

Tips:

  • 顶级标识始终相对 base 基础路径解析。
    • 如果不设置,base 路径默认就是 sea.js 库文件所属的路径
    • 可以通过 seajs.config({ base: ‘基础路径’ }) 来配置基础路径
  • 绝对路径和根路径始终相对当前页面解析。
  • 相对标识永远相对于当前文件
  • seajs.use 中的相对路径始终相对当前页面来解析。

    5、 module

module 是一个对象,上面存储了与当前模块相关联的一些属性和方法。

  • module.id
    • 模块的唯一标识,可以通过 define 方法的第一个参数来指定,默认为该模块文件的绝对路径
  • module.uri
    • 模块的绝对路径
  • module.dependencies
    • dependencies 是一个数组,表示当前模块的依赖
  • module.exports
    • 当前模块对外提供的接口对象
    • 相当于每个模块内部最终都执行了这么一句话:return module.exports
    • 模块与模块之间的通信接口

6、 exports

exports 仅仅是 module.exports 的一个引用。
也就是说修改了 exports 就相当于修改了 module.exports。

但是一旦在 factory 内部给 exports 重新赋值,并不会改变 module.exports 的值。
因此给 exports 赋值是无效的。

7、 exports 和 module.exports 的区别

  • 每个模块内部对外到处的接口对象始终都是 module.exports
  • 可以通过修改 module.exports 或给它赋值改变模块接口对象
  • exportsmodule.exports 的一个引用,就好比在每一个模块定义最开始的地方写了这么一句代码:var exports = module.exports

关于这俩哥们儿的区别请分析一下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var module = {
exports: {}
}

function changeExports (exports, module) {
// var exports = module.exports
exports.foo = 'bar'

// 这里赋值拿不到,不要使用使用
// exports = function () {}
return module.exports
}

changeExports(module.exports, module)

那为啥要有 exports

为了开发体验,API更友好,使用 exports 的时候,可以少写一个点儿。

如果你实在分不清楚 exportsmodule.exports 之间的区别,就只记得 module.exports 就可以了。

8、如何将一个普通的模块文件改造为兼容 CMD 规范的模块

1
2
3
4
5
6
if (typeof define === "function" && define.cmd) {
// 有 Sea.js 等 CMD 模块加载器存在
define(function (require, exports, module) {
// 使用 module.exports 向外暴露接口对象
})
}

9、高级配置 seajs.config(options)

可以对 Sea.js 进行配置,让模块编写、开发调试更方便。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
seajs.config({

// 别名配置
alias: {
'es5-safe': 'gallery/es5-safe/0.9.3/es5-safe',
'json': 'gallery/json/1.0.2/json',
'jquery': 'jquery/jquery/1.10.1/jquery'
},

// 路径配置
paths: {
'gallery': 'https://a.alipayobjects.com/gallery'
},

// Sea.js 的基础路径
base: 'http://example.com/path/to/base/',
});

那么现在了解了这么多,就上手做一下练习吧。
现在需求是这样的,利用模块加载器的方式,引入jquery模块以及jquery的插件等,并利用他们的功能完成一些功能。
我的demo结构:

step1:引入seajs文件

1
2
   <!-- 模块加载器 -->
<script src="../public/libs/sea.js"></script>

step2:使用顶级标识配置路径

1
2
3
4
5
6
7
seajs.config({
base: '../public/assets',
alias: {
jquery: 'jquery',
fullpage:'jquery.fullPage.js'
}
});

step3:在main.js中定义一个模块,引入jquery或者jquery插件(我这里引入的jquery.fullpage.js插件)
下面就是main模块的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
define(function (require, exports, module) {
// 相对路径,是相对当前模块来说的
// var $ = require('../assets/jquery');

// 顶级路径
var $ = require('jquery1');

// 这里使用的相对标识
// require('../assets/jquery.fullPage');
// 或者使用顶级标识(要在sea.config中配置一下)
require('fullpage');

$('.box').fullpage();
// 在现实开发中需要将一些第三方的库“改装”成模块
// 大多数第三库是支持requirejs

// 使用jQuery 做一个小例子
$('button').click(function () {
$('.box').animate({
width: 300,
height: 300
}, 400);
})
});

注意:记得引入Jquery插件的时候要先查看Jquery插件是否支持CMD,如果不支持的话,要自己手动的在插件源代码中配置

1
2
3
4
5
6
if(typeof define === 'function' && define.cmd) {
define(function (require) {
var $ = require('jquery');
factory($);
});
}

step4:加载main模块

1
seajs.use('../public/scripts/main');

-------------本文结束感谢您的阅读-------------