下载及安装

要在项目中使用SeaJS,其实就一步:下载sea.js文件,然后扔到你项目的某个目录里。
SeaJS托管在GitHub上,项目主页是 https://github.com/seajs/seajs/ 。从它的build目录下可以找到两个版本:sea.js(压缩版)和sea-debug.js(未压缩版)。
下载后放到项目合适的位置,页面里用标签引入,一切就绪。
SeaJS基本开发原则
在正式动手之前,先聊聊SeaJS的模块化理念和开发原则。
使用SeaJS写Ja vaScript,核心就一句话:一切皆为模块。引入SeaJS之后,写Ja vaScript代码变成了写一个个模块,模块的概念跟面向对象里的类有点像——模块可以有数据和方法,还能设置公共或私有,公共的接口可以供其他模块调用。
另外,每个模块通常对应一个单独的js文件,一个文件就是一个模块。
接下来看看模块怎么写、怎么调。
模块的定义及编写
模块定义函数define
SeaJS用define函数来定义一个模块。翻阅源码会发现,define可以接收三个参数:
复制代码 代码如下:
/** * Defines a module. * @param {string=} id The module id. * @param {Array.|string=} deps The module dependencies. * @param {function()|Object} factory The module factory function. */ fn.define = function(id, deps, factory) { //code of function… }
参数分别是模块ID、依赖模块数组和工厂函数。根据参数个数,解析规则如下:
- 只有一个参数:直接赋值给factory。
- 两个参数:第二个给factory;第一个如果是数组就给deps,否则给id。
- 三个参数:依次赋值给id、deps、factory。
不过,包括官方示例在内,绝大多数场景都只传一个工厂函数,像这样:
复制代码 代码如下:
define(function(require, exports, module) { //code of the module... });
通常建议跟随官方示例,只用单个参数定义模块。那id和deps怎么办呢?
id是模块的标识字符串,如果只传一个参数,id默认就是js文件的绝对路径。比如example.com下的a.js中定义模块,它的ID会被赋值为https://example.com/a.js。没特殊需要就别传id。deps一般也不需要,依赖模块直接用require加载就行。
工厂函数factory解析
工厂函数是模块的核心。只传一个参数给define时(推荐做法),这个参数就是工厂函数,它有三个参数:
1. require——模块加载函数,用来加载依赖模块。
2. exports——接口点,把数据或方法定义在它上面,就能暴露给外部调用。
3. module——模块的元数据。
这三个参数按需使用,不一定全都要。
module是个对象,存储模块的元信息:
- module.id——模块ID。
- module.dependencies——数组,存放所有依赖的模块ID。
- module.exports——和exports指向同一个对象。
三种编写模块的模式
第一种是基于exports的模式:
复制代码 代码如下:
define(function(require, exports, module) { var a = require('a'); //引入a模块 var b = require('b'); //引入b模块 var data1 = 1; //私有数据 var func1 = function() { //私有方法 return a.run(data1); } exports.data2 = 2; //公共数据 exports.func2 = function() { //公共方法 return 'hello'; } });
这是一种比较正统的写法。除了把公共接口挂在exports上,也可以直接返回一个对象,功能相同:
复制代码 代码如下:
define(function(require) { var a = require('a'); //引入a模块 var b = require('b'); //引入b模块 var data1 = 1; //私有数据 var func1 = function() { //私有方法 return a.run(data1); } return { data2: 2, func2: function() { return 'hello'; } }; });
如果模块除了返回一个对象没别的代码,还能进一步简化:
复制代码 代码如下:
define({ data: 1, func: function() { return 'hello'; } });
第三种写法对于纯JSON数据模块特别合适。
模块的载入和引用
模块的寻址算法
前面说过,一个模块对应一个js文件。载入模块时给个字符串参数,需要有一套从字符串到实际文件路径的解析算法。SeaJS支持以下标识:
- 绝对地址——直接给js文件的绝对路径。
如:require("https://example/js/a"); 就加载 https://example/js/a.js。
- 相对地址——相对于调用require的那个js文件所在的路径。
例如在 https://example/js/b.js 中 require("./c");,则加载 https://example/js/c.js。
- 基址地址——如果标识既不是绝对路径也不是以 ./ 开头,就相对SeaJS全局配置中的 base 来寻址,稍后会讨论。
注意:加载模块时不用带 .js 后缀,SeaJS会自动添加。但下面三种情况不会自动添加:
- 加载CSS时,如 require("./module1-style.css");
- 路径中含有 ? 时,如 require("https://example/js/a.json?cb=func");
- 路径以 # 结尾时,如 require("https://example/js/a.json#");
根据场景不同,SeaJS提供了三个载入模块的API:seajs.use、require 和 require.async。
seajs.use
seajs.use 主要用于加载入口模块——类似C程序的main函数,也是整个依赖树的根。用法如下:
复制代码 代码如下:
//单一模式 seajs.use('./a'); //回调模式 seajs.use('./a', function(a) { a.run(); }); //多模块模式 seajs.use(['./a', './b'], function(a, b) { a.run(); b.run(); });
通常 seajs.use 只在页面加载入口模块时使用,SeaJS会顺着入口模块解析所有依赖并加载。如果入口模块只有一个,还可以通过给引入sea.js的script标签加 data-main 属性来省略 seajs.use,比如前面TinyApp的index.html可以改写成:
复制代码 代码如下:
这种写法让HTML更简洁。
require
require 是SeaJS主要的模块加载方法。在一个模块里需要用到其他模块时,一般用它加载:
var m = require('/path/to/module/file');
简要说说SeaJS的自动加载机制:页面引入sea.js后,SeaJS会先下载入口模块,然后通过正则表达式匹配代码中所有的require,根据标识下载对应的js文件,并迭代处理。整个过程类似图的遍历(因为可能存在交叉循环依赖,依赖结构是个图而不是树)。
明白了这一点,下一条规则就很好理解了:传给require的路径标识必须是字符串字面量,不能是表达式。比如下面这样是错误的:
复制代码 代码如下:
require('module' + '1'); require('Module'.toLowerCase());
这会妨碍SeaJS的正则匹配,导致无法正确下载js文件。
require.async
SeaJS默认在页面打开时通过静态分析一次性加载所有需要的js文件。如果想让某个js文件用到时才下载,可以用 require.async:
复制代码 代码如下:
require.async('/path/to/module/file', function(m) { //code of callback... });
这样只有在用到这个模块时,对应的js文件才会下载,实现了按需加载。
SeaJS的全局配置
SeaJS提供了 seajs.config 方法设置全局配置,接收一个配置对象。用法如下:
复制代码 代码如下:
seajs.config({ base: 'path/to/jslib/', alias: { 'app': 'path/to/app/' }, charset: 'utf-8', timeout: 20000, debug: false });
base 表示基址路径。比如base设为 https://example.com/js/3-party/,那么 var $ = require('jquery'); 会加载 https://example.com/js/3-party/jquery.js。
alias 可以对较长的常用路径设置缩写。
charset 表示下载js时script标签的charset属性。
timeout 表示下载文件的最大时长,单位毫秒。
debug 表示是否工作在调试模式下。
SeaJS如何与现有JS库配合使用
要把jQuery等现有库与SeaJS一起用,只需按照SeaJS的模块定义规则封装一下。比如jQuery的封装:
复制代码 代码如下:
define(function() { //{{{jQuery原有代码开始 /*! * jQuery Ja vaScript Library v1.6.1 * https://jquery.com/ * * Copyright 2011, John Resig * Dual licensed under the MIT or GPL Version 2 licenses. * https://jquery.org/license * * Includes Sizzle.js * https://sizzlejs.com/ * Copyright 2011, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * * Date: Thu May 12 15:04:36 2011 -0400 */ //... //}}}jQuery原有代码结束 return $.noConflict(); });
SeaJS项目的打包部署
SeaJS原本集成了打包部署工具spm,后来作者为了更轻量,把spm拆成了独立项目。spm的核心思想是:将所有模块的代码合并压缩后并入入口模块。利用SeaJS本身的特性,HTML无需任何改动就能方便地在开发环境和生产环境间切换。不过spm目前还没有正式版本,这里就不详细展开了,感兴趣的朋友可以看看它的GitHub项目主页 https://github.com/seajs/spm/ 。
其实,每个项目用的JS合并压缩工具不尽相同,spm不一定完全适用。在理解SeaJS的原理之后,完全可以自己写一个符合项目特征的打包脚本。
