当前位置: 首页
前端开发
SeaJS模块依赖加载与模块执行流程

SeaJS模块依赖加载与模块执行流程

热心网友 时间:2026-06-10
转载

Sea js通过seajs use创建匿名模块,调用Module use加载依赖,用load递归加载并借助_waitings和_remain追踪状态,并行请求,依赖全部加载后执行回调,通过exec获取模块导出。

详细解析 Sea.js 的模块依赖加载与执行机制。本文直接切入核心,梳理其关键逻辑的运行流程。

入口方法

每个模块系统都存在一个入口,类似于 C 语言中的 main 函数。在 Sea.js 中,这个入口由 seajs.use() 承担。它接收两个参数:模块名称列表和回调函数。入口方法内部会创建一个新模块,该模块依赖于传入的所有模块,并设置一个回调,当所有依赖进入 loaded 状态后触发执行。回调的主要任务是依次调用每个依赖模块的工厂函数,最后再运行入口方法中用户提供的那个回调。

// Public API
// 入口地址
seajs.use = function(ids, callback) {
 Module.preload(function() {
 Module.use(ids, callback, data.cwd + "_use_" + cid())
 })
 return seajs
}

// Load preload modules before all other modules
Module.preload = function(callback) {
 var preloadMods = data.preload
 var len = preloadMods.length

 if (len) {
 Module.use(preloadMods, function() {
  // Remove the loaded preload modules
  preloadMods.splice(0, len)

  // Allow preload modules to add new preload modules
  Module.preload(callback)
 }, data.cwd + "_preload_" + cid())
 }
 else {
 callback()
 }
}

// Use function is equal to load a anonymous module
Module.use = function (ids, callback, uri) {
 var mod = Module.get(uri, isArray(ids) ? ids : [ids])

 mod.callback = function() {
 var exports = []
 var uris = mod.resolve()

 for (var i = 0, len = uris.length; i < len; i++) {
  exports[i] = cachedMods[uris[i]].exec()
 }
 // 回调函数的入参对应依赖模块的返回值
 if (callback) {
  callback.apply(global, exports)
 }

 delete mod.callback
 }

 mod.load()
}

Module.preload 主要用于 Sea.js 插件体系,并非核心路径,可以暂时忽略。Module.use 才是真正的核心入口方法:创建匿名模块,绑定回调,然后触发其所有依赖的加载流程。

加载依赖之 load 方法

load 方法是 Sea.js 最为精妙的部分。它负责递归加载依赖模块,并按照依赖关系依次触发回调,最终会执行到通过 seajs.use 创建的匿名模块的回调(即 mod.callback)。

该方法通过递归实现依赖加载:如果依赖模块本身还有子依赖,则继续深入加载。关键在于两个实例属性:_waitings_remain

Module.prototype.load = function() {
 var mod = this

 // If the module is being loaded, just wait it onload call
 if (mod.status >= STATUS.LOADING) {
 return
 }

 mod.status = STATUS.LOADING

 // Emit `load` event for plugins such as combo plugin
 var uris = mod.resolve()
 emit("load", uris, mod)

 var len = mod._remain = uris.length
 var m

 // Initialize modules and register waitings
 for (var i = 0; i < len; i++) {
 m = Module.get(uris[i])

 // 修改 依赖文件 的 _waiting属性
 if (m.status < STATUS.LOADED) {
  // Maybe duplicate: When module has dupliate dependency, it should be it's count, not 1
  m._waitings[mod.uri] = (m._waitings[mod.uri] || 0) + 1
 }
 else {
  mod._remain--
 }
 }

 // 加载完依赖,执行模块
 if (mod._remain === 0) {
 mod.onload()
 return
 }

 // Begin parallel loading
 var requestCache = {}

 for (i = 0; i < len; i++) {
 m = cachedMods[uris[i]]

 // 该依赖并未加载,则先fetch,将seajs.request函数绑定在对应的requestCache上,此时并未加载模块
 if (m.status < STATUS.FETCHING) {
  m.fetch(requestCache)
 }
 else if (m.status === STATUS.SA VED) {
  m.load()
 }
 }

 // Send all requests at last to a void cache bug in IE6-9. Issues#808
 // 加载所有模块
 for (var requestUri in requestCache) {
 if (requestCache.hasOwnProperty(requestUri)) {
  // 此时加载模块
  requestCache[requestUri]()
 }
 }
}

