New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
实例解析 SeaJS 内部执行过程 - 从 use 说起 #308
Comments
感谢 @pigcan ,太给力了! |
建议除SeaJS之外的文件都异步加载。鸡蛋里挑骨头 >.< |
mark~~ |
牛B,mark先 |
标记下,回来认真看~~~ |
mark,i will go back |
看了这里发现:原来preload并没有预加载啊,更像基础依赖必须加载。如果a->b->c->jQuery的话,配置了preload:jQuery,jQuery确实在加载树中提前了,但是与通常的预加载不太一样。 |
花了一天时间,完整看了一遍,优雅的代码令人印象深刻~ 各种回调脑子不够用,只能画着流程图方便记忆。 不晓得 @pigcan 写这个花了多长时间~ 佩服! |
看晕了,什么时候才能写出这样的代码 |
好长,得仔细研读 |
不错。。需要反复看 |
mark |
先下载a.js,后下载jquery.js,那哪个先下载完,就先执行吗?假如a.js很大,jquery.js先下载完,那么是不是浏览器先执行jquery.js?谢谢 |
先执行定义,真正执行是你程序的逻辑。 |
mark |
膜拜下。 |
今天才看到这篇强文 马克土温一下 |
看来是需要反复阅读才能很好得理解 |
mark |
先编译a.js, 然后会执行a.js中function,这时因为依赖b.js就会去执行b.js中的function, 那我想问jquery.js是在a,b,c都执行完成后,再编译,执行function吗? |
学习 |
正在看seajs源码,一头雾水。 竟然找到这篇文章,真棒! 支持开源社区 |
mark~学习了 |
厉害,还是很多看不懂 |
下面将通过一个例子来说明seajs内部是怎么运行的
代码中文注释已经在7月30日以issue的形式提交到seajs的github上
参考地址为:#305
假设1:
seajs为同步引入 并且 config的配置如下 ,并且在页面中head区写上
<script src='./index.js'></script>
index.js :
a.js :
b.js :
c.js :
由上述可知 a 依赖 b ,b依赖c
首先将会执行seajs本身,在这个过程中将会定义其中一些全局的方法,seajs多版本的容错等等 。
当程序进入到
index.js
seajs将调用use方法 :
use方法
将会从我们的config配置信息中查看 ,是否有预先需要被加载的模块,这边config中我们并没有相关设置,所以将直接 执行globalModule._use(ids, callback)
说明: globalModule 为seajs初始化时候Module的实例
var globalModule = new Module(util.pageUri, STATUS.COMPILED)
util.pageUri
为 页面地址此时
ids -> ['./a','jquery']
callback -> function(a,$){var num = a.a;$('#J_A').text(num);}
接下来将调用
globalModule._use(ids, callback)
这里有需要解释
resolve
和_load
两个函数此时 传递给 resolve的两个参数分别为
ids -> ['./a','jquery']
refUri -> 页面地址 例子中 地址为 http://localhost/test/SEAJS/test.html
此时由于
ids -> ['./a','jquery']
为 数组 , 那么将通过map方法对数组每个成员都执行一次resolve方法,并返回新的数组。最终执行
Module._resolve('./a', 'http://localhost/test/SEAJS/test.html') Module._resolve('jquery', 'http://localhost/test/SEAJS/test.html')
源码中
Module._resolve = util.id2Uri
实际 Module._resolve 是 util.id2Uri的引用id2Uri('./a', 'http://localhost/test/SEAJS/test.html') - > parseAlias('./a') return ./a -> http://localhost/test/SEAJS/a -> normalize(http://localhost/test/SEAJS/a ) -> http://localhost/test/SEAJS/a .js
id2Uri('jquery', 'http://localhost/test/SEAJS/test.html') - > parseAlias('jquery') return jquery/1.7.2/jquery-debug.js -> http://localhost/test/SEAJS/lib//jquery/1.7.2/jquery-debug.js ->normalize(http://localhost/test/SEAJS/lib//jquery/1.7.2/jquery-debug.js) -> http://localhost/test/SEAJS/lib/jquery/1.7.2/jquery-debug.js
最终
var uris = resolve(ids, this.uri)
中
uris
为['http://localhost/test/SEAJS/a.js','http://localhost/test/SEAJS/lib/juqery/1.7.2/juqery-debug.js']
模块路径解析已经完毕,而接下来让我们再次返回
Module_use
方法此时
uris ->['http://localhost/test/SEAJS/a.js','http://localhost/test/SEAJS/lib/juqery/1.7.2/juqery-debug.js']
callback -> function(a,$){var num = a.a;$('#J_A').text(num);}
接下来将执行
Module._load()
方法。进入到
_load(uris, callback)
方法uris ->
['http://localhost/test/SEAJS/a.js','http://localhost/test/SEAJS/lib/juqery/1.7.2/juqery-debug.js']
callback - >
其首先第一步做的事情就是 在
cachedModules[uri]
中查找 是否存在 该uri 的 存储信息,如果存在存储信息,则表明该模块已经被下载好了,但是有可能它所依赖的模块还没有下载好 ,如果没有信息则说明该模块需要被下载。关于模块状态:推荐直接阅读中文版注释代码#305 或者 玉伯的文章#303
经过第一步 我们将会得到
unLoadedUris -> ['http://localhost/test/SEAJS/a.js','http://localhost/test/SEAJS/lib/juqery/1.7.2/juqery-debug.js']
如果
unLoadedUris
为空 那么直接执行_load
的回调callback
。这里
unLoadedUris
的数组长度为2 所以 接下来 会产生两个 以 js路径为名称的闭包以http://localhost/test/SEAJS/a.js为例
接下来 : 首先会创建一个Module
cachedModules('http://localhost/test/SEAJS/a.js') = new Module('http://localhost/test/SEAJS/a.js',1)
初始化 a模块存储信息
module.status >= STATUS.FETCHED ? onFetched() : fetch(uri, onFetched)
因为此时模块并没有加载 所以 接下来将会执行
fetch(uri, onFetched)
即fetch('http://localhost/test/SEAJS/a.js',onFetched)
进入
fetch
函数 , 首先会根据请求的url
和map
去做一次匹配,如果map
中已经存在相关规则,那么替换uri为匹配规则后的uri 因为这里map并没有做任何设置,所以不会对uri做任何处理紧接着 http://localhost/test/SEAJS/a.js 会在 已请求 和 正在请求 的队列中查找 是否存在
如果不存在 那么 a.js 将被 添加到 请求队列中 其回调也将被添加到回调列表中
接下来 将会执行
Module._fetch()
在
seajs
中fetch
其实算是非常非常重要的一个函数, 他把控了资源的加载,其中内部存在了大量兼容性代码,主要有针对ie6 baseElement的问题 , oldWebkit的问题 , 以及 css 加载监听的兼容,如何兼容暂不谈,因为在以上代码中我也非常详细阐述了。首先fetch函数会查看 需要加载的资源是 css 文件还是 js 文件
这里 http://localhost/test/SEAJS/a.js 是一个脚本文件 所以这个时候会动态创建一个空的script对象 设置 charset 值
并最终执行
assetOnload(node, callback || noop)
所以接下来会执行 scriptOnload函数 最终
head.appendChild(node)
该节点将被插入到页面的head中<script charset="utf-8" async="" src="http://localhost/test/SEAJS/a.js"></script>
这个时候 js 将被正式下载 但运行将被阻塞 。
同样道理
<script charset="utf-8" async="" src="http://localhost/test/SEAJS/libs/jquery/1.7.2/jquery.js"></script>
到此 seajs.use 到此完毕 。 接下来 资源加载完毕之后将运行a.js 随后将触发onload 事件
a.js运行之时 会 触发
define
的执行接着分析 , 那么 接下来将会执行
define
,define
首先会对factory
执行一个判断 ,判断它是否为一个函数(原因是因为define
内也可以包括文件,对象)如果是函数 , 那么 就会 通过
factory.toString()
,得到函数 并通过 正则匹配 得到 a.js的依赖 并 把依赖保存在deps
中对于 a.js 而言 它的依赖 是 b.js 所以
deps
为['./b']
并对 a.js 的信息进行保存
var meta = { id: id, dependencies: deps, factory: factory }
针对a.js
meta = { id : undefined , dependencies : ['./b'] , factory : function(xxx){xxx}}
在 ie 6-9 浏览器中可以拿到当前运行js的路径 但是在标准浏览器中 这不可行 所以暂时先把 元信息赋值给
anonymousModuleMeta = meta
define
执行完毕后onload
事件 将被 触发这个时候将直接执行
Module._fetch(requestUri,callback,config.charset)
中的callback
在
callback
执行过程中 , 修改当前回调 模块的 状态值 , 将其设置为module.status = STATUS.FETCHED
因为在 ie 6-9 浏览器中可以拿到当前运行js的路径 但是在标准浏览器中 这不可行 所以暂时先把 元信息赋值给
anonymousModuleMeta = meta
所以接下来 判断 是否存在
anonymousModuleMeta
判断存在 其值为 :
{ id : undefined , dependencies : ['./b'] , factory : function(xxx){xxx}}
接下来将触发
save(uri,meta)
该函数首先会判断其是否存在
cachedModules
中 ,如果不存在将新建一个Module
实例这边模块 a.js 已经存在在
cachedModules
具体信息为:接下来 将 设置
module
更多的信息 , 这些信息来源于 a.jsdefine
执行过程中所产生的一些信息 。设置a模块的id , 依赖关系 (依赖关系将通过resolve,统一转换成绝对路径),factory , 模块状态为
module.status = STATUS.SAVED
经过一系列转换后 a 的模块信息为 :
然后清空
anonymousModuleMeta
信息 , 避免之后加载信息出乱 并清空firstModuleInPackage = null
firstModuleInPackage
意在 #137Handle un-correspondence case
信息不配对的情况
再接下来 将统一 执行 回调队列 callbackList 中的 a.js 所 对应的回调。
callbackList
为:所以 a.js 的
callback
为onFetched
, 因为onFteched
存在与闭包之中,而闭包保存了之前的状态,所以函数将在之前状态的情况下 继续执行接下来 把 a.js 的存储信息直接从
cachedModules['http://localhost/test/SEAJS/a.js']
获得 ,内容如下:接下来
getPureDependencies(module)
方法 将执行 //获取纯粹的依赖关系 , 得到不存在循环依赖关系的依赖数组这些函数的意义就在与 剔除 a.js 中 是否 存在 对于自身的 依赖
经过这一步
var deps = getPureDependencies(module)
依赖关系为
紧接着 这个时候会
对模块a所依赖的b.js 执行
_load()
这个时候就b.js 会再次重复 a.js 所执行的一切 。
此时会先执行 jq
define
因为jq对其他无依赖所以 正式返回module
并设置了其状态为ready
再接下来将执行 b 的
define
,define
首先会对factory
执行一个判断 ,判断它是否为一个函数(原因是因为define
内也可以包括文件,对象)如果是函数 , 那么 就会 通过
factory.toString()
,得到函数 并通过 正则匹配 得到 b.js的依赖 并 把依赖保存在 deps 中对于 b.js 而言 它的依赖 是 c.js 所以
deps
为['./c']
并对 b.js 的信息进行保存
var meta = { id: id, dependencies: deps, factory: factory }
针对b.js
meta = { id : undefined , dependencies : ['./c'] , factory : function(xxx){xxx}}
在 ie 6-9 浏览器中可以拿到当前运行js的路径 但是在标准浏览器中 这不可行 所以暂时先把 元信息赋值给
anonymousModuleMeta = meta
执行b的
define
执行完毕后onload
事件 将被 触发之后再会加载c.js , 执行和 b.js加载一致 , 这个时候加载完c.js
执行c的define 继而
onload
事件被触发 由于c无依赖 c 状态 被 修改为 ready进而 b 修改为
ready
a 修改为
ready
当所有模块都为ready之后 开始 执行回调
回调过程中开始编译各个模块
首先将编译 a.js
编译代码和过程省略,因为结合源代码 这一块相对没有那么绕 所以不做特别多的说明。
假设模块 a 的 factory 执行时,假设a内部含有b的依赖,因此也会触发模块 b 的执行,模块 b 有可能还有依赖模块,比如 c,这时会继续触发模块 c 的执行,这就形成一个 stack:
// 这个信息,就存储在内部变量 compileStack 里。
/*
模块 a 开始执行
模块 b 开始执行
模块 c 开始执行
模块 c 执行完毕
模块 b 执行完毕
模块 a 执行完毕
*/
等编译都完成
这里的
args
为:也就是说这里的
把a的module.exports 和 jquery 的 module.exports 传递 给 a , $
The text was updated successfully, but these errors were encountered: