转载

seajs3.0.0源码分析记录

自己边读变加了一些注释,理解了一下seajs3.0.0工作的流程。正则没有一个个去理解,插件模块也没看, 以后有时间了可以补充完整~

事件系统中事件队列的获取&定义方法

var list = events[name] || (events[name] = [])

以前自己写都是

if(!events[name]){ events[name]=[]; } var list=events[name];

加载模块文件的方法

webworker环境下加载模块文件

获取seajs的加载路径:

var stack; try { var up = new Error(); throw up; } catch (e) { // IE won't set Error.stack until thrown stack = e.stack.split('/n'); }
// at Error (native) <- Here's your problem // at http://localhost:8000/_site/dist/sea.js:2:4334 <- What we want // at http://localhost:8000/_site/dist/sea.js:2:8386 // at http://localhost:8000/_site/tests/specs/web-worker/worker.js:3:1

根据url加载模块,使用的是webworker全局环境下的importScripts方法

function requestFromWebWorker(url, callback, charset) { // Load with importScripts var error; try { importScripts(url); } catch (e) { error = e; } callback(error); } // For Developers seajs.request = requestFromWebWorker;

浏览器js线程中异步加载模块文件

// 通过script标签加载脚本 function request(url, callback, charset) { var node = doc.createElement("script") if (charset) { var cs = isFunction(charset) ? charset(url) : charset if (cs) { node.charset = cs } } addOnload(node, callback, url) node.async = true node.src = url // For some cache cases in IE 6-8, the script executes IMMEDIATELY after // the end of the insert execution, so use `currentlyAddingScript` to // hold current node, for deriving url in `define` call currentlyAddingScript = node // ref: #185 & http://dev.jquery.com/ticket/2709 baseElement ? head.insertBefore(node, baseElement) : head.appendChild(node) currentlyAddingScript = null } //添加加载完成之后的处理,包括报错、清除对象、移除dom等操作 function addOnload(node, callback, url) { var supportOnload = "onload" in node if (supportOnload) { node.onload = onload node.onerror = function() { emit("error", { uri: url, node: node }) onload(true) } } else { node.onreadystatechange = function() { if (/loaded|complete/.test(node.readyState)) { onload() } } } function onload(error) { // Ensure only run once and handle memory leak in IE node.onload = node.onerror = node.onreadystatechange = null // Remove the script to reduce memory leak // 添加了又去除掉 if (!data.debug) { head.removeChild(node) } // Dereference the node node = null callback(error) } } // For Developers seajs.request = request

核心模块类 Module

Module类定义

//定义module类 function Module(uri, deps) { //模块路径 this.uri = uri //这是一个字符串数组,内容是该模块依赖模块的id列表 this.dependencies = deps || [] //这是该模块所依赖的模块的一个map key为id,value为Module对象 this.deps = {} // Ref the dependence modules //模块状态 this.status = 0 //用来辅助load的一个数组,load完成后会删掉 this._entry = [] }

模块缓存

var cachedMods = seajs.cache = {}

原型方法列表

// 获取模块中依赖模块的uri数组 Module.prototype.resolve  // 一个辅助模块load过程的函数 Module.prototype.pass //加载并执行模块 Module.prototype.load //模块加载完成后触发 Module.prototype.onload //模块404时候触发 Module.prototype.error //执行模块 Module.prototype.exec //用来获取模块文件,每调用一次给一个缓存对象设置一个key value,value是一个函数,调用即开始加载文件

函数方法列表

//通过id获取文件uri Module.resolve = function(id, refUri) //用来定义一个模块 Module.define= function (id, deps, factory) //将id、依赖、 factory 、模块加载状态设置给缓存中的模块 Module.save= function(uri, meta) ////根据uri,从缓存中获取模块,或者以接收的uri和deps参数创建一个模块返回,并加入缓存 Module.get=function(uri, deps) //用来加载并运行模块 Module.use = function(ids, callback, uri)

暴露出来的API

1 // Public API  2  3 seajs.use = function(ids, callback) {  4 //use 实际上创建了一个id为data.cwd + "_use_" + cid()的模块,这个模块的依赖模块是待启动的模块  5 Module.use(ids, callback, data.cwd + "_use_" + cid())  6 return seajs  7 }  8  9 Module.define.cmd = {} 10 global.define = Module.define 11 12 13 // For Developers 14 15 seajs.Module = Module 16 data.fetchedList = fetchedList 17 data.cid = cid 18 19 seajs.require = function(id) { 20 var mod = Module.get(Module.resolve(id)) 21 if (mod.status < STATUS.EXECUTING) { 22  mod.onload() 23  mod.exec() 24  } 25 return mod.exports 26 } 27 28 /** 29  * config.js - The configuration for the loader 30 */ 31 32 // The root path to use for id2uri parsing 33 data.base = loaderDir 34 35 // The loader directory 36 data.dir = loaderDir 37 38 // The loader's full path 39 data.loader = loaderPath 40 41 // The current working directory 42 data.cwd = cwd 43 44 // The charset for requesting files 45 data.charset = "utf-8" 46 47 // data.alias - An object containing shorthands of module id 48 // data.paths - An object containing path shorthands in module id 49 // data.vars - The {xxx} variables in module id 50 // data.map - An array containing rules to map module uri 51 // data.debug - Debug mode. The default value is false 52 53 seajs.config = function(configData) { 54 55 for (var key in configData) { 56 var curr = configData[key] 57 var prev = data[key] 58 59 // Merge object config such as alias, vars 60 if (prev && isObject(prev)) { 61 for (var k in curr) { 62 prev[k] = curr[k] 63  } 64  } 65 else { 66 // Concat array config such as map 67 if (isArray(prev)) { 68 curr = prev.concat(curr) 69  } 70 // Make sure that `data.base` is an absolute path 71 else if (key === "base") { 72 // Make sure end with "/" 73 if (curr.slice(-1) !== "/") { 74 curr += "/" 75  } 76 curr = addBase(curr) 77  } 78 79 // Set config 80 data[key] = curr 81  } 82  } 83 84 emit("config", configData) 85 return seajs 86 }  View Code

常用API和特性的理解

seajs.use原理

seajs.use ("index.js") = Module.use(["index.js"],undefined,"http://xxxx.com/xxx/xxx/_use_i")

① 通过 Module.get 创建模块mod,

② 通过 Module.prototype.load 加载mod模块,为mode定义history、remain、callback属性,设置_entry

Module.prototype.load中

①通过 Module.prototype.resolve 函数获取模块的依赖模块uri数组。

Module.prototype.resolve会遍历mod对象的dependencies中的模块id,挨个通过 Module.resolve 函数获取其uri,最后返回数组

② 遍历上一步获取的uri,通过Module.get获取模块,给mod的deps属性设置值,key为依赖模块id,value为依赖模块对象

④ 通过 Module.prototype.fetch 获取所有依赖模块的模块文件

⑤ 对子模块重复以上过程

⑥mod模块加载完成后,执行onload回调,在onload中执行Module.use的回调callback,在callback中执行Module.prototype.exec来运行该模块

require、exports、module的注入&懒执行

Module.use的回调callback中会运行模块的Module.prototype.exec ,返回module的exports对象。

这个过程中,若factory是function,则 调用factory ,参数传入require,mod.exports,mod。

require:

function require(id) { var m = mod.deps[id] || Module.get(require.resolve(id)) if (m.status == STATUS.ERROR) { throw new Error('module was broken: ' + m.uri); } return m.exec() }

注入的过程:

// Exec factory var factory = mod.factory //执行factory的代码,这个时候才执行,懒执行 var exports = isFunction(factory) ? factory(require, mod.exports = {}, mod) : factory

可以看到,require的作用是从缓存中获取模块,然后exec。也就是说,只有require函数被执行的时候,模块代码才会被执行。这就是seajs的as lazy as posible ,懒执行。

global.define

define函数的作用是从参数中获取 id 、dependences 、  factory,然后从模块缓存中获取模块对象,或者创建新模块加入缓存,将id、dependences、factory设置给模块。

贴上关键代码:

Module.define = function (id, deps, factory) { var argsLen = arguments.length // 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 } } // 从function字符串中获取依赖模块id if (!isArray(deps) && isFunction(factory)) { deps = typeof parseDependencies === "undefined" ? [] : parseDependencies(factory.toString()) } var meta = { id: id, uri: Module.resolve(id), deps: deps, factory: factory } meta.uri ? Module.save(meta.uri, meta) : // Save information for "saving" work in the script onload event anonymousMeta = meta }

(为了方便理解,源码中一些辅助代码被我剪切掉了)

最后附上我写过注释的源码

sea3.0.0-learn

正文到此结束
Loading...