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 ...
随机推荐
- underscore functions
// 创建一个用于设置prototype的公共函数对象 var ctor = function() {}; 1..bind(function, object, [*arguments]) :绑定fun ...
- WebApi使用JWT认证(一)
这是第一部:先实现NetFramework上的WebApi使用JWT认证 1.VS新建一个WebApi项目 2.项目右键----管理Nuget程序包----找到JWT,然后安装 3.Model文件夹下 ...
- Accepted Technical Research Papers and Journal First Papers 【ICSE2016】
ICSE2016 Accepted Paper Accepted Technical Research Papers and Journal First Papers Co-chairs: Wille ...
- linux系统编程之信号(四):alarm和可重入函数
一,alarm() 在将可重入函数之前我们先来了解下alarm()函数使用: #include <unistd.h> unsigned int alarm(unsigned int sec ...
- 安装CentOS桌面环境
CentOS 作为服务器的操作系统是很常见的,但是因为需要稳定而没有很时髦的更新,所以很少做为桌面环境.在服务器上通常不需要安装桌面环境,最小化地安装 CentOS(也就是 minimal CentO ...
- telerik:RadGrid 在表格中编辑更新数据
对于 telerik 这个框架 我也不是很熟悉 也是刚刚开始学习 有兴趣的可以去官网看下 https://www.telerik.com/ 啥也不多说 直接上代码 首先是 telerik:RadGr ...
- AspNetCore Mvc 自定义中间件认证
AspNetCore Mvc 自定义中间件认证 实现控制器访问验证和拦截. 1.注册政策. 例如: services.AddAuthorization(options => { options. ...
- win10 打开sql server配置管理器
win10 安装 sql server之后无法在开始菜单找到“sql server 配置管理器(SQL server configuration manager 1)在开始菜单中,无法找到 配置管理器 ...
- openstack 实用命令
port 1.创建port(create) i.随机ip openstack port create --network public --fixed-ip subnet=sub-public '' ...
- Android------------fragment数据传递
一.activity向fragment的数值之间的传递 关键点:fragment.setArguments(bundle);---->activity发出的信息 Bundle bund ...