seajs源码(define,exec定义和执行模块代码)

这里是从sea.js 源码中的 Module.js 的Module核心代码抽离出来的,建议大家先行阅读
sea.js源码(Module.js核心代码),对本篇的理解会更加顺畅。

前提引入

前面我们讲到 seajs中,最后执行 顶部模块 的callback回调的时候,最后一层一层的向上执行依赖模块。而 其中的依赖模块是我们js文件中用Module.define()定义的,相应就需要Module.exec()去执行编译。

那么在这之前我们就先看一下 我们js代码如何定义模块。

1
2
3
define(id,deps,function(require, exports, module) {

});

这里我们都知道,function(){} 我们可以调用 require 加载资源,使用exports或module暴露模块接口。那么这些在seajs源码中怎么实现的呢?

实例前瞻

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!--test.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>sea.js解读测试</title>
</head>
<body>
</body>
<script src="../src/seajs-whole.js"></script>
<script>
console.log(seajs);
seajs.use(["./a1"]);
</script>
</html>

<!--a1.js-->
define(function(require, exports, module) {
var b2 = require('./b2');
console.log(b2);
});

<!--b2.js-->
define(function(require, exports, module) {
var msg = "b2";
exports.msg = msg;
console.log(require,exports,module);
});

seajs-js代码中模块的生成

  • define(id,deps,factory) 方法,将js代码封装称为一个模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// Define a module
Module.define = function (id, deps, factory) {
var argsLen = arguments.length

// define(factory)
if (argsLen === 1) {
factory = id
id = undefined
}
else if (argsLen === 2) {
factory = deps

// define(deps, factory)
if (isArray(id)) {
deps = id
id = undefined
}
// define(id, factory)
else {
deps = undefined
}
}

// Parse dependencies according to the module factory code
if (!isArray(deps) && isFunction(factory)) {
deps = typeof parseDependencies === "undefined" ? [] : parseDependencies(factory.toString())
}

var meta = {
id: id,
uri: Module.resolve(id),
deps: deps,
factory: factory
}

// Try to derive uri in IE6-9 for anonymous modules
if (!isWebWorker && !meta.uri && doc.attachEvent && typeof getCurrentScript !== "undefined") {
var script = getCurrentScript()

if (script) {
meta.uri = script.src
}

// NOTE: If the id-deriving methods above is failed, then falls back
// to use onload event to get the uri
}

// Emit `define` event, used in nocache plugin, seajs node version etc
emit("define", meta)

meta.uri ? Module.save(meta.uri, meta) :
// Save information for "saving" work in the script onload event
anonymousMeta = meta
}
  • 熟悉seajs的同学一定知道define()定义模板的时候,有三个参数,(id ,deps, factory), 而id 和 deps是可选的,而这里对参数的处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// define(factory)
if (argsLen === 1) {
factory = id
id = undefined
}
else if (argsLen === 2) {
factory = deps

// define(deps, factory)
if (isArray(id)) {
deps = id
id = undefined
}
// define(id, factory)
else {
deps = undefined
}
}
  • 这里是解析 factory 中js代码中所需要 同步加载的模块,并赋值给 deps 依赖模块数组。
  • util-deps.js–parseDependencies() :解析factory代码中的require()加载的模块,而通过require.async()并不会被解析。

    1
    2
    3
    4
    // Parse dependencies according to the module factory code
    if (!isArray(deps) && isFunction(factory)) {
    deps = typeof parseDependencies === "undefined" ? [] : parseDependencies(factory.toString())
    }
  • 根据定义,依赖,保存 ‘当前定义模块’ 到模块缓存中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var meta = {
id: id,
uri: Module.resolve(id),
deps: deps,
factory: factory
}
if (!isWebWorker && !meta.uri && doc.attachEvent && typeof getCurrentScript !== "undefined") {
var script = getCurrentScript()
if (script) {
meta.uri = script.src
}
// 无法通过Module.resolve(id)解析到uri的时候,通过getCurrentScript()和js加载即执行的的特性。获取uri
}
meta.uri ? Module.save(meta.uri, meta) :
// Save information for "saving" work in the script onload event
anonymousMeta = meta
// 保存 ‘当前定义模块’ 到模块缓存中

seajs-js代码中模块的执行

Module.exec() 执行js代码,获取模块接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// Execute a module
// 执行模块代码
Module.prototype.exec = function () {
var mod = this

// When module is executed, DO NOT execute it again. When module
// is being executed, just return `module.exports` too, for avoiding
// circularly calling
if (mod.status >= STATUS.EXECUTING) {
return mod.exports
}

mod.status = STATUS.EXECUTING

if (mod._entry && !mod._entry.length) {
delete mod._entry
}

//non-cmd module has no property factory and exports
if (!mod.hasOwnProperty('factory')) {
mod.non = true
return
}

// Create require
var uri = mod.uri

function require(id) {
var m = mod.deps[id] || Module.get(require.resolve(id))
if (m.status == STATUS.ERROR) {
throw new Error('module was broken: ' + m.uri)
}
return m.exec()
}

require.resolve = function(id) {
return Module.resolve(id, uri)
}

require.async = function(ids, callback) {
Module.use(ids, callback, uri + "_async_" + cid())
return require
}

// Exec factory
var factory = mod.factory

var exports = isFunction(factory) ?
factory(require, mod.exports = {}, mod) :
factory

if (exports === undefined) {
exports = mod.exports
}

// Reduce memory leak
delete mod.factory

mod.exports = exports
mod.status = STATUS.EXECUTED

// Emit `exec` event
emit("exec", mod)

return mod.exports
}
  • 整体的思路就是
    • 先判断状态,根据当前模块的状态进行处理。
    • == 》 声明定义 require构造对象
    • == 》 执行factory,将require构造对象,初始化的exports空对象,模块传入factory。
    • == 》 暴露并返回exports

声明定义 require构造对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function require(id) {
var m = mod.deps[id] || Module.get(require.resolve(id))
if (m.status == STATUS.ERROR) {
throw new Error('module was broken: ' + m.uri)
}
return m.exec()
}

require.resolve = function(id) {
return Module.resolve(id, uri)
}

require.async = function(ids, callback) {
Module.use(ids, callback, uri + "_async_" + cid())
return require
}
  • 这里值得一提的就是 require.async这是提供我们按需加载的接口。
  • 先了解一下require.async(id)require(id)的不同
    • require(id) 返回模块执行解析的接口,这种同步依赖在Module.define()中调用parseDependencies函数解析得到需要的模块并添加到deps依赖数组中。
    • 而require.async()请求的模块并不会,它会等到我们执行factory代码的时候,根据代码异步地按需加载,也就是说如果我们通过if()等没有执行该代码,则模块不会被加载进来。
  • 当我们执行模块代码的时候,如果遇到require.async(),那么我们就会调用Module.use(ids, callback, uri + "_async_" + cid()),使用Module.use加载这个模块。从而达到异步加载。

执行factory,将(require构造对象,初始化的exports空对象,模块)传入factory

1
factory(require, mod.exports = {}, mod)

暴露exports接口

1
return mod.exports

总结

  • 如何将一个js文件看成一个模块?

    js文件 通过 define() 转换为 “模块”,并提供 require构造对象,exports接口对象,和 modul自身模块对象。

  • sea.js怎么实现按需加载?

    通过提供 require.async() ,该方法引入的模块,不会在模块启动过程中被加载到页面,而是在我们执行解析模块代码的时候,自身调用 Module.use()才开始加载模块资源到页面和执行模块。