当前位置: 首页
前端开发
SeaJS模块依赖加载与API导出详解

SeaJS模块依赖加载与API导出详解

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

SeaJS通过Module类管理模块生命周期,状态机控制加载进度。define函数定义模块并解析require依赖。异步加载采用scriptelement方式并行获取资源,支持CSS。parseDependencies正则提取依赖名,fetch方法更新状态并处理等待队列,最终安全导出模块API。

引言

seajs学习之模块的依赖加载及模块API的导出

SeaJS 的核心优势在于,它能够加载任意 JavaScript 模块甚至 CSS 样式表,并确保在调用某个模块时,其所有依赖都已安全无误地加载完成。这背后究竟采用了哪些技术手段?借助上一节的示例,我们直接深入源码一探究竟——那些看似简单的 API 调用,其背后应用了哪些技巧来实现模块依赖加载和模块 API 的导出。

模块类与状态类

首先来看 Module 类,它对应一个独立的模块单元。

function Module(uri, deps) {
 this.uri = uri
 this.dependencies = deps || []
 this.exports = null
 this.status = 0

 // Who depends on me
 this._waitings = {}

 // The number of unloaded dependencies
 this._remain = 0
}

Module 类中几个重要的属性:uri 代表模块的绝对 URL(在 Module.define 中自动生成);dependencies 是依赖模块的数组;exports 用于导出公开的 API;status 表示当前的状态码;_waitings 是一个哈希表,存储那些依赖于当前模块的其他模块的 URL;_remain 是一个计数器,记录尚未加载完成的依赖数量。

var STATUS = Module.STATUS = {
 // 1 - The `module.uri` is being fetched
 FETCHING: 1,
 // 2 - The meta data has been saved to cachedMods
 SAVED: 2,
 // 3 - The `module.dependencies` are being loaded
 LOADING: 3,
 // 4 - The module are ready to execute
 LOADED: 4,
 // 5 - The module is being executed
 EXECUTING: 5,
 // 6 - The `module.exports` is available
 EXECUTED: 6
}

状态对象清晰描述了模块从初始化到执行完成的完整生命周期:初始状态为 0;开始加载时转变为 FETCHING;加载完毕并缓存至 cacheMods 后变为 SAVEDLOADING 表示正在加载该模块的其他依赖;LOADED 表示所有依赖均已就绪,可以执行回调,同时需检查那些依赖当前模块的其他模块是否也已准备就绪;EXECUTING 是模块正在执行,EXECUTED 则代表执行结束,此时可以安全使用 exports 导出的 API。

模块定义机制

CommonJS 规范规定使用 define 函数来定义模块。该函数可接受 1 到 3 个参数,但根据 Module/wrappings 规范,module.declaredefine 仅允许接受一个参数——工厂函数或对象。实际上参数数量不影响核心功能,库会在后台自动补齐模块名称。

SeaJS 推荐使用 define(function(require, exports, module){}) 的写法,这是典型的 Module/wrappings 规范实现。后台会自动解析工厂函数中的 require 调用以提取依赖,并为模块自动设置 id 和 url。

// 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
 // 如果deps为非数组,则序列化工厂函数获取入参。
 if (!isArray(deps) && isFunction(factory)) {
 deps = parseDependencies(factory.toString())
 }

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

 // Try to derive uri in IE6-9 for anonymous modules
 // 导出匿名模块的uri
 if (!meta.uri && doc.attachEvent) {
 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
}

在定义的最后,通过 Module.save 将模块存储到 cachedMods 缓存中。

parseDependencies 方法的设计十分巧妙——它将工厂函数转换为字符串,然后利用正则表达式提取所有 require("...") 中的模块名称。

