1 獻上源碼node
/** * Add the capability to load CMD modules in node environment * @author lifesinger@gmail.com */ //node遵循cmd規範,經過require來引入模塊; var fs = require("fs") var path = require("path") var vm = require("vm") var normalize = require("./winos").normalize var moduleStack = [] var uriCache = {} var nativeLoad //首先運行sea-debug.js runSeaJS("../dist/sea-debug.js") hackNative() attach() keep() seajs.config({ cwd: normalize(process.cwd()) + "/" }) function runSeaJS(filepath) { var code = fs.readFileSync(path.join(__dirname, filepath), "utf8") code = code.replace("})(this);", "})(exports);") // Run "sea.js" code in a fake browser environment var sandbox = require("./sandbox") vm.runInNewContext(code, sandbox, "sea-debug.vm") global.seajs = sandbox.exports.seajs global.define = sandbox.exports.define } function hackNative() { var Module = module.constructor nativeLoad = Module._load Module._load = function(request, parent, isMain) { var exports = nativeLoad(request, parent, isMain) var _filename = Module._resolveFilename(request, parent) var filename = normalize(_filename) var mod = seajs.cache[filename] if (mod) { if (mod.status < seajs.Module.STATUS.EXECUTING) { seajs.use(filename) } exports = Module._cache[_filename] = mod.exports } return exports } var _compile = Module.prototype._compile Module.prototype._compile = function(content, filename) { moduleStack.push(this) try { return _compile.call(this, content, filename) } finally { moduleStack.pop() } } } function attach() { seajs.on("request", requestListener) seajs.on("define", defineListener) } function requestListener(data) { var requestUri = pure(data.requestUri) var ext = path.extname(requestUri) //process.stdout.write("requestUri = " + requestUri + "\n") if (ext === ".js") { // Use native `require` instead of script-inserted version nativeLoad(requestUri) data.onRequest() data.requested = true } // Throw error if this function is the last request handler else if (seajs.data.events["request"].length === 1) { throw new Error("Do NOT support to load this file in node environment: " + requestUri) } } function defineListener(data) { if (!data.uri) { var derivedUri = normalize(moduleStack[moduleStack.length - 1].id) data.uri = uriCache[derivedUri] || derivedUri } } function keep() { var _off = seajs.off var events = seajs.data.events seajs.off = function(name, callback) { // Remove *all* events if (!(name || callback)) { // For Node.js to work properly for (var prop in events) { delete events[prop] } } else { _off(name, callback) } attach() return seajs } } function pure(uri) { // Remove timestamp etc var ret = uri.replace(/\?.*$/, "") // Cache it if (ret !== uri) { uriCache[ret] = uri } return ret }
2 咱們引入seajs以後在控制檯輸出下數組
// console.log(seajs); Object Module:function t(a,b) cache:Object config:function (a) data:Object emit:function (a,b) off:function (a,b) on:function (a,b) request:function o(a,b,c,d) require:function (a) resolve:function m(a,b) use:function (a,b) version:"2.2.3"
3 seajs源碼地址閉包
seajs-debug.js源碼地址app
//先來看下runSeaJS執行的過程 function runSeaJS(filepath) { var code = fs.readFileSync(path.join(__dirname, filepath), "utf8") code = code.replace("})(this);", "})(exports);") // Run "sea.js" code in a fake browser environment var sandbox = require("./sandbox") vm.runInNewContext(code, sandbox, "sea-debug.vm") global.seajs = sandbox.exports.seajs global.define = sandbox.exports.define } //會執行seajs-debug.js
接下來一步步分析其執行片斷async
3.1 一些函數和變量的聲明ide
var seajs = global.seajs = { // The current version of Sea.js being used version: "3.0.1" } var data = seajs.data = {}; var isObject = isType("Object") var isString = isType("String") var isArray = Array.isArray || isType("Array") var isFunction = isType("Function") var isUndefined = isType("Undefined") //seajs給每一個模塊的uri命名後綴; var _cid = 0 function cid() { return _cid++ } var events = data.events = {}; //全部經過define定義的模塊都會放在這個數組裏面 var cachedMods = seajs.cache = {} var anonymousMeta; //在全局定義define函數; global.define = Module.define; //給seajs對象定義Module構造函數; seajs.Module = Module data.fetchedList = fetchedList data.cid = cid; // The root path to use for id2uri parsing data.base = loaderDir // The loader directory data.dir = loaderDir // The loader's full path data.loader = loaderPath // The current working directory data.cwd = cwd // The charset for requesting files data.charset = "utf-8" //seajs.config函數的定義,給每一個模塊定義別名等 // data.alias - An object containing shorthands of module id // data.paths - An object containing path shorthands in module id // data.vars - The {xxx} variables in module id // data.map - An array containing rules to map module uri // data.debug - Debug mode. The default value is false seajs.config = function(configData) { for (var key in configData) { var curr = configData[key] var prev = data[key] // Merge object config such as alias, vars if (prev && isObject(prev)) { for (var k in curr) { prev[k] = curr[k] } } else { // Concat array config such as map if (isArray(prev)) { curr = prev.concat(curr) } // Make sure that `data.base` is an absolute path else if (key === "base") { // Make sure end with "/" if (curr.slice(-1) !== "/") { curr += "/" } curr = addBase(curr) } // Set config data[key] = curr } } emit("config", configData) return seajs }
3.2 先來看下在seajs中一個模塊是如何定義的;函數
3.2.1 首先看下Module構造函數fetch
var STATUS = Module.STATUS = { // 1 - The `module.uri` is being fetched FETCHING: 1, // 2 - The meta data has been saved to cachedMods SAVED: 2, // 3 - The `module.dependencies` are being loaded LOADING: 3, // 4 - The module are ready to execute LOADED: 4, // 5 - The module is being executed EXECUTING: 5, // 6 - The `module.exports` is available EXECUTED: 6, // 7 - 404 ERROR: 7 } function Module(uri, deps) { this.uri = uri this.dependencies = deps || [] this.deps = {} // Ref the dependence modules this.status = 0 this._entry = [] } // Resolve module.dependencies 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.prototype.pass = function() { var mod = this var len = mod.dependencies.length for (var i = 0; i < mod._entry.length; i++) { var entry = mod._entry[i] var count = 0 for (var j = 0; j < len; j++) { var m = mod.deps[mod.dependencies[j]] // If the module is unload and unused in the entry, pass entry to it if (m.status < STATUS.LOADED && !entry.history.hasOwnProperty(m.uri)) { entry.history[m.uri] = true count++ m._entry.push(entry) if(m.status === STATUS.LOADING) { m.pass() } } } // If has passed the entry to it's dependencies, modify the entry's count and del it in the module if (count > 0) { entry.remain += count - 1 mod._entry.shift() i-- } } } // Load module.dependencies and fire onload when all done Module.prototype.load = function() { var mod = this // If the module is being loaded, just wait it onload call if (mod.status >= STATUS.LOADING) { return } mod.status = STATUS.LOADING // Emit `load` event for plugins such as combo plugin var uris = mod.resolve() emit("load", uris) for (var i = 0, len = uris.length; i < len; i++) { mod.deps[mod.dependencies[i]] = Module.get(uris[i]) } // Pass entry to it's dependencies mod.pass() // If module has entries not be passed, call onload if (mod._entry.length) { mod.onload() return } // Begin parallel loading var requestCache = {} var m for (i = 0; i < len; i++) { m = cachedMods[uris[i]] if (m.status < STATUS.FETCHING) { m.fetch(requestCache) } else if (m.status === STATUS.SAVED) { m.load() } } // Send all requests at last to avoid cache bug in IE6-9. Issues#808 for (var requestUri in requestCache) { if (requestCache.hasOwnProperty(requestUri)) { requestCache[requestUri]() } } } // Call this method when module is 404 Module.prototype.error = function() { var mod = this mod.onload() mod.status = STATUS.ERROR } // Fetch a module Module.prototype.fetch = function(requestCache) { var mod = this var uri = mod.uri mod.status = STATUS.FETCHING // Emit `fetch` event for plugins such as combo plugin var emitData = { uri: uri } emit("fetch", emitData) var requestUri = emitData.requestUri || uri // Empty uri or a non-CMD module if (!requestUri || fetchedList.hasOwnProperty(requestUri)) { mod.load() return } if (fetchingList.hasOwnProperty(requestUri)) { callbackList[requestUri].push(mod) return } fetchingList[requestUri] = true callbackList[requestUri] = [mod] // Emit `request` event for plugins such as text plugin emit("request", emitData = { uri: uri, requestUri: requestUri, onRequest: onRequest, charset: isFunction(data.charset) ? data.charset(requestUri) : data.charset, crossorigin: isFunction(data.crossorigin) ? data.crossorigin(requestUri) : data.crossorigin }) if (!emitData.requested) { requestCache ? requestCache[emitData.requestUri] = sendRequest : sendRequest() } function sendRequest() { seajs.request(emitData.requestUri, emitData.onRequest, emitData.charset, emitData.crossorigin) } function onRequest(error) { delete fetchingList[requestUri] fetchedList[requestUri] = true // Save meta data of anonymous module if (anonymousMeta) { Module.save(uri, anonymousMeta) anonymousMeta = null } // Call callbacks var m, mods = callbackList[requestUri] delete callbackList[requestUri] while ((m = mods.shift())) { // When 404 occurs, the params error will be true if(error === true) { m.error() } else { m.load() } } } } // Resolve id to uri Module.resolve = function(id, refUri) { // Emit `resolve` event for plugins such as text plugin var emitData = { id: id, refUri: refUri } emit("resolve", emitData) return emitData.uri || seajs.resolve(emitData.id, refUri) } // Define a module 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 } } //主要是將本身定義的模塊中的依賴所有給到deps數組中; // Parse dependencies according to the module factory code if (!isArray(deps) && isFunction(factory)) { deps = typeof parseDependencies === "undefined" ? [] : parseDependencies(factory.toString()) } var meta = { id: id, uri: Module.resolve(id), deps: deps, factory: factory } // Try to derive uri in IE6-9 for anonymous modules if (!isWebWorker && !meta.uri && doc.attachEvent && typeof getCurrentScript !== "undefined") { var script = getCurrentScript() if (script) { meta.uri = script.src } // NOTE: If the id-deriving methods above is failed, then falls back // to use onload event to get the uri } // Emit `define` event, used in nocache plugin, seajs node version etc emit("define", meta) //這裏將define定義的模塊經過save==>get 放入cachedMods數組中 meta.uri ? Module.save(meta.uri, meta) : // Save information for "saving" work in the script onload event anonymousMeta = meta } // Save meta data to cachedMods Module.save = function(uri, meta) { var mod = Module.get(uri) // Do NOT override already saved modules if (mod.status < STATUS.SAVED) { mod.id = meta.id || uri mod.dependencies = meta.deps || [] mod.factory = meta.factory mod.status = STATUS.SAVED emit("save", mod) } } // Get an existed module or create a new one Module.get = function(uri, deps) { return cachedMods[uri] || (cachedMods[uri] = new Module(uri, deps)) } // Use function is equal to load a anonymous module Module.use = function (ids, callback, uri) { var mod = Module.get(uri, isArray(ids) ? ids : [ids]) mod._entry.push(mod) mod.history = {} mod.remain = 1 mod.callback = function() { var exports = [] var uris = mod.resolve() for (var i = 0, len = uris.length; i < len; i++) { //這裏返回模塊的exports exports[i] = cachedMods[uris[i]].exec() } if (callback) { callback.apply(global, exports) } delete mod.callback delete mod.history delete mod.remain delete mod._entry } mod.load() } // Public API seajs.use = function(ids, callback) { Module.use(ids, callback, data.cwd + "_use_" + cid()) return seajs } Module.define.cmd = {} global.define = Module.define
因此當咱們定義seajs的模塊的時候,其實調用的是Module的define函數ui
define(funtion(require,exports){ //your code export.init = function(){ } });
回過頭來看上面定義的Module.prototype.define函數裏面的一些註釋;this
3.2.2 再來看下seajs.use函數
基本使用
seajs.use(['a.js','b.js','c.js'],function(a,b,c){ //your code }) seajs.use = function(ids, callback) { //這裏data.cwd在變量聲明中賦值過了; Module.use(ids, callback, data.cwd + "_use_" + cid()) return seajs } Module.use = function (ids, callback, uri) { //若是在cachedMods數組中有uri這個模塊,就直接返回該模塊,若是沒有則建立新的模塊 var mod = Module.get(uri, isArray(ids) ? ids : [ids]) mod._entry.push(mod) mod.history = {} mod.remain = 1 mod.callback = function() { var exports = [] var uris = mod.resolve() //這裏給exports數組賦值爲從cachedMods數組中每一個模塊執行後的結果 for (var i = 0, len = uris.length; i < len; i++) { //每一個模塊的exec執行的返回值有兩種可能 //第一,若是define傳入的不是一個函數,那麼該模塊exec的返回值就是傳入define的值,能夠是對象,也能夠是其餘數據類型 //第二,若是define傳入的是一個函數,那麼該模塊exec的返回值就是傳入define函數中給exports添加的對象; exports[i] = cachedMods[uris[i]].exec() } if (callback) { //這裏apply函數會將exports數組一個一個傳給callback callback.apply(global, exports) } delete mod.callback delete mod.history delete mod.remain delete mod._entry } mod.load() }
3.2.3 接下來重點看下Module.prototype.exec函數的實現
// Execute a module Module.prototype.exec = function () { var mod = this // When module is executed, DO NOT execute it again. When module // is being executed, just return `module.exports` too, for avoiding // circularly calling if (mod.status >= STATUS.EXECUTING) { return mod.exports } mod.status = STATUS.EXECUTING if (mod._entry && !mod._entry.length) { delete mod._entry } //non-cmd module has no property factory and exports if (!mod.hasOwnProperty('factory')) { mod.non = true return } // Create require var uri = mod.uri //定義define函數中的做爲參數的factory的參數define(function(require,exports){})中的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() } require.resolve = function(id) { return Module.resolve(id, uri) } require.async = function(ids, callback) { Module.use(ids, callback, uri + "_async_" + cid()) return require } // Exec factory 這個函數就是define中傳入的函數; var factory = mod.factory //這裏獲得define函數中傳入的函數的執行以後的返回值; //若是傳入的不是函數,那麼直接返回傳入的值; //將這些返回值所謂模塊的exports屬性的值; var exports = isFunction(factory) ? //先給mod.exports = {} factory.call(mod.exports = {}, require, mod.exports, mod) : factory //如上所示,define函數中的傳入的函數沒有返回值,因此exports爲undefined;此時在將其賦值爲mod.exports; if (exports === undefined) { exports = mod.exports } // Reduce memory leak delete mod.factory mod.exports = exports mod.status = STATUS.EXECUTED // Emit `exec` event emit("exec", mod) return mod.exports }
3.2.4 seajs.require
seajs.require = function(id) { //若是重複加載同一個模塊,若是該模塊已經加載過,也就是其define(factory)中的factory已經執行過,那麼該模塊的狀態就會是 STATUS.EXECUTING ;那麼該模塊不會再次執行,直接從該模塊的exports屬性上獲取factory執行後給到的結果; //這裏是一個閉包,也就是factory函數中的變量對象,被模塊的exports屬性引用; var mod = Module.get(Module.resolve(id)) if (mod.status < STATUS.EXECUTING) { mod.onload() mod.exec() } return mod.exports } // Call this method when module is loaded Module.prototype.onload = function() { var mod = this mod.status = STATUS.LOADED // When sometimes cached in IE, exec will occur before onload, make sure len is an number for (var i = 0, len = (mod._entry || []).length; i < len; i++) { var entry = mod._entry[i] if (--entry.remain === 0) { entry.callback() } } delete mod._entry }
若是再次加載同一個組件,則不會重複執行,執行從模塊的exports屬性上讀取結果便可;