本文谈论的代码版本是Sea.js 2.3.0,seajs最新的版本是3.0,3.0版本变动较大.
新事物的出现或多或少的是想改变已有的行为方式,解决遇到的问题。关于这段历史,大家可以看看这几篇文章:why-seajs1、why-seajs2
关于seajs原理介绍和分析的文字也有不少,例如:Sea.js是如何工作的?、实例解析 SeaJS 内部执行过程 - 从 use 说起、也有人分析了seajs代码中的数据结构:SeaJSV1.1.0-代码结构和数据结构
既然已经有很多优秀的文章介绍seajs了,为什么你还要浪费时间呢?
因为脑子不好使,写作的过程使我审视自己的结论,加深了我对事物的理解。
seajs干嘛的
SeaJS是一个遵循CMD规范的JavaScript模块加载框架,用以实现JavaScript的模块化开发及加载机制
注意关键词:CommonJS规范,模块加载,模块化开发
-
CMD规范全称Common Module Definition,该规范明确了模块的基本书写格式和基本交互规则,即定义了一组模块化开发的接口,具体细节可以参看:CMD 模块定义规范、英文 Common Module Definition
-
有C++,Java,Python开发经验的同学应该很容易理解模块化开发和模块加载的概念,以java为例,类是java的最小开发单位,一个类可以看做是一个最小模块,一个或多个类可以组成一个package,这种组织代码的方式就是模块化开发
-
当我们在一个类中import另一个package的内容时,import的内容最终会被包含进当前类中,seajs要做的一个事情就是模拟java中import这种模块加载机制。
-
与服务器端不同,在浏览器端实现模块加载又有其特点:模块(js文件)需要经过网络传输到达用户浏览器,那么何时将js文件下载到用户本地?性能考虑,提前将用到的js文件下载到用户本地是首选,免去了执行期从服务器请求文件消耗的时间,但同时seajs也提供了异步加载模块的功能。
两个核心
1. 模块的依赖加载
如何从服务器获取js文件,这里就不详细解释了,原理就是append script标签的方式。
模块间的关系无非就是依赖和被依赖的关系,具体到两个模块上,有下图三种关系:
- a模块依赖b模块
- b模块依赖a模块
- a,b模块相互依赖
![](https://img.haomeiwen.com/i74982/d33ef45d724428c1.png)
前两种情况很好理解,对于第三种情况,seajs在3.0版本前是不支持循环依赖的,具体表现为模块加载会中断,a.doSomething()并没有执行,3.0的循环依赖处理和node一样了#1382,下面的代码可以用来测试这个问题。
seajs.use("a" , function(a){
a.doSomething();
});
//a.js
define(function(require, exports , module) {
var b = require("b");
exports.doSomething = function() {
console.log("in a");
};
});
//b.js
define(function(require, exports, module) {
var c = require("c");
exports.doSomething = function() {
console.log("in b");
};
});
//c.js
define(function(require, exports, module) {
var a = require("a");
exports.doSomething = function() {
console.log("in c");
};
});
提炼一下上面描述的内容就是:对于一个模块M,它应该拥有下面的关系
Module{
a: {Module依赖的模块}
b: {依赖Module的模块}
}
在看看seajs中Module的定义:
function Module(uri, deps) {
this.uri = uri
this.dependencies = deps || []//我依赖的模块
this.exports = null
this.status = 0 //我当前的状态
this._waitings = {} //依赖我的模块
this._remain = 0 //我依赖的模块还有多少没有完成加载
}
以一个实际的例子说明一下seajs模块加载的逻辑,如下图:
![](https://img.haomeiwen.com/i74982/c1353f31f05a1ebd.png)
- a依赖b和c
- b依赖d
尽管存在上述依赖,但是a,b,c,d模块download到浏览器端的顺序确是a,b,c,d,而不是d,b,c,a,笨想一下后一种执行顺序也是不可能的,因为模块间的依赖只有download到浏览器端seajs才能进行分析。
概括一下整个加载的流程就是:
自顶向下的download,自底向上的反馈准备就绪。
如何做到的呢?
-
主要是Module中的几个属性发挥的作用,模块被download到浏览器端后,按照CMD规范,define函数会被执行,module.define会分析该模块的依赖,记录到dependencies属性中,define函数执行完毕,绑定在script标签上的onload事件会被触发,进而加载当前模块的依赖模块,也就是执行module.load函数,这是一个循环往复的过程。
-
假设d模块加载就绪,执行module.load时发现,d模块已无其他依赖,进而执行module.onload, 在module.onload中,通过_waitings属性找到父模块,操作父模块的依赖计数_remain,达到通知父模块的目的。
这是一个完美的反馈系统。
seajs的模块加载可以分为两部分,一部分就是模块加载的过程,另一部分就是模块执行的过程。相比较而言,执行过程就比较简单了,具体可以看源码module.exec这个函数。
下一节分析seajs中模块id的解析原理。
网友评论