var REQUIRE_RE = /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\/\*[\s\S]*?\*\/|\/(?:[^\/\r\n])+\/(?=[^\/])|\/\/.*|\.\s*require|(?:^|[^$])\brequires\s*\(\s*(["'])(.+?)\1\s*)/g
var SLASH_RE = /\\/g

function parseDependencies(code) {
 var ret = []
 // 此处使用函数序列化(传入的factory)进行字符串匹配,寻找require(“...”)的关键字
 code.replace(SLASH_RE, "")
 .replace(REQUIRE_RE, function(m, m1, m2) {
 if (m2) {
  ret.push(m2)
 }
 })

 return ret
}

异步加载模块原理

模块加载有多种实现方式。XHR 支持同步或异步,但受同源策略限制,不够灵活。script tag 方式在 IE 及现代浏览器下能确保并行加载且按顺序执行;script element 方式可实现并行加载,但不保证执行顺序。这两种方式均可采用。

SeaJS 选择了 script element 方式进行 JS/CSS 资源的并行加载,并对旧版本 WebKit 浏览器加载 CSS 做了特殊兼容处理。

function request(url, callback, charset) {
 var isCSS = IS_CSS_RE.test(url)
 var node = doc.createElement(isCSS ? "link" : "script")

 if (charset) {
 var cs = isFunction(charset) ? charset(url) : charset
 if (cs) {
 node.charset = cs
 }
 }

 // 添加 onload 函数。
 addOnload(node, callback, isCSS, url)

 if (isCSS) {
 node.rel = "stylesheet"
 node.href = url
 }
 else {
 node.async = true
 node.src = url
 }

 // For some cache cases in IE 6-8, the script executes IMMEDIATELY after
 // the end of the insert execution, so use `currentlyAddingScript` to
 // hold current node, for deriving url in `define` call
 currentlyAddingScript = node

 // ref: #185 & http://dev.jquery.com/ticket/2709
 baseElement ?
 head.insertBefore(node, baseElement) :
 head.appendChild(node)

 currentlyAddingScript = null
}

function addOnload(node, callback, isCSS, url) {
 var supportOnload = "onload" in node

 // for Old WebKit and Old Firefox
 if (isCSS && (isOldWebKit || !supportOnload)) {
 setTimeout(function() {
 pollCss(node, callback)
 }, 1) // Begin after node insertion
 return
 }

 if (supportOnload) {
 node.onload = onload
 node.onerror = function() {
 emit("error", { uri: url, node: node })
 onload()
 }
 }
 else {
 node.onreadystatechange = function() {
 if (/loaded|complete/.test(node.readyState)) {
 onload()
 }
 }
 }

 function onload() {
 // Ensure only run once and handle memory leak in IE
 node.onload = node.onerror = node.onreadystatechange = null

 // Remove the script to reduce memory leak
 if (!isCSS && !data.debug) {
 head.removeChild(node)
 }

 // Dereference the node
 node = null

 callback()
 }
}
// 针对 旧webkit和不支持onload的CSS节点判断加载完毕的方法
function pollCss(node, callback) {
 var sheet = node.sheet
 var isLoaded

 // for WebKit < 536
 if (isOldWebKit) {
 if (sheet) {
 isLoaded = true
 }
 }
 // for Firefox < 9.0
 else if (sheet) {
 try {
 if (sheet.cssRules) {
 isLoaded = true
 }
 } catch (ex) {
 // The value of `ex.name` is changed from "NS_ERROR_DOM_SECURITY_ERR"
 // to "SecurityError" since Firefox 13.0. But Firefox is less than 9.0
 // in here, So it is ok to just rely on "NS_ERROR_DOM_SECURITY_ERR"
 if (ex.name === "NS_ERROR_DOM_SECURITY_ERR") {
 isLoaded = true
 }
 }
 }

 setTimeout(function() {
 if (isLoaded) {
 // Place callback here to give time for style rendering
 callback()
 }
 else {
 pollCss(node, callback)
 }
 }, 20)
}

有一个细节值得特别关注:使用 script element 插入 script 节点时,应尽量将其放置在 head 的第一个子节点之前。原因是 IE6 下存在一个不易察觉的 Bug——当页面 head 中包含 标签时,globalEval 的执行会出现异常。

fetch 模块:加载与依赖处理

初始化 Module 对象时状态为 0,对应的 JS 文件尚未加载。要加载该文件,就需要使用上面提到的 request 方法,但不仅仅要加载文件,还需更新模块状态并处理依赖关系。这些逻辑全部封装在 fetch 方法中。

// Fetch a module
// 加载该模块,fetch函数中调用了seajs.request函数
Module.prototype.fetch = function(requestCache) {
 var mod = this
 var uri = mod.uri

 mod.status = STATUS.FETCHING

 // Emit `fetch` event for plugins such as combo plugin
 var emitData = { uri: uri }
 emit("fetch", emitData)
 var requestUri = emitData.requestUri || uri

 // Empty uri or a non-CMD module
 if (!requestUri || fetchedList[requestUri]) {
 mod.load()
 return
 }

 if (fetchingList[requestUri]) {
 callbackList[requestUri].push(mod)
 return
 }

 fetchingList[requestUri] = true
 callbackList[requestUri] = [mod]

 // Emit `request` event for plugins such as text plugin
 emit("request", emitData = {
 uri: uri,
 requestUri: requestUri,
 onRequest: onRequest,
 charset: data.charset
 })

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

 function sendRequest() {
 seajs.request(emitData.requestUri, emitData.onRequest, emitData.charset)
 }
 // 回调函数
 function onRequest() {
 delete fetchingList[requestUri]
 fetchedList[requestUri] = true

 // Save meta data of anonymous module
 if (anonymousMeta) {
 Module.save(uri, anonymousMeta)
 anonymousMeta = null
 }

 // Call callbacks
 var m, mods = callbackList[requestUri]
 delete callbackList[requestUri]
 while ((m = mods.shift())) m.load()
 }
}

seajs.request 实际上就是上一节中介绍的 request 方法。onRequest 作为回调函数,主要负责触发该模块所依赖的其他模块的加载流程。

结语

以上便是 SeaJS 模块依赖加载与模块 API 导出的核心实现原理。后续章节将进一步分析模块间依赖的加载机制及模块执行流程。

来源:https://www.jb51.net/article/95271.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款游戏大全
宾果消消消原版下载大全 宾果消消消原版下载大全
  • 热门数据榜