深入seajs源码系列二
模块类和状态类
参照上文的demo,我们结合源码分析在简单的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对象为当前依赖该模块的其他模块哈希表,其中key为其他模块的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后,为状态saved;loading状态意味着正在加载该模块的其他依赖模块;loaded表示所有依赖模块加载完毕,执行该模块的回调函数,并设置依赖该模块的其他模块是否还有依赖模块未加载,若加载完毕执行回调函数;executing状态表示该模块正在执行;executed则是执行完毕,可以使用exports的API。
模块的定义
commonJS规范规定用define函数来定义一个模块。define可以接受1,2,3个参数均可,不过对于Module/wrappings规范而言,module.declare或者define函数只能接受一个参数,即工厂函数或者对象。不过原则上接受参数的个数并没有本质上的区别,只不过库在后台给额外添加模块名。
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|(?:^|[^$])\brequire\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做了hack。
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中,这是由于一个难以发现的bug:
GLOBALEVAL WORKS INCORRECTLY IN IE6 IF THE CURRENT PAGE HAS <BASE HREF> TAG IN THE HEAD
fetch模块
初始化Module对象时,状态为0,该对象对应的js文件并未加载,若要加载js文件,需要使用上节提到的request方法,但是也不可能仅仅加载该文件,还需要设置module对象的状态及其加载module依赖的其他模块。这些逻辑在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源码系列二的更多相关文章
- 手牵手,从零学习Vue源码 系列二(变化侦测篇)
系列文章: 手牵手,从零学习Vue源码 系列一(前言-目录篇) 手牵手,从零学习Vue源码 系列二(变化侦测篇) 陆续更新中... 预计八月中旬更新完毕. 1 概述 Vue最大的特点之一就是数据驱动视 ...
- Spring源码系列(二)--bean组件的源码分析
简介 spring-bean 组件是 Spring IoC 的核心,我们可以使用它的 beanFactory 来获取所需的对象,对象的实例化.属性装配和初始化等都可以交给 spring 来管理. 本文 ...
- 深入seajs源码系列三
入口方法 每个程序都有个入口方法,类似于c的main函数,seajs也不例外.系列一的demo在首页使用了seajs.use(),这便是入口方法.入口方法可以接受2个参数,第一个参数为模块名称,第二个 ...
- 深入seajs源码系列一
简述 前端开发模块化已经是大势所趋,目前模块化的规范有很多,众所周知的有commonJS,Module/Wrappings和AMD等,而且ES6也着手开始制定模块化机制的实现.类似于c/c++的inc ...
- spring源码系列(二):IOC接口设计分析
这里主要对springIOC接口体系进行简单的概述和分析,具体每个接口详细分析在下面目录: 参考内容: <Spring技术内幕:深入解析 Spring架构与设计原理> 和 <Spri ...
- 框架源码系列二:手写Spring-IOC和Spring-DI(IOC分析、IOC设计实现、DI分析、DI实现)
一.IOC分析 1. IOC是什么? IOC:Inversion of Control控制反转,也称依赖倒置(反转) 问题:如何理解控制反转? 反转:依赖对象的获得被反转了.由自己创建,反转为从IOC ...
- 手牵手,从零学习Vue源码 系列一(前言-目录篇)
系列文章: 手牵手,从零学习Vue源码 系列一(前言-目录篇) 手牵手,从零学习Vue源码 系列二(变化侦测篇) 手牵手,从零学习Vue源码 系列三(虚拟DOM篇) 陆续更新中... 预计八月中旬更新 ...
- Spring源码系列(三)--spring-aop的基础组件、架构和使用
简介 前面已经讲完 spring-bean( 详见Spring ),这篇博客开始攻克 Spring 的另一个重要模块--spring-aop. spring-aop 可以实现动态代理(底层是使用 JD ...
- Spring源码系列(四)--spring-aop是如何设计的
简介 spring-aop 用于生成动态代理类(底层是使用 JDK 动态代理或 cglib 来生成代理类),搭配 spring-bean 一起使用,可以使 AOP 更加解耦.方便.在实际项目中,spr ...
随机推荐
- Ubuntu14.04或16.04下Hadoop及Spark的开发配置
对于Hadoop和Spark的开发,最常用的还是Eclipse以及Intellij IDEA. 其中,Eclipse是免费开源的,基于Eclipse集成更多框架配置的还有MyEclipse.Intel ...
- 初步认识Less
LESS 是一个流行的样式表语言,它提供了 CSS3 也未曾实现的多种功能,让您编写 CSS 更加方便,更加直观.LESS 已经被广泛使用在多种框架中 ( 例如:BootStrap).本文将介绍 LE ...
- Linux中bashshell的一些知识
数据流重导向 重导向redirect:就是将当前的所得数据输出到其他地方: 三种输出输入的状况,分别是: -标准输入stdin:代码为0:使用<或<< -标准输出stdout:代码为 ...
- CentOS 7 安装RabbitMQ 3.3
1.安装erlang 语言环境 安装依赖文件 #yum install ncurses-devel 进入 http://www.erlang.org/download.html 选择源文件下载 wge ...
- 让Response.Redirect页面重定向更有效率
用 Redirect 方法可将浏览器重定向到另一个 URL,而不是将内容发送给用户. 这里有一篇文章介绍使用Redirect<Using Response.Redirect Effectivel ...
- ASP.Net MVC开发基础学习笔记:一、走向MVC模式
一.ASP.Net的两种开发模式 1.1 ASP.Net WebForm的开发模式 (1)处理流程 在传统的WebForm模式下,我们请求一个例如http://www.aspnetmvc.com/bl ...
- iconfont的蜕化操作
很多国外的网站,访问的时候可以看到,页面先是大面积白一下,然后恢复正常.原因是网页上用到了 webfont,这些页面很多情况都是直接引用 google 的 webfont 地址,中华大局域网下,由于网 ...
- 《HiWind企业快速开发框架实战》(3)使用HiWind创建和管理菜单
<HiWind企业快速开发框架实战>(3)使用HiWind创建和管理菜单 关于HiWind HiWind企业快速开发框架,是基于.NET+EasyUi(支持各种前端扩展,后面将扩展Boot ...
- 备忘-Sql server Timeout expired 超时时间已到. 达到了最大池大小 错误及Max Pool Size设置
select * from sysprocesses where dbid= db_id('数据库名') 通过此语句可查看目前所有的连接进程 不够了就必须设置Max Pool Size,理论最大值为3 ...
- SQL Server 的 Statistics 簡介
當你要清空「資料表(table)」,或倒入大量「資料(data;record)」,或公司「資料庫(database)」改用新版本要資料大搬家…等情形,不只是要重建「索引(index)」,還應要重建或更 ...