Sea.js曾经是国内前端圈非常流行的一个模块加载器,由淘宝前端团队开发和维护。它遵循CMD规范,核心目标就是把网页中零散的JavaScript文件组织成一个个独立、可复用的模块,让代码结构更清晰、依赖关系更明确。虽然现在新项目大多已经转向Webpack、Vite等构建工具,但大量存量项目仍然在使用Sea.js,理解它的原理和用法依然是很多开发者的实际需求。这篇文章就从基础概念讲起,把Sea.js的定位、用法和常见误区一次说透。

Sea.js到底是什么,解决什么问题
在没有模块化方案的时代,网页开发通常是把所有JavaScript文件用script标签依次引入页面。这种方式有几个明显的痛点:全局变量容易冲突、文件之间的依赖关系全靠人脑记忆、加载顺序一旦出错整个页面就挂掉。比如你写了三个文件a.js、b.js、c.js,其中b.js依赖a.js里的函数,c.js又依赖b.js,那么引入顺序必须是a、b、c,一旦顺序乱了,浏览器就会直接报错。
Sea.js的作用就是把这些文件变成模块。每个模块有自己独立的作用域,模块内部定义的变量不会污染全局。模块之间通过require来声明依赖,Sea.js会自动分析这些依赖关系,按照正确的顺序加载和执行代码,开发者再也不用手动管理script标签的顺序。
它遵循的是CMD规范,全称Common Module Definition。CMD的特点是依赖就近,也就是说你可以在代码的任意位置写require,用到的时候才去加载。这一点和AMD规范(代表库是RequireJS)不同,AMD推崇依赖前置,所有依赖必须在模块定义的开头一次性声明完。两种规范各有优劣,CMD写起来更符合直觉,AMD则在依赖分析上更利于优化。
Sea.js基础用法详解
引入Sea.js并配置
首先要从官网下载seajs文件并在页面中引入,通常只引入sea.js这一个文件即可。引入之后需要通过seajs.config做基础配置,最常用的是base和alias两个选项。base指定模块的基准路径,alias用来给常用路径起别名,避免在代码里写又长又容易出错的相对路径。
一个典型的配置写法如下:
seajs.config({ base: './js/', alias: { 'jquery': 'lib/jquery.min.js', 'utils': 'modules/utils.js' } });
配置了alias之后,在其他模块里就可以直接写require('jquery'),Sea.js会自动把它映射到对应的文件路径,既简洁又方便统一维护。
定义模块用define
在Sea.js的世界里,每一个文件就是一个模块,定义模块必须使用全局函数define。define接收一个函数作为参数,这个函数有三个形参:require、exports、module,它们由Sea.js在执行时自动传入。
require用来加载其他模块并获取其接口;exports是模块对外暴露的对象,往上面挂属性就等于对外提供方法;module是一个元数据对象,module.exports是模块真正的导出值。一个简单的模块长这样:
define(function(require, exports) { var util = require('utils'); exports.formatDate = function(ts) { return util.date(ts); }; });
加载模块用seajs.use
页面上要启动某个模块时,使用seajs.use方法。它可以接收两个参数,第一个是模块标识,第二个是加载完成后的回调函数。例如seajs.use('./main')会加载main.js并执行它,回调中还能拿到模块的导出值直接使用。seajs.use是整个应用的入口,一般只需要在页面底部调用一次。
Sea.js常见误区盘点
误区一:搞不清相对路径相对于谁
很多新手发现require('./a.js')时报找不到模块,问题往往出在路径解析上。在Sea.js中,以./或../开头的相对标识,是相对于当前模块文件本身的路径来解析的,而不是相对于页面URL。如果你在utils.js里写require('./date.js'),那么解析的是utils.js同目录下的date.js。另外要注意,顶级标识(不以点开头、也不是路径形式)是相对于base路径解析的,别把这两种情况混为一谈。
误区二:把require当成普通函数随意使用
CMD虽然支持依赖就近书写,但require的参数必须是字符串字面量,不能写成变量拼接的形式。比如require('jquery' + '-min')这种写法Sea.js无法在加载前分析出依赖,会导致加载失败或行为异常。正确做法是老老实实写require('jquery-min')。如果确实需要动态加载,应该使用require.async方法,它接收模块标识和回调函数,专门处理运行时才能确定的依赖。
误区三:混淆exports和module.exports
不少人以为给exports重新赋值就能导出新对象,比如写exports = {a: 1},结果发现外部拿不到任何东西。原因是Sea.js内部维护的是module.exports的引用,exports只是它的一个别名。一旦给exports整体赋值,就切断了这个引用关系。如果确实需要导出一个全新的对象或函数,正确写法是module.exports = {a: 1}。如果只是添加单个属性,用exports.a = 1则完全没问题。
误区四:忽略异步加载的时序问题
Sea.js的模块加载是异步的,seajs.use执行后并不会阻塞后续代码。有些开发者在use之后立刻访问某个模块暴露的方法,结果报undefined错误。解决办法是把依赖该模块的逻辑放到use的回调函数里,或者放到入口模块内部执行,保证时序正确。
Sea.js与RequireJS对比一览
| 对比项 | Sea.js | RequireJS |
|---|---|---|
| 遵循规范 | CMD | AMD |
| 依赖书写 | 依赖就近,随意位置require | 依赖前置,开头统一声明 |
| 执行时机 | 延迟执行 | 提前执行 |
| 开发体验 | 写法接近CommonJS,更自然 | 结构化更强,利于静态优化 |
| 适用场景 | 国内存量项目较多 | 国际化项目较常见 |
总结
Sea.js的核心价值在于用模块化的方式组织JavaScript代码,define定义模块、require声明依赖、seajs.use启动应用,这三板斧掌握了就等于入门了。对于维护老项目的开发者来说,重点要留意路径解析规则、require参数写法、exports与module.exports的区别以及异步时序这四个高频坑点,遇到问题时按这几个方向排查,基本都能快速定位原因。虽然Sea.js已经不再是新技术选型的主流,但它体现的模块化思想依然是前端工程化的基石,理解它对学习后续的ES Module和各类打包工具都有帮助。