seajs源码分析(一)---整体结构以及module.js
1,seajs的主要内容在module.js内部,最开始包含这么几个东西
var cachedMods = seajs.cache = {}
var anonymousMeta var fetchingList = {}
var fetchedList = {}
var callbackList = {}
还有几个状态:
var STATUS = Module.STATUS = {
// 1 - `module.uri` 获取中
FETCHING: 1,
// 2 - 模块已经存储在了cachedMods对象中
SAVED: 2,
// 3 - 模块依赖加载中
LOADING: 3,
// 4 - 模块已经加载完成,准备执行
LOADED: 4,
// 5 - 模块执行中
EXECUTING: 5,
// 6 - 模块已执行完成
EXECUTED: 6
}
2,页面中主要部分是一个Module类
function Module(uri, deps) {
this.uri = uri
this.dependencies = deps || []
this.exports = null
this.status = 0 // 依赖于本模块的模块
this._waitings = {} // 本模块未加载完的依赖数
this._remain = 0
}
3,大体结构如上,具体实现的话得先从seajs的入口seajs.use说起:
//可以看到,ids实际上是在preload的模块全部开始加载后才开始加载的
seajs.use = function(ids, callback) {
Module.preload(function() {
Module.use(ids, callback, data.cwd + "_use_" + cid())
})
return seajs
}
seajs是个什么对象?
var seajs = global.seajs = {
// The current version of Sea.js being used
version: "@VERSION"
}
global的传入是这样的:
(function(global, undefined) { // 在seajs多次加载时避免冲突
if (global.seajs) {
return
} })(this);
跟jquery很像,只是传入的是this,应该是为了兼容node等其他环境,在浏览器内,global就是window
4,module.preload
Module.preload = function(callback) {
var preloadMods = data.preload
var len = preloadMods.length if (len) {
Module.use(preloadMods, function() {
// 移除已经加载好的预加载模块
preloadMods.splice(0, len) // 允许预加载模块再次定义预加载模块(没用过……)
Module.preload(callback)
}, data.cwd + "_preload_" + cid())
}
else {
callback()
}
}
实际上它的过程是一直不断的读取需要预加载的内容,最后调用callback。
preload的内容,是从data.preload读取来的,在config.js文件内部:
data.preload = (function() {
var plugins = [] // 这段代码获取url?后面的字段,并把"seajs-xxx"转换成"seajs-xxx=1"
// 注意: 在uri或cookie内使用"seajs-xxx=1"标志来预加载"seajx-xxx"文件
var str = loc.search.replace(/(seajs-\w+)(&|$)/g, "$1=1$2") // 在字符串后面连接上cookie这一大段字符串
str += " " + doc.cookie // 将"seajs-xxx=1"的"seajs-xxx"存入plugins数组中,并返回
str.replace(/(seajs-\w+)=1/g, function(m, name) {
plugins.push(name)
}) return plugins
})()
这段代码是preload的默认字段,另外里面有几个变量:
var data = seajs.data = {}
var doc = document
var loc = location
var cwd = dirname(loc.href)
var scripts = doc.getElementsByTagName("script")
preload还有一部分是我们自己配置的,在config.js文件内,还有一段这样的代码:
seajs.config = function(configData) { //configData就是我们的所有配置,遍历之
for (var key in configData) {
var curr = configData[key]//存储当前key的配置信息
var prev = data[key] // 如果之前有调用过seajs.config函数进行配置,那么把这些配置信息内的对象合并起来
if (prev && isObject(prev)) {
for (var k in curr) {
prev[k] = curr[k]
}
}
else {
//如果是数组的话,就concat起来
if (isArray(prev)) {
curr = prev.concat(curr)
}
//确认base是个绝对路径
//如果base不是以"/"结尾,加上"/",addBase的作用是将相对路径转换成绝对路径
else if (key === "base") {
(curr.slice(-1) === "/") || (curr += "/")
curr = addBase(curr)
} data[key] = curr
}
}
//emit是触发事件的函数
emit("config", configData)
return seajs
}
至此,data这个对象是什么,config存哪了,就都清楚了,另外,其实在config.js内还有这样一个默认值:
//这段正则用来匹配path/seajs/xxx/xx/xx/...的path/部分然后存到data.base中
var BASE_RE = /^(.+?\/)(\?\?)?(seajs\/)+/ data.base = (loaderDir.match(BASE_RE) || ["", loaderDir])[1]
data.dir = loaderDir
data.cwd = cwd
data.charset = "utf-8"
我们看到,module.preload调用了module.use,那module.use是个什么东西呢?
5,Module.use
Module.use = function (ids, callback, uri) {
var mod = Module.get(uri, isArray(ids) ? ids : [ids]) //这个绑定在mod上的callback会在onload后触发,作用是获取各个依赖模块的输出,并且调用传入的callback,这个callback起真正作用的实际上是从seajs.use中传入的那个函数(如果有的话),因为Module.preload调用Module.use时传入的callback效果实际上在不断的调用自身,直到没有需要预加载的模块。实际效果是seajs.use(['a', 'b', 'c'], function(a, b, c){})
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.get, mod.load, mod.exec, mod.resolve
6,Module.get
//如果存在则获取该Module对象,否则以模块url为id,存入cachedMods这个对象中,实际上所有新建的Module对象都存在这个对象中
Module.get = function(uri, deps) {
return cachedMods[uri] || (cachedMods[uri] = new Module(uri, deps))
}
7,Module.prototype.resolve(mod.resolve)
//该方法获取所有依赖的完成id,然后返回,调用了Module.resolve方法
Module.prototype.resolve = function() {
var mod = this
var ids = mod.dependencies
var uris = [] for (var i = 0, len = ids.length; i < len; i++) {
uris[i] = Module.resolve(ids[i], mod.uri)
}
return uris
}
可以看到,该方法依赖于Module.resolve方法
8,Module.resolve
Module.resolve = function(id, refUri) {
// 为插件触发resolve事件比如text插件
var emitData = { id: id, refUri: refUri }
emit("resolve", emitData) //id2Uri函数将id(比如相对路径)转换成完整的路径,作为存储id
return emitData.uri || id2Uri(emitData.id, refUri)
}
9,Module.prototype.load(mod.load)
这个方法是重头戏,而且有点长
Module.prototype.load = function() {
var mod = this // 如果已经加载或正在被加载,不要重复加载
if (mod.status >= STATUS.LOADING) {
return
} mod.status = STATUS.LOADING // 为插件触发load事件,比如combo插件,获取依赖,存到uris变量中
var uris = mod.resolve()
emit("load", uris) //获取依赖数
var len = mod._remain = uris.length
var m // 依赖模块初始化,并给每个依赖的模块_waiting赋初值
for (var i = 0; i < len; i++) {
m = Module.get(uris[i]) //没加载完的模块,添加_waiting
if (m.status < STATUS.LOADED) {
// 有可能重复依赖?
m._waitings[mod.uri] = (m._waitings[mod.uri] || 0) + 1
}
else {
mod._remain--
}
} //依赖加载完,则该模块加载完成,触发onload
if (mod._remain === 0) {
mod.onload()
return
} // 开始加载
var requestCache = {} for (i = 0; i < len; i++) {
//事实上在resolve后在cachedMods对象中都存储了模块id
m = cachedMods[uris[i]] //如果自身没加载,则加载该模块,调用了m.fetch,事实上fetch并没有真正执行内容获取,而仅仅是绑定发送请求的方法,requestCache的作用,是为下面的for循环做准备,具体看fetch。
if (m.status < STATUS.FETCHING) {
m.fetch(requestCache)
}
else if (m.status === STATUS.SAVED) {
m.load()
}
} // 这里才是真正执行该方法,这是为了解决 IE6-9的缓存bug , 具体请看github上的Issues#808
for (var requestUri in requestCache) {
if (requestCache.hasOwnProperty(requestUri)) {
requestCache[requestUri]()
}
}
}
该方法内部调用了mod.fetch,mod.onload方法,除此之外,还有一个mod.exec方法在前面被Module.use依赖
10,Module.prototype.fetch(mod.fetch)
Module.prototype.fetch = function(requestCache) {
var mod = this
var uri = mod.uri mod.status = STATUS.FETCHING // 为combo等插件触发fetch事件
var emitData = { uri: uri }
emit("fetch", emitData)
var requestUri = emitData.requestUri || uri // 如果uri不符合规范或者已经加载好,直接加载依赖
if (!requestUri || fetchedList[requestUri]) {
mod.load()
return
} //如果使用了combo插件,且在加载队列中有该页面的uri,只需要将该模块推入callback中,等待触发onload就是了
if (fetchingList[requestUri]) {
callbackList[requestUri].push(mod)
return
}
//否则,推入fetchingList,并初始化该uri的callbackList
fetchingList[requestUri] = true
callbackList[requestUri] = [mod] // 为text等插件触发request事件
emit("request", emitData = {
uri: uri,
requestUri: requestUri,
onRequest: onRequest,
charset: data.charset
}) //如果还没有请求,且传入了requestCache对象,则将uri和发送函数对应推入该对象中,如果没有传入,直接启动发送
if (!emitData.requested) {
requestCache ?
requestCache[emitData.requestUri] = sendRequest :
sendRequest()
}
//此处调用了request方法,作用是将uri作为一个link或者script标签 //的链接,onrequest函数是onload的回调函数,启动发送后将script标签删除
function sendRequest() {
request(emitData.requestUri, emitData.onRequest, emitData.charset)
} function onRequest() {
//完成后将该模块在fetching列表中删除,推入fetched列表
delete fetchingList[requestUri]
fetchedList[requestUri] = true // 此时模块运行,实际会有一个调用Module.define的过程,如果该模块是匿名模块,define会给anonymousMeta赋值
//如果是匿名模块,则调用Module.save
if (anonymousMeta) {
Module.save(uri, anonymousMeta)
anonymousMeta = null
} // 该requestUri的所有模块接收完成,调用其load方法
var m, mods = callbackList[requestUri]
delete callbackList[requestUri]
while ((m = mods.shift())) m.load()
}
}
该方法依赖于Module.save方法,实际上隐式依赖于Module.define方法
11,Module.define
Module.define = function (id, deps, factory) {
var argsLen = arguments.length //这一长串ifelse逻辑是为了解析传入的参数
// 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
}
} // 如果没有申明依赖,并且传入了函数,则调用parseDependencies来解析该函数内容
if (!isArray(deps) && isFunction(factory)) {
deps = parseDependencies(factory.toString())
} //此处尝试给模块命名id,如果没有成功,则生成匿名模块,等待在后期触发onrequest的时候在将其使用Module.save命名
var meta = {
id: id,
uri: Module.resolve(id),
deps: deps,
factory: factory
} // 这个是尝试在ie6-9下面给uri复制,getCurrentScript为获取当前的script节点
if (!meta.uri && doc.attachEvent) {
var script = getCurrentScript() if (script) {
meta.uri = script.src
} } // 为nocache插件触发define事件
emit("define", meta) //如果存在uri,则存储,否则,定义为匿名模块
meta.uri ? Module.save(meta.uri, meta) :
anonymousMeta = meta
}
这里,实际上如果在定义模块的时候没有使用数组存入依赖,很有可能会成为一个匿名模块,但是依赖它的模块会在require里面申明它的一个uri,并在load的时候调用Module.get使用该uri为该匿名模块创建了一个Module对象,所以在启动onrequest之前,若有匿名模块,则肯定是拥有该onrequest函数的模块,然后会在函数内部给该匿名模块命名。此处使用了Module.save。
12,Module.save
Module.save = function(uri, meta) {
var mod = Module.get(uri) // 确认模块没被存储,如果是匿名模块,则meta.id是没有的
if (mod.status < STATUS.SAVED) {
mod.id = meta.id || uri
mod.dependencies = meta.deps || []
mod.factory = meta.factory
mod.status = STATUS.SAVED
}
}
13,Module.prototype.onload(mod.onload)
//只有在所有依赖加载完后,才会调用onload
Module.prototype.onload = function() {
var mod = this
mod.status = STATUS.LOADED //这不就是需要调用module.use内绑定的callback么
if (mod.callback) {
mod.callback()
} //检测依赖该模块的模块,_remain相应减少,如果为0,则调用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()
}
}
} // 释放内存
delete mod._waitings
delete mod._remain
}
14,Module.prototype.exec(mod.exec)
这个放到最后,是因为一个模块在所有工作做完后,才会调用该方法
Module.prototype.exec = function () {
var mod = this // 避免重复计算
if (mod.status >= STATUS.EXECUTING) {
return mod.exports
} mod.status = STATUS.EXECUTING // 这里创建了最常用的require函数
var uri = mod.uri //返回依赖模块的计算值exports
function require(id) {
return Module.get(require.resolve(id)).exec()
} require.resolve = function(id) {
return Module.resolve(id, uri)
}
//异步加载,触发callback
require.async = function(ids, callback) {
Module.use(ids, callback, uri + "_async_" + cid())
return require
} var factory = mod.factory var exports = isFunction(factory) ?
factory(require, mod.exports = {}, mod) :
factory
//有可能在factory是通过给exports复制来传递结果的
if (exports === undefined) {
exports = mod.exports
} // 出错,触发error事件(没有结果或者该模块是CSS文件)
if (exports === null && !IS_CSS_RE.test(uri)) {
emit("error", mod)
} // 释放内存
delete mod.factory mod.exports = exports
mod.status = STATUS.EXECUTED // 触发excec事件
emit("exec", mod)
//最终计算出了该模块的返回值
return exports
}
seajs源码分析(一)---整体结构以及module.js的更多相关文章
- JavaScript 模块化及 SeaJs 源码分析
网页的结构越来越复杂,简直可以看做一个简单APP,如果还像以前那样把所有的代码都放到一个文件里面会有一些问题: 全局变量互相影响 JavaScript文件变大,影响加载速度 结构混乱.很难维护 和后端 ...
- ABP源码分析三:ABP Module
Abp是一种基于模块化设计的思想构建的.开发人员可以将自定义的功能以模块(module)的形式集成到ABP中.具体的功能都可以设计成一个单独的Module.Abp底层框架提供便捷的方法集成每个Modu ...
- 【Seajs源码分析】1. 整体架构
seajs是一个非常流行的模块开发引擎,目前项目中使用比较多,为了深入了解已经改进seajs我阅读了他的源码,希望对自己的代码生涯能有所启发. 本文说介绍的是指seajs2.3.3版本. 首先seaj ...
- jjQuery 源码分析1: 整体结构
目前阅读的是jQuery 1.11.3的源码,有参考nuysoft的资料. 原来比较喜欢在自己的Evernote上做学习基类,并没有在网上写技术博客的习惯,现在开始学习JS的开源代码,想跟大家多交流, ...
- seajs源码分析
seajs主要做了2件事 1.定义什么是模块,如何声明模块:id.deps.factory.exports ----define=function(id,deps,factory){return ex ...
- 【Seajs源码分析】3. 工具方法2
util-request.js 动态加载模块 /** * util-request.js - The utilities for requesting script and style files * ...
- 【Seajs源码分析】2. 工具方法1
Sea.js: var seajs = global.seajs = { // The current version of Sea.js being used version: "@VER ...
- Kibana6.x.x源码分析--Error: $injector:nomod Module Unavailable
首先我的依赖注入写法如下: 由于是新手,比对着Kinaba中已有的插件,进行摸索开发,但运行代码后,发现在注册依赖的时候报错了.如下截图所示: 然后根据提示:http://errors.angular ...
- jQuery 源码分析和使用心得 - core.js
core是jQuery的核心内容, 包含了最基础的方法, 比如我们常用的 $(selector, context), 用于遍历操作的 each, map, eq, first 识别变量类型的 isAr ...
随机推荐
- uva 579 ClockHands 几何初接触 求时针与分针的夹角
貌似是第一次接触几何题... 求时针与分针的夹角,这不是小学生的奥数题么.我小时候也想过这问题的. 每过一小时时针走1/12*360=30度,每过一分钟时针走1/60*30=0.5度,分针走1/60* ...
- jdk tomcat maven svn plsql客户端 环境变量配置整理
1 jdk 新建: 1.JAVA_HOME ----- C:\Program Files\Java\jdk1.7.0 2.CLASSPATH ------ .;%JAVA_HOME%\li ...
- Win10下安装msi程序包时报2503、2502错误问题及其解决办法
Win10系统下安装TortoiseSvn.Node.js时(.msi后缀的安装文件),在点击安装时老是提示2503,2502错误,因此无法安装上. 搜索了下一般都提到是权限不够引起的该问题.但是右键 ...
- 使用DbTableColumnWeb项目简要
项目说明 环境:Vs2013 .Net4.5 MVC5 主要功能:直观编辑表字段说明:生成表对应的实体类:生成数据库表文档说明: 初衷:在开发过程中,经常会遇到同事询问表字段含义.手动编写表对应的实体 ...
- GitHub设置使用SSH Key,用TortoiseGit进行Clone仓库
GitHub设置使用SSH Key的好处就是可以使用SSH连接,并且提交代码的时候可以不用输入密码,免密提交. 生成SSH Key 这里我们使用PuTTYgen来生成公钥(Public Key),私钥 ...
- DataSet转换成List<>
方法一: //DataSet转换成List<ArticleInfo> public List<ArticleInfo> GetArticleList(DataSet ds) { ...
- .Net Core + DDD基础分层 + 项目基本框架 + 个人总结
为什么要写这篇文章 1,在大半年前,公司开发任务比较轻松,自己不知道干什么,但是又想要学习技术,比较迷茫,后面我接触到了博客园,看了一个帖子,深有感触,我当时不知道学习什么,于是我信息给他,他居然回复 ...
- java项目部署在linux上
在将java项目在linux上之前我们需要准备一下,首先是 一个打成war包的java项目,一个contes的系统,还有就是tomcat和jdkl的压缩包,可以在官网下载, jdk安装 先卸载cent ...
- Django(完整的登录示例、render字符串替换和redirect跳转)
day61 1. 登录的完整示例 复习: form表单往后端提交数据需要注意哪三点: 五一回来默写 <-- 谁写错成from谁 ...
- php主要用于哪几方面
1,服务端脚本,网站和web应用程序,web服务器,php解析器,web浏览器 2,命令行脚本 3,编写桌面应用程序