seajs用于管理和加载js模块化(其实也可以用来加载css的),他的api非常简单,重要的就use,define等几个,源码一共也不到1000行。
源码目录结构如下:
config.js:seajs.config()api,data配置变量
intro.js:全局window注入,闭包开始
module.js:核心api,包管理api,包括seajs对象下的use,require和module加载的各种实现api
outro.js:传入window,闭包结束
sea.js:seajs对象申明
util-deps.js:处理依赖的工具函数(匹配factory中的require,获取依赖)
util-events.js:seajs中的事件触发
util-lang.js:类型判断工具函数
util-path.js:路径处理工具函数
util-request.js:处理加载工具函数
其中最核心的是seajs和modulejs两个文件。
接下来,对每个进行单独解读。
intro和outro比较简单,跟jq等类库类似,申明自执行闭包函数,传入window和undefined对象,为了压缩和效率。
util-deps.js代码只有20行,其中最重要的是一个正则
var REQUIRE_RE = /"(?:\\"|[^"])*"|‘(?:\\‘|[^‘])*‘|\/\*[\S\s]*?\*\/|\/(?:\\\/|[^\/\r\n])+\/(?=[^\/])|\/\/.*|\.\s*require|(?:^|[^$])\brequire\s*\(\s*(["‘])(.+?)\1\s*\)/g
这个正则其实就是用来匹配require关键字,并找到require对应的模块提取出来,供加载使用。这个正则表达式非常巧妙,真正匹配的在最后一部分
(?:^|[^$])\brequire\s*\(\s*(["‘])(.+?)\1\s*\)
取到require()括号内的模块名称,而前边的都可以认为是过滤器。因为运用了replace的高级函数,通过传入函数获取匹配到的值,前边的匹配规则是为了过滤代码中双引号、单引号、多行注释、单行注释、正则表达式中包含的require,这些require其实都是无效的,所以过滤掉。
util-events.js为了支持加载模块各个过程中的事件处理和调试,seajs自己实现了事件机制,实现原理是内部定义事件对象(挂载在data上的events),把绑定的事件挂载到events上,触发和删除其实都是对event对象的操作。简易代码结构:
var events = data.events = {} seajs.on = function(name, callback) {} seajs.off = function(name, callback) {} var emit = seajs.emit = function(name, data) {}
seajs中申明的事件有:config、error、load、fetch、request、exec、resolve、define
util-lang.js为内部提供类型判断函数,以及内部实现id自增,每调用一次id加1
function isType(type) { return function(obj) { return {}.toString.call(obj) == "[object " + type + "]" } } var isObject = isType("Object") var isString = isType("String") var isArray = Array.isArray || isType("Array") var isFunction = isType("Function") var isUndefined = isType("Undefined") var _cid = 0 function cid() { return _cid++ }
util-path.js处理内部所有关于路径的转换,seajs对模块的查找都必须是绝对路径,所以针对用户的输入,需要对各种情况转为绝对路径来操作。
// dirname("a/b/c.js?t=123#xx/zz") ==> "a/b/" function dirname(path) {} // realpath("http://test.com/a//./b/../c") ==> "http://test.com/a/c" function realpath(path) {} // normalize("path/to/a") ==> "path/to/a.js" // NOTICE: substring is faster than negative slice and RegExp function normalize(path) {} //处理config中的各个路径配置 function parseAlias(id) {} function parsePaths(id) {} function parseVars(id) {} function parseMap(uri) {} //给传入的模块生成绝对路径 function addBase(id, refUri) {} //这个函数内部调用了parseAlias、parsePaths、parseVars、parseMap function id2Uri(id, refUri) {} //内部挂载,最后都是通过resolve来访问 seajs.resolve = id2Uri
util-request.js是真正的底层加载模块函数,seajs对模块的加载,也就是js或者css的加载,底层都是通过往页面内插入标签实现,就是依赖这个工具库。
function request(url, callback, charset, crossorigin) { /*内部根据传入参数判断css还是js文件,然后往页面head插入对应的link和script标签,并调用addOnload*/ addOnload(node, callback, isCSS, url) } //这个函数需要判断css或js有没有加载完,加载完毕之后才会执行移除script标签操作并执行回调。内部做了兼容处理,判断css加载完做了特殊处理,通过内部函数pollCss实现。js的加载通过script节点的onload事件或者onreadystatechange执行相应回调即可。 function addOnload(node, callback, isCSS, url) {} //内部函数,判断css是否加载完,通过link标签的sheet的属性(兼容性需要判断到link标签下sheet属性下的cssRule属性)是否存在来判断,内部做了循环,每毫秒去监测一次这个值 pollCss() //内部函数,在IE6-9下onload判断script有兼容性问题,触发的时机不对,所以需要通过interactive属性来判断 getCurrentScript() // For Developers seajs.request = request
sea.js申明版本信息和seajs对象。
var seajs = global.seajs = { // The current version of Sea.js being used version: "@VERSION" } var data = seajs.data = {}
config.js中做了两件事:1.为data对象挂载几个有用的属性。2.seajs.config函数保存配置到data对象下
// If loaderUri is `http://test.com/libs/seajs/[??][seajs/1.2.3/]sea.js`, the // baseUri should be `http://test.com/libs/` data.base = (loaderDir.match(BASE_RE) || ["", loaderDir])[1] // The loader directory data.dir = loaderDir // The current working directory data.cwd = cwd // The charset for requesting files data.charset = "utf-8" // 这里有个nb用法,url参数如果带有?seajs-aaa,首先回去在所有模块之前去加载aaa模块 data.preload = (function() { return plugins })() //这个api的实现实际就是把传入的配置保存到data对象内 seajs.config = function(configData) {})
除了最重要的module.js,其他源码已经分析完毕,modulejs我们下一篇专门分析。
原文:http://www.cnblogs.com/weislywang/p/6667615.html