// 依赖模块加载完毕执行回调函数
// 并检查依赖该模块的其他模块是否可以执行
Module.prototype.onload = function() {
 var mod = this
 mod.status = STATUS.LOADED

 if (mod.callback) {
 mod.callback()
 }
 console.log(mod)
 // Notify waiting modules to fire onload
 var waitings = mod._waitings
 var uri, m

 for (uri in waitings) {
 if (waitings.hasOwnProperty(uri)) {
  m = cachedMods[uri]
  m._remain -= waitings[uri]
  if (m._remain === 0) {
  m.onload()
  }
 }
 }

 // Reduce memory taken
 delete mod._waitings
 delete mod._remain
}

执行流程如下:首先初始化 _waitings_remain。如果 _remain 为 0,说明没有依赖或依赖已经加载就绪,直接调用 onload;否则,将尚未加载的模块通过 fetch 拉取下来。

实现中有一个巧妙的小技巧:所有加载请求先统一收集到 requestCache 对象中,最后集中发出,从而实现并行加载效果。在 fetch 函数中会进行如下绑定:

if (!emitData.requested) {
 requestCache ?
  requestCache[emitData.requestUri] = sendRequest :
  sendRequest()
 }

其中 sendRequest 最终调用 seajs.request

function sendRequest() {
 seajs.request(emitData.requestUri, emitData.onRequest, emitData.charset)
 }

所有依赖并行加载完成后,会执行 onRequest 回调,继续向上冒泡加载更深的依赖层,直到到达最顶层模块。

当最上层模块的所有子依赖都已加载完毕时,执行 onload 函数:将模块状态设为 loaded,执行 mod.callback,然后检查并更新依赖它的各模块的 _waitings 计数。如果下层模块也已无剩余依赖,则继续触发下层模块的 mod.callback。这样逐层回溯,最终会执行到由 seajs.use 创建的匿名模块的回调。

例证

用一个简单实例验证上述流程:

tst.html


-------------------------------------
a.js

define(function(require,exports,module){
 exports.add = function(a,b){
  return a+b;
 }
})
------------------------------------
b.js

define(function(require,exports,module){
 var a = require("./a");
 console.log(a.add(3,5));
})

通过调试工具可以观察到 onload 的执行顺序:

\

最顶层的匿名模块状态为 4(即未执行),因为它没有工厂函数,确实无法执行。

模块执行之 exec

模块的实际执行发生在 seajs.use 所定义的 mod.callback 内部,该回调会依次调用所有依赖模块的 exec 方法。在 exec 方法中,会创建 CommonJS 环境下的 requireexports 等关键对象:

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 a voiding
 // circularly calling
 if (mod.status >= STATUS.EXECUTING) {
 return mod.exports
 }

 mod.status = STATUS.EXECUTING

 // Create require
 var uri = mod.uri

 function require(id) {
 return Module.get(require.resolve(id)).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

 // 工厂函数有返回值,则返回;
 // 无返回值,则返回mod.exports
 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 exports
}

require 函数用于获取指定模块并执行其工厂函数,返回导出的值。require.resolve 负责将模块标识解析为绝对路径。require.async 则提供异步加载依赖并执行回调的能力。

关于工厂函数的返回值处理:如果工厂函数本身是一个对象,那么该对象直接作为 exports;如果工厂函数有返回值,则采用返回值作为 exports;否则将 module.exports 作为 exports 返回。

一个值得注意的细节: 如果试图通过直接给 exports 赋值来导出对象:

define(function(require,exports,module){
 exports ={
  add: function(a,b){
    return a+b;
  }
 }
})

这种做法不会生效。因为 Java​​Script 的参数传递遵循“按共享传递”,初始时 exports === module.exports,但当为 exports 赋予一个新对象时,exports 便指向该新对象,而 module.exports 仍为 undefined,最终导出的将是 undefined

