avalon模块加载
avalon自己实现了一套可被替换的模块加载系统(AMD loader)。具体什么是AMD loader可参看doJo官方博客关于AMD loader的翻译讲解,看完之后,再继续往下看,会比较清楚些。
模块加载配置
模块加载系统可替换原理参见javascript 闭包暴露句柄,可替换的前提是前面加载的amd loader文件将define
和require
函数暴露给window对象。具体如何替换可执行
avalon.config.plugins.loader(false)//不推荐,但有效
//或者
avalon.config({loader:false})
当然了,如果更改了amd loader的话,可不要用avalon.config.plugins.text|css(url)
了。
avalon.config.nocache可用来配置是否去除缓存,测试的时候
avalon可通过配置avalon.config.[shim|paths],来加载一些不符合amd加载规则的数据源,例如加载csdn上的jquery等。
paths意味着地址。这里需要注意的是,paths的设定时,需要:
,也就是全路径,不要像这样www.abc.com
,而是http:www.abc.com
。
shim则是一个object。
//shim
{//参考下面的数据结构
$moduleId:{
src:$url
exports:$fn|$string // string说明加载的js文件会在window下存放引用,例如jquery, "$"==>window["$"]
deps:...
}
}
模块加载数据结构
模块加载所用到的数据先放出来,方便源码阅读推断。
//avalon.modules 存储模块信息
{
$moduleId:{//默认值
id:$string//随机生成或指定
exports:$object|$string,//暴露出引用
deps:$array|$object,//依赖的部件 为$object时,会添加当前部件的加载状态,依赖的deps 又都会在avalon.modules下有各自的状态记录
state:$number//加载状态 2:加载过 1:正在加载
factory:$fn//模块本身,会被存放到factorys中,他的this指代window对象
args:$array//保存依赖模块的返回值
}
}
//factorys数组下的factory函数
factory.delay=$fn//检查依赖,延迟加载
factory.id=$string//用来debug用的
require实现
require(deps, factory, baseUrl)
require 默认id的生成是通过"callback"+setTimeout("1")
来实现UniId。
require 函数会先调用loadSource
遍历deps是否都加载完,没加载完的,会根据加载文件类型调用不同的函数去完成异步加载,并将其要加载的依赖放入loadings
存储,通过各种合适的时机(例如,完成一个js模块的加载时)调用checkDeps
来将loadings
处理掉,更新依赖状态,进而加载自己。
这里需要注意的是,factory的this,指代window对象(不知为何)。
动态加载
动态加载划分为三种:js、css、text(文本),分别对应一个函数。并对url #?
后缀进行删除。
加载css时,前面添加 css! 。
css加载
css分两种加载,先介绍一个简单的,复杂的则是通过AMD loader加载的。加载方式是构造<link href='...' rel='stylesheet' id='$sepcialURL'>
并插入到head最上边。这个地方有个疑问是,css样式覆盖会按照后来覆盖前面的样式来吗?有待测试。
text加载
text的加载是通过ajax做的,并将结果赋值给exports,保存在avalon.modules下面。
js加载
js加载会有对前面提到的avalon.config.shim进行处理(说实在的这个处理只有看完源码后方能使用无误。纠结~),具体的加载功能由loadJS
函数来完成。loadJS
通过创建<script class='$日期'>
节点并插入到head上来加载js文件,当加载完成后,会将class属性改写掉,并将define
函数定义的factory函数和回调函数执行一下。如果加载失败了,写个日志呗。
define实现
define(id, deps, factory)
define的实现会牵扯到用户传参的循环依赖,例如:加载a需要先加载b,加载b又需要先加载a。所以上面的数据结构$moduleId
和deps
就变的十分有用了。根据这个参数的关系,就可找到是否存在循环依赖。
define还会调用require函数来实现JS文件的加载。
define还是require函数的属性值哦。innerRequire.define=function(){...}
ready!
ready!
是avalon内置模块,主要用来等待游览器扫完dom树之后,再执行。avalon.ready
也就是基于此的,上下avalon.ready
源码。
avalon.ready = function(fn) {
innerRequire("ready!", fn)// require('ready!',fn);使用innerRequire是预防avalon的AMD loader被替代掉}
函数介绍
checkDeps
checkDeps
是用来检查存放在loadings模块的依赖是否都完成加载,如果依赖都完成了加载,而自己没有,则去执行自己的factory工厂函数。这个函数用到了loop来跳过双for循环break问题,可以拿来借鉴下。
function checkDeps() {
//检测此JS模块的依赖是否都已安装完毕,是则安装自身
//只要loadings有一个
loop: for (var i = loadings.length, id; id = loadings[--i];) {
var obj = modules[id],
deps = obj.deps
for (var key in deps) {
if (ohasOwn.call(deps, key) && modules[key].state !== 2) {
continue loop
}
}
//如果deps是空对象或者其依赖的模块的状态都是2
if (obj.state !== 2) {
loadings.splice(i, 1) //必须先移除再安装,防止在IE下DOM树建完后手动刷新页面,会多次执行它
fireFactory(obj.id, obj.args, obj.factory)
checkDeps() //如果成功,则再执行一次,以防有些模块就差本模块没有安装好
}
}
}
获取当前scriptURL
源码很棒且注释很全,直接上源码。作者司徒正美还在博文getBasePath 函数中进行了详细的讲解,有兴趣的可以去了解下。
//getCurrentScript(true);
function getCurrentScript(base) {
// 参考 https://github.com/samyk/jiagra/blob/master/jiagra.js
var stack
try {
a.b.c() //强制报错,以便捕获e.stack
} catch (e) { //safari的错误对象只有line,sourceId,sourceURL
stack = e.stack
if (!stack && window.opera) {
//opera 9没有e.stack,但有e.Backtrace,但不能直接取得,需要对e对象转字符串进行抽取
stack = (String(e).match(/of linked script \S+/g) || []).join(" ")
}
}
if (stack) {
/**e.stack最后一行在所有支持的浏览器大致如下:
*chrome23:
* at http://113.93.50.63/data.js:4:1
*firefox17:
*@http://113.93.50.63/query.js:4
*opera12:http://www.oldapps.com/opera.php?system=Windows_XP
*@http://113.93.50.63/data.js:4
*IE10:
* at Global code (http://113.93.50.63/data.js:4:1)
* //firefox4+ 可以用document.currentScript
*/
stack = stack.split(/[@ ]/g).pop() //取得最后一行,最后一个空格或@之后的部分
stack = stack[0] === "(" ? stack.slice(1, -1) : stack.replace(/\s/, "") //去掉换行符
return stack.replace(/(:\d+)?:\d+$/i, "") //去掉行号与或许存在的出错字符起始位置
}
var nodes = (base ? DOC : head).getElementsByTagName("script") //只在head标签中寻找
for (var i = nodes.length, node; node = nodes[--i]; ) {
if ((base || node.className === subscribers) && node.readyState === "interactive") {//subscribers="$"+(new Date-0)
return node.className = node.src
}
}
}
一个有趣的加载测试
这个测试时关于加载顺序的。
<!DOCTYPE html>
<html>
<head>
<script src="avalon.js"></script>
</head>
<body>
<div ms-controller="hello">
<h1>Hello, {{name}}</h1>
</div>
<script>
avalon.ready(function(){
var model = avalon.define('hello', function(vm){
vm.name = 'Avalon';
});
avalon.scan();
})
</script>
</body>
</html>
请问avalon什么时候将model刷进页面去的,为什么?和下面的代码有什么区别?
<!DOCTYPE html>
<html>
<head>
<script src="avalon.js"></script>
</head>
<body>
<div ms-controller="hello">
<h1>Hello, {{name}}</h1>
</div>
<script>
var model = avalon.define('hello', function(vm){
vm.name = 'Avalon';
});
</script>
</body>
</html>
做完这个练习,我相信你一定会对dom加载有个新的认识。
小结
整个实现,为factory函数依赖注入废了不少劲,不知道添上angularJS的annotate函数,会不会简单些。
来源:oschina
链接:https://my.oschina.net/u/1179600/blog/229414