hello,SeaJS
为了理解KISS理念(Keep It Simple, Stupid),专门学习并应用SeaJS。
好处
- 令JavaScript开发模块化并可以轻松愉悦进行加载
- 可以与jQuery这类框架完美集成
- 可以提高JavaScript代码的可读性和清晰度
- 解决目前JavaScript编程中普遍存在的依赖关系混乱和代码纠缠等问题,方便代码的编写和维护
- 本身仅有个位数的API,因此学习起来毫无压力
- 遵循MIT 协议,无论个人还是公司,都可以免费自由使用
API文档
官方提供的demo:example
官方文档:doc
经常使用的 API 只有 define
, require
, require.async
, exports
, module.exports
这五个。其他 API 有个印象就好,在需要时再来查文档,不用刻意去记。
基础概念
一切都是模块(遵循CMD:Common Module Definition规范)
一个模块一个文件
且一般开头为:
define(factory);
所以在SeaJS中,define是用来定义一个模块的。
而factory,可以是函数、字符串、json。
factory
为函数时,表示是模块的构造方法。执行该构造方法,可以得到模块向外提供的接口。factory
方法在执行时,默认会传入三个参数:require
、exports
和module
:define(function(require, exports, module) { // 模块代码 });
重点来了
我如果不想按照CMD规范来写可以吗?答案也是可以的,SeaJs也允许我们使用Modules/Transport规范来书写代码,具体的表现形式如下:
define(id?, deps?, factory)
字符串id
表示模块标识,数组deps
是模块依赖。define('hello', ['jquery'], function(require, exports, module) { // 模块代码 });
require
- 第一个参数
require
是一个方法,接受 模块标识 作为唯一参数,用来获取其他模块提供的接口。
define(function(require, exports) {
// 获取模块 a 的接口
var a = require('./a');
// 调用模块 a 的方法
a.doSomething();
});
在SeaJS中,我们需要把require看作一个关键词,具体书写规范可以看[require](https://github.com/seajs/seajs/issues/259)
require
是同步往下执行,require.async
则是异步回调执行。require.async
一般用来加载可延迟异步加载的模块。define(function(require, exports, module) { // 异步加载一个模块,在加载完成时,执行回调 require.async('./b', function(b) { b.doSomething(); }); // 异步加载多个模块,在加载完成时,执行回调 require.async(['./c', './d'], function(c, d) { c.doSomething(); d.doSomething(); }); });
require.resolve(id)
该函数不会加载模块,只返回解析后的绝对路径。define(function(require, exports) { console.log(require.resolve('./b')); // ==> http://example.com/path/to/b.js });
exports
exports
是一个对象,用来向外提供模块接口。define(function(require, exports) { // 对外提供 foo 属性 exports.foo = 'bar'; // 对外提供 doSomething 方法 exports.doSomething = function() {}; });
`exports` 仅仅是 `module.exports` 的一个引用。在 `factory` 内部给 `exports` 重新赋值时,并不会改变 `module.exports` 的值。因此给 `exports` 赋值是无效的,不能用来更改模块接口。
module
module
是一个对象,上面存储了与当前模块相关联的一些属性和方法。module.id & module.url
module.id: 模块的唯一标识。
module.uri: 根据模块系统的路径解析规则得到的模块绝对路径。
一般情况下(没有在
define
中手写id
参数时),module.id
的值就是module.uri
,两者完全相同。module.dependencies: 表示当前模块的依赖
module.exports: 当前模块对外提供的接口。
传给
factory
构造方法的exports
参数是module.exports
对象的一个引用。只通过exports
参数来提供接口,有时无法满足开发者的所有需求。 比如当模块的接口是某个类的实例时,需要通过module.exports
来实现:define(function(require, exports, module) { // exports 是 module.exports 的一个引用 console.log(module.exports === exports); // true // 重新给 module.exports 赋值 module.exports = new SomeClass(); // exports 不再等于 module.exports console.log(module.exports === exports); // false });
注意:对
module.exports
的赋值需要同步执行,不能放在回调函数里。下面这样是不行的:// x.js define(function(require, exports, module) { // 错误用法 setTimeout(function() { module.exports = { a: "hello" }; }, 0); });
在 y.js 里有调用到上面的 x.js:
// y.js define(function(require, exports, module) { var x = require('./x'); // 无法立刻得到模块 x 的属性 a console.log(x.a); // undefined });
模块的加载与启动
启动
<script src="path/to/sea.js">script> <script> seajs.use('./main'); </script>
或者这样写:
// 加载模块 main,并在加载完成时,执行指定回调
seajs.use('./main', function(main) {
main.init();
});
`use` 方法还可以一次加载多个模块:
// 并发加载模块 a 和模块 b,并在都加载完成时,执行指定回调
seajs.use(['./a', './b'], function(a, b) {
a.init();
b.init();
});
seajs.use
与DOM ready
事件没有任何关系。如果某些操作要确保在DOM ready
后执行,需要使用jquery
等类库来保证,比如:seajs.use(['jquery', './main'], function($, main) { $(document).ready(function() { main.init(); }); });
配置
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',
'app/biz': 'http://path/to/app/biz.js',
},
// 路径配置
paths: {
'gallery': 'https://a.alipayobjects.com/gallery',
'app': 'path/to/app',
},
// 变量配置
vars: {
'locale': 'zh-cn'
},
// 映射配置
map: [
['http://example.com/js/app/', 'http://localhost/js/app/'],
['.js', '-debug.js']
],
// 预加载项
preload: [
Function.prototype.bind ? '' : 'es5-safe',
this.JSON ? '' : 'json'
],
// 调试模式
debug: true,
// Sea.js 的基础路径
base: 'http://example.com/path/to/base/',
// 文件编码
charset: 'utf-8'
});
上述代码中对别名进行配之后,再次引入jquery,就可以这样写:
define(function(require, exports, module) {
var $ = require('jquery');
//=> 加载的是 http://path/to/base/jquery/jquery/1.10.1/jquery.js
var biz = require('app/biz');
//=> 加载的是 http://path/to/app/biz.js
});
上述代码中对路径进行配置后,再次引用可以这样写:
define(function(require, exports, module) {
var underscore = require('gallery/underscore');
//=> 加载的是 https://a.alipayobjects.com/gallery/underscore.js
var biz = require('app/biz');
//=> 加载的是 path/to/app/biz.js
});
vars
变量配置,就是说模块路径在运行时才能确定的情况下去使用。
define(function(require, exports, module) {
var lang = require('./i18n/{locale}.js');
//=> 加载的是 path/to/i18n/zh-cn.js
});
map
配置,可对模块路径进行映射修改,可用于路径转换、在线调试等。
define(function(require, exports, module) {
var a = require('./a');
//=> 加载的是 path/to/a-debug.js
});
preload
可以在普通模块加载前,提前加载并初始化好指定模块。 注意 :preload
中的配置,需要等到 use
时才加载。比如:
seajs.config({
preload: 'a'
});
// 在加载 b 之前,会确保模块 a 已经加载并执行好
seajs.use('./b');
preload
配置不能放在模块文件里面:
debug
为true时,加载器不会删除动态插入的 script 标签。插件也可以根据 debug 配置,来决策 log 等信息的输出。
base
,Sea.js 在解析顶级标识时,会相对 base
路径来解析
seajs.config
可以多次运行,每次运行时,会对配置项进行合并操作
构建工具
SeaJS的官方文档推荐我们使用spm进行构建,构建完了再将 dist 目录下的文件部署到 sea-modules
目录中,如果你要自定义构建的话再去用grunt云云。
作为一个gulp的粉丝,怎么能够容忍grunt在这里撒野,所以我自己用gulp写了SeaJS的构建脚本。
具体代码请见我的一个git项目starComment