前端框架 seajs 使用总结
CMD 模块定义规范
- seajs中,所有的javascript都遵循CMD模块定义规范。该规范明确定义了模块的定义格式和模块依赖的规则说明。
- define.cmd : 一个空对象,可以用来判断当前页面是否存在cmd模块加载器,调用方法如下:
if(typeof define.cmd ==="undefined" || define.cmd){//Seajs存在cmd模块加载器}
- 与 RequireJS 的 AMD 规范相比,CMD 规范尽量保持简单,并与 CommonJS 和 Node.js 的 Modules 规范保持了很大的兼容性
seajs的特点
- 简单友好的模块定义规范:遵循CMD规范。
- 简单直观的代码组织方式:依赖自动加载,配置简洁清晰。
seajs的兼容性
Chrome 3+ ✔ Firefox 2+ ✔ Safari 3.2+ ✔ Opera 10+ ✔ IE 5.5+ ✔
理论上适用于任何浏览器,包括 Mobile 。
seajs的配置
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
可以简化较长模块标示的书写。
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'
}
});
模块定义:
define(function(require, exports , module){
var $ = require("jquery");
//==>加载的是http://example.com/path/to/base/jquery/jquery/1.10.1/jquery.js
});
path Object
如果依赖层次较深,或者跨目录调用模块,path可以简化书写。
path 可以结合alias一起使用。
seajs.config({
// 路径配置
paths: {
'gallery': 'https://a.alipayobjects.com/gallery'
},
});
模块定义:
define(function(require,exports, module){
var json = require("gallery/jsonparser");
//==> 加载的是https://a.alipayobjects.com/gallery/jsonparser.js
});
vars Object
有些模块需要在运行时才能确定,可以使用vars指定。
vars 配置的是模块标识中的变量值,在模块标识中用 {key} 来表示变量。
seajs.config({
// 变量配置
vars: {
'local': 'zh-cn'
},
});
模块定义
define(function(require, exports, module){
var language_config = require("language/{local}.js");
console.log(language_config);//获取中文配置文件
});
map Array
路由模块路径。
seajs.config({
// 映射配置
map: [
['http://example.com/js/app/', 'http://localhost/js/app/'],
['.js','-debg.js']
],
});
define(function(require, exports, module){
var m_a = require("./a");
//==>加载的是./a-debug.js
});
preLoad Array
预加载一些公共模块或者指定模块
seajs.config({
// 预加载项
preload: [
Function.prototype.bind ? '' : 'es5-safe',
this.JSON ? '' : 'json'
],
});
preLoad中的配置,需要等到use时才加载
seajs.use("./b",function(){
//在加载b模块之前,预加载项已经加载完成。
});
preLoad中的配置,无法保证模块定义中已经加载完成并执行完成。
debug String
值为 true 时,加载器不会删除动态插入的 script 标签。插件也可以根据 debug 配置,来决策 log 等信息的输出。
base String
Sea.js 在解析顶级标识时,会相对 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 可以多次执行,每次执行都会对配置项进行合并操作。
config 会自动合并不存在的项,对存在的项则进行覆盖。
建议seajs.config所在的js文件
独立成一个文件时,一般通过 script 标签在页面中同步引入。
定义函数:
函数: define(factory)
其中,define方法是全局方法,作用域是window;factory可以是字符串,对象或者函数。
Factory类型:
- 字符串或者对象。
define({"username":"fol","age":"23"}); //factory是对象类型
define("I love zhuzhou"); //factory是字符串类型
如果模块的factory类型是字符串或者对象,那么该模块对外提供的接口(输出)就是字符串或者对象本身。
- 函数
define(function(require, exports, module){
//todo
});
如果factory的类型是函数,那么给函数就是该模块的构造函数,执行该函数可以得到模块对外提供接口。
factory默认传入的参数是require, exports, module。
这种模块定义方式未指定模块id,模块依赖列表。
定义格式2:
define(id? , deps?, factory?)
模块定义也可以接受两个以上参数,其中id定义模块的id,deps声明模块依赖的其他模块列表(Array)。
define("user", ["jquery", "ajax", "upload", "json"], function(require, exports, module){
// todo
});
注意:id 和 deps参数是可选参数,可以通过构建工具自动生成。
模块引用(require Function)
模块加载函数:
函数: require( id? )
其中,require对象是factory的第一个参数,同样,require函数也是全局函数,作用域是window;
id是模块标识,一般在模块定义函数中被调用,用来获取其他模块提供的接口。
define(function( require, exports, module ) {
var $ = require("jquery"); //引入jquery模块
$(document).ready(function(){
$("#bt").on("click", function(){
console.log("the bt is clicked");
});
});
});
异步加载:
函数: require.async( id?, callback? )
其中,id是模块的标识,callback是模块异步加载完成后的回调函数。
define(function(require, exports, module){
//异步加载单个模块
require.async("plugins/bootstrap", function(b){
b.doSomething();
});
//异步加载多个模块
require.async(["./utils/datepicker","./utils/colorpicker"], function(datepicker, colorpicker){
datepicker.init();
colorpicker.init();
});
});
注意:require()是同步执行的,require.async()是异步回调执行,require.async用来执行可以延迟加载执行的模块。
函数: require.resolve(id?)
其中,id 是模块的唯一标识,只用来解析模块的绝对路径。
define(function(require, exports, module){
val jqueryPath = require.resolve("jquery"); //解析jquery的绝对路径
//http://cdn.bootcss.com/jquery/3.0.0-beta1/jquery.js
});
seajs 三个对象
exports Object
exports 是一个对象,负责对外提供模块接口。
define(function(require, exports, module){
function trim() {
return this.replace(/(^\s*)|(\s*$)/g, "")
}
exports.trim = trim; //把内部函数暴露给其他模块
exports.config = {"username":"foo", "age":"23"};//把内部对象暴露给其他模块
});
除了使用exports 对象对外提供接口外,还可以使用return直接对外提供接口
define(function(require, exports, module){
function trim() {
return this.replace(/(^\s*)|(\s*$)/g, "")
}
return {
"trim":trim,
"config":{"username":"foo", "age":23}
}
});
如果模块中return语句是唯一的代码,那么可以使用define({})简化模块定义:
define({
"trim":function(){
return this.replace(/(^\s*)|(\s*$)/g, "")
},
"config":{"username":"foo","age":23}
}
);
上面的格式可以适合定义JSONP模块。
注意:不能对exports重新赋值,这样虽然不会影响到module.exports 。但是无法对外提供接口,可以赋值module.exports达到对外提供接口的目的。
define(function(require, exports , module){
module.exports = {
trim:function(){ return this.replace(/(^\s*)|(\s*$)/g, "");},
config:{"username":"foo", "age":23}
}
});
module Object
module是一个对象,其中存储着于当前模块相关的一些方法和属性。
module.id:返回模块的标识
module.uri:返回模块的绝对路径。
define(function(require, exports, module){
var module_uri = module.uri;
//==>
http://example.com/path/to/this/file.js
});
一般情况下,如果没有定义模块id, 那么module.id == module.uri,两者完全相同。
module.dependencies Array :返回当前模块依赖的模块列表。
module.exports :返回当前模块对外提供的接口对象。
注意:传递给factory方法的参数中exports 只是module.exports 的一个引用,只能通过exports来对外提供接口,但是module.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 不能异步执行
setTimeout(function(){
module.exports = {"username":"foo", "age":23}; //错误,无法对外提供接口
}, 1000); });
seajs.use Function
函数:seajs.use(ids?, callback?)
作用:用在页面上加载其他模块。
//加载一个模块
seajs.use("./a");
//加载一个模块并回调
seajs.use("./a", function(a){
a.doSomething();
});
//加载多个模块并回调
seajs.use(["jquery","./a"], function($, a){
$("#bt").click(function(){
//doSomething
});
a.doSomething();
});
注意:seajs.use方法和document.ready()方法没有必然关系,如果某些操作需要在document ready后才能执行操作,需要借助jquery等依赖模块。
seajs.cache Function
函数:seajs.cache();
seajs.resolve Function
可以用来查看当前页面加载的依赖模块列表。
函数:seajs.resolve(id?);
可以用来获取依赖模块的绝对路径。
seajs.data Function
函数:seajs.data();
可以用来seajs的所有配置以及一些内部变量,可以用在插件开发中。
模块标识与路径关系
模块标识:
模块标识主要以小驼峰,. 或者 .. 为主。
//在http://example.com/my/js/user.js中
define(function(require, exports, module){
var path = require.resolve("./json");
//==>路径为http://example.com/my/js/json.js
var path2 = require.resolve("../json");
//==> 路径为http://example.com/my/json.js
});
注意:以小驼峰开头的模块标识是顶级标识,以base为根目录加载模块文件;以.和..开头的模块标识是相对标识,以当前模块文件所在的位置为基础根目录加载。
//假设base 是http://examle.com/my/js
define(function(require, exports, module){
var path = require.resolve("json");
//==>路径为http://example.com/my/js/json.js
});
注意:页面中基于当前页面为根目录加载模块文件。
构建工具那些事
构建过程描述:
- 提取操作
提取模块的标识id以及模块的其他依赖dependencies。
//a.jsdefine(function(require, exports, module){var b = require("./b");});
经过提取操作,a.js文件会变成临时文件:
define("xxx/1.0.0/a",["./b"],function(require, exports, module){var b = require("./b");});
- 压缩操作
经过上面的提取操作后,构建工具就可以调用任何 JS 压缩工具来进行压缩了,require 参数也可以被压缩成任意字符。相比于其他的压缩工具,CMD模块的构建过程增加了id和dependencies的提取过程。
我们在模块定义过程中,可能会合并两个模块定义文件,使得模块管理更加方便和易用。
//a.js
define(function(require, exports, module){
//todoSomething
});
//b.js
define(function(require, exports, module){
//todoSomething
});
如果我们希望合并以上两个文件,那么会出现模块定义不清楚,无法确认加载哪个模块的问题,所以在模块构建过程中需要提取模块标识id。
此外,即便不合并,保持一个文件一个模块,如果压缩时不提取 id,那么在 IE6-9 下也有可能会出现问题。
为了保证压缩文件随意压缩代码,构建工具在提取id的同时,也会提取dependencies数组。这样seajs不会再通过factory.toString()借助于正则匹配来获取依赖,直接可以通过factory函数的第二个参数拿到依赖数组。
注意:一旦factory的第二个参数定义了依赖数组,那么seajs将不会使用正则匹配的方式去分析并获取依赖,而是直接使用factory第二个参数提供的依赖数组作为所有的依赖。