seajs.config
用来对 Sea.js 进行配置。
seajs.config({
// 设置路径,方便跨目录调用
paths: {
"arale": "https://a.alipayobjects.com/arale",
"jquery": "https://a.alipayobjects.com/jquery"
},
// 设置别名,方便调用
alias: {
"class": "arale/class/1.0.0/class",
"jquery": "jquery/jquery/1.10.1/jquery"
}
});
更多配置项请参考:
配置
可以对 Sea.js 进行配置,让模块编写、开发调试更方便。
seajs.config seajs.config(options)
用来进行配置的方法。
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"
},
// 变量配置
vars: {
"locale": "zh-cn"
},
// 映射配置
map: [
["http://example.com/js/app/", "http://localhost/js/app/"]
],
// 预加载项
preload: [
Function.prototype.bind ? "" : "es5-safe",
this.JSON ? "" : "json"
],
// 调试模式
debug: true,
// Sea.js 的基础路径
base: "http://example.com/path/to/base/",
// 文件编码
charset: "utf-8"
});
支持以下配置选项:
alias Object
当模块标识很长时,可以使用 alias
来简化。
seajs.config({
alias: {
"jquery": "jquery/jquery/1.10.1/jquery",
"app/biz": "http://path/to/app/biz.js",
}
});
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
});
使用 alias
,可以让文件的真实路径与调用标识分开,有利于统一维护。
paths Object
当目录比较深,或需要跨目录调用模块时,可以使用 paths
来简化书写。
seajs.config({
paths: {
"gallery": "https://a.alipayobjects.com/gallery",
"app": "path/to/app",
}
});
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
});
paths
配置可以结合 alias
配置一起使用,让模块引用非常方便。
vars Object
有些场景下,模块路径在运行时才能确定,这时可以使用 vars
变量来配置。
seajs.config({
vars: {
"locale": "zh-cn"
}
});
define(function(require, exports, module) {
var lang = require("./i18n/{locale}.js");
//=> 加载的是 path/to/i18n/zh-cn.js
});
vars
配置的是模块标识中的变量值,在模块标识中用 {key}
来表示变量。
map Array
该配置可对模块路径进行映射修改,可用于路径转换、在线调试等。
seajs.config({
map: [
[ ".js", "-debug.js" ]
]
});
define(function(require, exports, module) {
var a = require("./a");
//=> 加载的是 path/to/a-debug.js
});
Sea.js 的调试接口
使用 Sea.js,无论开发时还是上线后,调试都很方便。下面一一阐述。
seajs.cache Object
通过 seajs.cache,可以查阅当前模块系统中的所有模块信息。
比如,打开 seajs.org,然后在 WebKit Developer Tools 的 Console 面板中输入 seajs.cache,可以看到:
Object
> http://seajs.org/docs/assets/main.js: x
> https://a.alipayobjects.com/jquery/jquery/1.10.1/jquery.js: x
> __proto__: Object
这些就是文档首页用到的模块。展开某一项可以看到模块的具体信息,含义可参考:CMD 模块定义规范 中的 module 小节。
seajs.resolve Function
类似 require.resolve,会利用模块系统的内部机制对传入的字符串参数进行路径解析。
seajs.resolve("jquery");
// => http://path/to/jquery.js
seajs.resolve("./a", "http://example.com/to/b.js");
// => http://example.com/to/a.js
seajs.resolve
方法不光可以用来调试路径解析是否正确,还可以用在插件开发环境中。
seajs.require Function
全局的 require
方法,可用来直接获取模块接口,比如
seajs.use(["a", "b"], function() {
var a = seajs.require("a")
var b = seajs.require("b")
// do something...
})
seajs.data Object
通过 seajs.data,可以查看 seajs 所有配置以及一些内部变量的值,可用于插件开发。当加载遇到问题时,也可用于调试。
preload Array
使用 preload
配置项,可以在普通模块加载前,提前加载并初始化好指定模块。
// 在老浏览器中,提前加载好 ES5 和 json 模块
seajs.config({
preload: [
Function.prototype.bind ? "" : "es5-safe",
this.JSON ? "" : "json"
]
});
preload
中的空字符串会被忽略掉。
注意:preload
中的配置,需要等到 use
时才加载。比如:
seajs.config({
preload: "a"
});
// 在加载 b 之前,会确保模块 a 已经加载并执行好
seajs.use("./b");
preload
配置不能放在模块文件里面:
seajs.config({
preload: "a"
});
define(function(require, exports) {
// 此处执行时,不能保证模块 a 已经加载并执行好
});
debug Boolean
值为 true
时,加载器不会删除动态插入的 script 标签。插件也可以根据 debug 配置,来决策 log 等信息的输出。
base String
Sea.js 在解析顶级标识时,会相对 base
路径来解析。详情请参阅 模块标识
在 seajs@2.3.0
之前,Sea.js 会根据 sea.js 的路径去猜测 base
路径,一般为路径上含有 seajs 字符串的上一级路径。在 seajs@2.3.0
后,去掉了这个模糊的猜测。我们推荐始终手动设置一个准确的 base
路径。
charset String | Function
获取模块文件时,<script>
或 <link>
标签的 charset
属性。 默认是 utf-8
charset
还可以是一个函数:
seajs.config({
charset: function(url) {
// xxx 目录下的文件用 gbk 编码加载
if (url.indexOf("http://example.com/js/xxx") === 0) {
return "gbk";
}
// 其他文件用 utf-8 编码
return "utf-8";
}
});
提示
多次配置自动合并
seajs.config
可以多次运行,每次运行时,会对配置项进行合并操作:
seajs.config({
alias: {
"jquery": "path/to/jquery.js",
"a": "path/to/a.js"
},
preload: ["seajs-text"]
});
seajs.config({
alias: {
"underscore": "path/to/underscore.js",
"a": "path/to/biz/a.js"
},
preload: ["seajs-combo"]
});
上面两处 config
运行的结果是:
alias = {
"jquery": "path/to/jquery.js",
"underscore": "path/to/underscore.js",
"a": "path/to/biz/a.js"
};
preload = ["seajs-text", "seajs-combo"];
即:config
会自动合并不存在的项,对存在的项则进行覆盖。
插件的配置
插件可以给 Sea.js
添加配置项,请查看具体插件了解相关配置。
配置文件
配置可以直接写在 html 页面上,也可以独立出来成为一个文件。
config.js
seajs.config({
...
});
独立成一个文件时,一般通过 script 标签在页面中同步引入。
常用的配置项是 alias
、paths
、base
,其他配置项有需要时,来查查文档就会用了。
seajs.use
用来在页面中加载一个或多个模块。
// 加载一个模块
seajs.use("./a");
// 加载一个模块,在加载完成时,执行回调
seajs.use("./a", function(a) {
a.doSomething();
});
// 加载多个模块,在加载完成时,执行回调
seajs.use(["./a", "./b"], function(a, b) {
a.doSomething();
b.doSomething();
});
更多用法请参考:
模块的加载启动
Sea.js 是一个模块加载器,模块加载器需要实现两个基本功能:
- 实现模块定义规范,这是模块系统的基础。
- 模块系统的启动与运行。
模块定义规范的实现
这就是 define
,require
,exports
,module
的实现。具体实现细节,有兴趣的可以看 Sea.js 的源码:seajs/src。可以按照 Gruntfile.js 中声明的合并顺序阅读,核心是 module.js
文件。
define
等方法的具体使用,请阅读:CMD 模块定义规范
模块系统的启动
有了 define
等模块定义规范的实现,我们可以开发出很多模块。但光有一堆模块不管用,我们还得让它们能跑起来。
首先就是启动问题。比如在 Node 中,启动很简单:
$ node main.js
这就是启动。
再举一个例子,操作系统的启动:大家都知道的,按一下开机键就好。
在 Sea.js 里,要启动模块系统很简单:
<script src="path/to/sea.js"></script>
<script>
seajs.use("./main");
</script>
seajs.use Function
用来在页面中加载模块。
seajs.use seajs.use(id, callback?)
通过 use
方法,可以在页面中加载任意模块:
// 加载模块 main,并在加载完成时,执行指定回调
seajs.use("./main", function(main) {
main.init();
});
use
方法还可以一次加载多个模块:
// 并发加载模块 a 和模块 b,并在都加载完成时,执行指定回调
seajs.use(["./a", "./b"], function(a, b) {
a.init();
b.init();
});
callback
参数可选,省略时,表示无需回调。
与 DOM ready 的关系
注意:seajs.use
与 DOM ready
事件没有任何关系。如果某些操作要确保在 DOM ready
后执行,需要使用jquery
等类库来保证,比如:
seajs.use(["jquery", "./main"], function($, main) {
$(document).ready(function() {
main.init();
});
});
sea.js 的引入
在调用 seajs.use
之前,需要先引入 sea.js
文件,推荐直接使用 script
标签同步引入:
<script src="path/to/sea.js"></script>
为了满足某些场景下的性能优化需求,也可以将 sea.js
的源码内嵌:
<script>
// sea.js 的源码
</script>
注意:代码内嵌时,需要通过 seajs.config
手动配置 base
路径。
最佳实践
seajs.use
理论上只用于加载启动,不应该出现在define
中的模块代码里。在模块代码里需要异步加载其他模块时,推荐使用require.async
方法。- 引入
sea.js
时,可以把sea.js
与其他文件打包在一起,可提前合并好,或利用 combo 服务动态合并。无论哪一种方式,为了让sea.js
内部能快速获取到自身路径,推荐手动加上id
属性:
<script src="path/to/sea.js" id="seajsnode"></script>
加上 seajsnode
值,可以让 sea.js
直接获取到自身路径,而不需要通过其他机制去自动获取。这对性能和稳定性会有一定提升,推荐默认都加上。
小结
seajs.use
是模块加载器必备的一个接口。在 seajs
上,还有用于配置的 config
方法、方便调试的cache
等接口,这些会在接下来的文档中详细阐述。
define
用来定义模块。Sea.js 推崇一个模块一个文件,遵循统一的写法:
define(function(require, exports, module) {
// 模块代码
});
require.async
用来在模块内部异步加载一个或多个模块。
define(function(require) {
// 异步加载一个模块,在加载完成时,执行回调
require.async("./b", function(b) {
b.doSomething();
});
// 异步加载多个模块,在加载完成时,执行回调
require.async(["./c", "./d"], function(c, d) {
c.doSomething();
d.doSomething();
});
});
exports
用来在模块内部对外提供接口。
define(function(require, exports) {
// 对外提供 foo 属性
exports.foo = "bar";
// 对外提供 doSomething 方法
exports.doSomething = function() {};
});
module.exports
与 exports
类似,用来在模块内部对外提供接口。
define(function(require, exports, module) {
// 对外提供接口
module.exports = {
name: "a",
doSomething: function() {};
};
});
CMD 模块定义规范
在 Sea.js 中,所有 JavaScript 模块都遵循 CMD(Common Module Definition) 模块定义规范。该规范明确了模块的基本书写格式和基本交互规则。
在 CMD 规范中,一个模块就是一个文件。代码的书写格式如下:
define(factory);
define Function
define
是一个全局函数,用来定义模块。
define define(factory)
define
接受 factory
参数,factory
可以是一个函数,也可以是一个对象或字符串。
factory
为对象、字符串时,表示模块的接口就是该对象、字符串。比如可以如下定义一个 JSON 数据模块:
define({ "foo": "bar" });
也可以通过字符串定义模板模块:
define("I am a template. My name is {{name}}.");
factory
为函数时,表示是模块的构造方法。执行该构造方法,可以得到模块向外提供的接口。factory
方法在执行时,默认会传入三个参数:require
、exports
和 module
:
define(function(require, exports, module) {
// 模块代码
});
define define(id?, deps?, factory)
define
也可以接受两个以上参数。字符串 id
表示模块标识,数组 deps
是模块依赖。比如:
define("hello", ["jquery"], function(require, exports, module) {
// 模块代码
});
id
和 deps
参数可以省略。省略时,可以通过构建工具自动生成。
注意:带 id
和 deps
参数的 define
用法不属于 CMD 规范,而属于 Modules/Transport 规范。
define.cmd Object
一个空对象,可用来判定当前页面是否有 CMD 模块加载器:
if (typeof define === "function" && define.cmd) {
// 有 Sea.js 等 CMD 模块加载器存在
}
require Function
require
是 factory
函数的第一个参数。
require require(id)
require
是一个方法,接受 模块标识 作为唯一参数,用来获取其他模块提供的接口。
define(function(require, exports) {
// 获取模块 a 的接口
var a = require("./a");
// 调用模块 a 的方法
a.doSomething();
});
注意:在开发时,require
的书写需要遵循一些 简单约定。
require.async require.async(id, callback?)
require.async
方法用来在模块内部异步加载模块,并在加载完成后执行指定回调。callback
参数可选。
define(function(require, exports, module) {
// 异步加载一个模块,在加载完成时,执行回调
require.async("./b", function(b) {
b.doSomething();
});
// 异步加载多个模块,在加载完成时,执行回调
require.async(["./c", "./d"], function(c, d) {
c.doSomething();
d.doSomething();
});
});
注意:require
是同步往下执行,require.async
则是异步回调执行。require.async
一般用来加载可延迟异步加载的模块。
require.resolve require.resolve(id)
使用模块系统内部的路径解析机制来解析并返回模块路径。该函数不会加载模块,只返回解析后的绝对路径。
define(function(require, exports) {
console.log(require.resolve("./b"));
// ==> http://example.com/path/to/b.js
});
这可以用来获取模块路径,一般用在插件环境或需动态拼接模块路径的场景下。
exports Object
exports
是一个对象,用来向外提供模块接口。
define(function(require, exports) {
// 对外提供 foo 属性
exports.foo = "bar";
// 对外提供 doSomething 方法
exports.doSomething = function() {};
});
除了给 exports
对象增加成员,还可以使用 return
直接向外提供接口。
define(function(require) {
// 通过 return 直接提供接口
return {
foo: "bar",
doSomething: function() {}
};
});
如果 return
语句是模块中的唯一代码,还可简化为:
define({
foo: "bar",
doSomething: function() {}
});
上面这种格式特别适合定义 JSONP 模块。
特别注意:下面这种写法是错误的!
define(function(require, exports) {
// 错误用法!!!
exports = {
foo: "bar",
doSomething: function() {}
};
});
正确的写法是用 return
或者给 module.exports
赋值:
define(function(require, exports, module) {
// 正确写法
module.exports = {
foo: "bar",
doSomething: function() {}
};
});
提示:exports
仅仅是 module.exports
的一个引用。在 factory
内部给 exports
重新赋值时,并不会改变 module.exports
的值。因此给 exports
赋值是无效的,不能用来更改模块接口。
module Object
module
是一个对象,上面存储了与当前模块相关联的一些属性和方法。
module.id String
模块的唯一标识。
define("id", [], function(require, exports, module) {
// 模块代码
});
上面代码中,define
的第一个参数就是模块标识。
module.uri String
根据模块系统的路径解析规则得到的模块绝对路径。
define(function(require, exports, module) {
console.log(module.uri);
// ==> http://example.com/path/to/this/file.js
});
一般情况下(没有在 define
中手写 id
参数时),module.id
的值就是 module.uri
,两者完全相同。
module.dependencies Array
dependencies
是一个数组,表示当前模块的依赖。
module.exports Object
当前模块对外提供的接口。
传给 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
});
小结
这就是 CMD 模块定义规范的所有内容。经常使用的 API 只有 define
, require
, require.async
, exports
,module.exports
这五个。其他 API 有个印象就好,在需要时再来查文档,不用刻意去记。
与 RequireJS 的 AMD 规范相比,CMD 规范尽量保持简单,并与 CommonJS 和 Node.js 的 Modules 规范保持了很大的兼容性。通过 CMD 规范书写的模块,可以很容易在 Node.js 中运行,后续会介绍。
require
, exports
和 module
三个参数可酌情省略,具体用法如下。
require
require
用来获取指定模块的接口。
define(function(require) {
// 获取模块 a 的接口
var a = require("./a");
// 调用模块 a 的方法
a.doSomething();
});
注意,require
只接受字符串直接量作为参数,详细约定请阅读:
require 书写约定
使用 Sea.js 书写模块代码时,需要遵循一些简单规则。
只是书写和调试时的规范!!!构建后的代码完全不需要遵循下面的约定!!!!!!
1. 正确拼写
模块 factory 构造方法的第一个参数 必须 命名为 require
。
// 错误!
define(function(req) {
// ...
});
// 正确!
define(function(require) {
// ...
});
2. 不要修改
不要重命名 require
函数,或在任何作用域中给 require
重新赋值。
// 错误 - 重命名 "require"!
var req = require, mod = req("./mod");
// 错误 - 重定义 "require"!
require = function() {};
// 错误 - 重定义 "require" 为函数参数!
function F(require) {}
// 错误 - 在内嵌作用域内重定义了 "require"!
function F() {
var require = function() {};
}
3. 使用直接量
require
的参数值 必须 是字符串直接量。
// 错误!
require(myModule);
// 错误!
require("my-" + "module");
// 错误!
require("MY-MODULE".toLowerCase());
// 正确!
require("my-module");
在书写模块代码时,必须遵循这些规则。其实只要把 require
看做是语法关键字 就好啦。
关于动态依赖
有时会希望可以使用 require
来进行条件加载:
if (todayIsWeekend)
require("play");
else
require("work");
但请牢记,从静态分析的角度来看,这个模块同时依赖 play 和 work 两个模块,加载器会把这两个模块文件都下载下来。 这种情况下,推荐使用 require.async
来进行条件加载。
Why?
这些约定初看起来会有些小不爽,其实也的确可以通过每次都编译的方式来去掉这些限制。但编译的方式,会给开发调试带来麻烦,代码的实现复杂度也会增加。Sea.js 的核心设计原则是保持简单,遵循 New Jersey Approach:
简单性:设计必须简单,这既是对实现的要求,也是对接口的要求。实现的简单要比接口的简单更加重要。简单是设计中需要第一重视的因素。
因为简单,所以可靠!