seajs教程

本文介绍 require.js(AMD) 的近亲 sea.js(CMD) 的基本使用,其基于 CMD(Common Module Definition)规范。该规范明确了模块的基本书写格式和基本交互规则。

至于为什么要使用模块化管理器,这里就不再说明了,自从 require.js 出现后,前端开发再一次发生了巨大变化,让原本无模块化系统的浏览器具备了模块化的功能,页面中<script>标签的引用不再是瀑布式的扑面而来,各模块之间的依赖关系管理变得轻而易举。随后不久,支付宝的 玉伯 开发出了基于 CMD规范sea.js,其与 require.js 有着很大的不同之处,比如 require.js 对模块文件加载方式是 预先加载 ,而 sea.js 则是 按需加载。在使用方式上也存在不同。

获取方式

  • 从 github 下载 seajs

模块系统

Sea.js 是一个适用于 Web 浏览器端的模块加载器。在 Sea.js 里,一切皆是模块,所有模块协同构建成模块系统。Sea.js 首要要解决的是模块系统的基本问题:

  • 模块是什么?

  • 模块之间如何交互?

在前端开发领域,一个模块,可以是 JS 模块,也可以是 CSS 模块,或是 Template 等模块。在 Sea.js 里,我们专注于 JS 模块(其他类型的模块可以转换为 JS 模块):

  • 模块是一段 JavaScript 代码,具有统一的基本书写格式。

  • 模块之间通过基本交互规则,能彼此引用,协同工作。

把上面两点中提及的基本书写格式和基本交互规则描述清楚,就能构建出一个模块系统。对书写格式和交互规则的详细描述,就是模块定义规范(Module Definition Specification)。比如 CommonJS 社区的 Modules 1.1.1 规范,以及 NodeJSModules 规范,还有 RequireJS 提出的 AMD 规范等等。

Sea.js 遵循的是 CMD 规范,会在接下来的文档中详细阐述。

define 函数定义模块

seajs 使用 define 函数来定义一个模块。

1
2
3
4
5
6
7
8
define(function( require, exports , module ){
// 引入依赖的 foo 模块
var foo = require('/some/foo');
// do something ...
module.exports = {
// 导出一些接口
}
})

define() 函数的详细介绍请参考: seajs之define函数

对外导出接口

seajs 中,对外导出接口的方式有 3 种。

  1. 使用 exports 导出接口。
1
2
3
4
5
6
// foo.js
define(function( require, exports , module ){
exports.hello = function(){
console.log('this is foo module!')
}
})

如上所示,foo 模块向外提供了 foo.hello() 方法。

  1. 使用 module.exports 导出接口。
1
2
3
4
5
6
7
8
// bar.js
define(function( require, exports , module ){
module.exports = {
say: function(){
console.log('this is bar module!')
}
}
})

如上所示,bar 模块向外提供了 bar.say() 方法。

  1. 使用 return 导出接口。

还可以像 requirejs 那样,直接使用 return 向外提供接口。

1
2
3
4
5
6
// baz.js
define(function(){
return {
name: 'baz'
}
})

如上所示,baz 模块向外导出了一个对象,其包含了一个 name 属性。

seajs.use() 方法加载指定模块

seajs 使用 seajs.use 方法加载一个模块。

1
seajs.use('./main')

如上代码,加载 main 模块。

seajs.config 配置

seajs 也是可配置的,使用 seajs.config() 函数进行配置。

1
2
3
4
5
seajs.config({
base: '/',
alias: {}
// ...
})

具体的配置项信息,请参考:seajs之config

非CMD模块处理

没错,sea.js 同样会面对那些非 CMD 规范的模块引用问题,解决方式如下。

  1. sea.js-2.1.0 之前的版本,可以使用 shim 插件来解决非 CMD 模块引用问题。例如:jquery,其使用的是标准的 AMD 模块定义方式,所以想要使用 sea.js 加载她,那么你必须对其进行特殊处理。
1
2
3
4
5
6
7
8
9
// seajs.config.js
seajs.config({
// 开启 shim 插件
plugins: ['shim'],
alias: {
// 配置别名
jquery: '/assets/jquery/jquery-1.12.4'
}
})

配置好后,便可以在其他模块使用 require() 函数通过别名 jquery 引用她。

1
2
3
4
define(function( require, exports, module ){
var $ = require('jquery');
console.log( $ )
})
  1. sea.js-2.1.0 之后的所有版本中,seajs 不再支持 shim 插件的用法,原因是,这种用法使得各模块之间的依赖关系变得极其复杂,代码量剧增,后期维护变得困难,为此玉伯放弃并剔除了这种用法,提出,我们可以对非 CMD 模块进行手动包装一层 define 函数再进行使用。

还是以 jquery 为例:

1
2
3
4
5
6
define(function(){

// jquery code ...

return jQuery
})

如此,你也可以在其他模块直接引入她。

1
2
3
4
define(function( require, exports , module ){
var $ = require('/assets/jquery/jquery-1.12.4')
console.log( $ )
})

看到这里,相信你跟我一样,对 seajs 的热度瞬间冰封,因为其对非 CMD 模块的支持度太低? 没错,就是这么现实,随着大前端的不断发展,像 seajs 包括 requirejs 必然会被历史淘汰,耶稣都拉不回来,我说的!

最佳实践

在多页面应用中,每个页面都会对应一个主逻辑文件,那如果,我们每次都在 *.js 文件头部写配置信息的话,那就太蠢了。效率低下而不优雅。

所以,在项目根目录下,创建一个 seajs.config.js 文件,将配置信息写在这个文件中。当然,文件名你可以随便定义,最好能够区分她是谁的配置文件。

对于多页应用来说,通常我们会有如下项目结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

|-www/
| |-assets
| | |- seajs
| | |- sea.js
| | |- jquery
| | |- jquery.js
| | |- ...
| |-pages
| | |- login
| | |- login.html
| | |- login.js
| | |- demo
| | |- demo.html
| | |- demo.js
| |-utils
| | |- dom.js
| |-style
| |-seajs.config.js
| |-README.md
...

那么我们会这样使用模块化。

demo.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

<html>
<head>
<title>demo</title>
</head>
<body>

<div id="app"> this is demo page </div>

<script src="/assets/seajs/sea.js"></script> <!-- 引入sea.js -->
<script src="/seajs.config.js"></script> <!-- 引入seajs的配置文件 -->
<script>
seajs.use('../pages/demo/demo') //页面的主入口文件
</script>

</body>

</html>

seajs.config.js

1
2
3
4
5
6
7
8
9
10
11
12

seajs.config({
base: "/assets",
alias: {
jquery: "jquery/jquery",
dom: "../utils/dom"
}

// ...

})

demo.js

1
2
3
4
5
6
7
8
9
10
11

define(function( require , exports , module ){

//该 主程序文件 依赖于 2个 模块 , 分别是 jquery, dom
var $ = require('jquery');
var dom = require('dom');
//其中 dom 为 自己定义的 模块
// some code here...

})