正确的做法是:

define(function(require,exports,module){
 module.exports ={
  add: function(a,b){
    return a+b;
  }
 }
})

总结

至此,Sea.js 核心模块的实现已分析完毕。源码中蕴含了许多精巧的编码技巧,回调模式运用得极为灵活,同时对内存泄漏、this 指针偏移等问题都做了积极的防御处理。这种设计哲学非常值得借鉴。

阅读 Sea.js 源码花费了一周多的时间,从最初的懵懂到逐渐透彻,真切体会到设计思想的重要性。过去可能更关注“能跑起来、不出 bug、有一定健壮性”,但深入 load 依赖模块的实现后,才明白技巧性往往决定了代码的美感与可维护性。希望这篇文章能对你有所启发。

来源:https://www.jb51.net/article/95306.htm

游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

同类文章
更多
DCloud平台选型指南 适用场景与核心差异详解

DCloud平台选型指南 适用场景与核心差异详解

DCloud是一个围绕跨平台开发与流应用构建的技术生态,旨在帮助前端开发者使用Web技术开发高性能移动应用。其核心产品包括集成开发环境HBuilderX和基于Vue js的uni-app框架,支持一套代码发布至iOS、Android、Web及多端小程序。相比ReactNative和Flutter,uni-app在多端覆盖和Vue技术栈学习成本上具有优势,适合

时间:2026-07-10 06:47
DCloud使用教程与常见问题解决方案详解

DCloud使用教程与常见问题解决方案详解

DCloud提供跨平台应用开发方案,核心为HBuilderX与uni-app框架。开发者使用Vue js语法编写代码,可编译发布至iOS、Android、Web及小程序等多端。流程涵盖项目创建、开发调试与真机预览。常见问题如设备识别、编译失败等可通过检查设置、查看日志与条件编译解决。应用完成后支持云打包生成安装包。

时间:2026-07-10 06:47
DCloud新手入门教程 从零开始快速掌握开发技巧

DCloud新手入门教程 从零开始快速掌握开发技巧

DCloud是一个基于Web技术的跨平台应用开发平台,允许开发者使用HTML5等语言编写代码,并编译为iOS、Android及小程序应用,实现“一次编写,多端发布”。开发需使用HBuilderX,基于Vue js进行页面开发,平台提供丰富组件与API,并支持插件市场扩展功能。

时间:2026-07-10 06:47
DCloud新手入门指南从零开始了解这个开发平台

DCloud新手入门指南从零开始了解这个开发平台

DCloud是一个移动应用开发平台,旨在帮助前端开发者使用Web技术高效开发性能接近原生的跨平台应用。其核心产品包括HBuilderX开发环境和基于Vue js的uni-app框架,可实现一次开发、多端发布。平台通过完整工具链和插件生态降低开发门槛,适合需要快速迭代、覆盖多端的应用场景。

时间:2026-07-10 06:47
Zepto实战项目优化技巧与应用场景详解

Zepto实战项目优化技巧与应用场景详解

Zepto js作为轻量级JavaScript库,专为移动端优化,体积仅约10KB。它支持模块化定制,可按需构建以减小体积。使用时需减少DOM操作、善用事件委托,并可混合原生API提升性能。Zepto能融入现代工程流,但需注意其与jQuery的API差异及兼容性限制,适合在移动端性能优先的场景中替代jQuery。

时间:2026-07-10 06:47
热门专题
更多
刀塔传奇破解版无限钻石下载大全 刀塔传奇破解版无限钻石下载大全
洛克王国正式正版手游下载安装大全 洛克王国正式正版手游下载安装大全
思美人手游下载专区 思美人手游下载专区
好玩的阿拉德之怒游戏下载合集 好玩的阿拉德之怒游戏下载合集
不思议迷宫手游下载合集 不思议迷宫手游下载合集
百宝袋汉化组游戏最新合集 百宝袋汉化组游戏最新合集
jsk游戏合集30款游戏大全 jsk游戏合集30款游戏大全
宾果消消消原版下载大全 宾果消消消原版下载大全
  • 热门数据榜