This article introduces the loading of dependencies between seajs modules and the execution of modules. Let’s not go into details below.
Entry method
Every program has an entry method, which is similar to the main function of c, and seajs is no exception. The demo of Series 1 uses seajs.use() on the homepage, which is the entry method. The entry method can accept 2 parameters, the first parameter is the module name, and the second parameter is the callback function. The entry method defines a new module, and this newly defined module depends on the module provided by the input parameter. Then set the callback function of the new module to be called after the loaded state. This callback function mainly executes the factory functions of all dependent modules, and finally executes the callback provided by the entry method.
// Public API // 入口地址 seajs.use = function(ids, callback) { Module.preload(function() { Module.use(ids, callback, data.cwd + "_use_" + cid()) }) return seajs } // Load preload modules before all other modules Module.preload = function(callback) { var preloadMods = data.preload var len = preloadMods.length if (len) { Module.use(preloadMods, function() { // Remove the loaded preload modules preloadMods.splice(0, len) // Allow preload modules to add new preload modules Module.preload(callback) }, data.cwd + "_preload_" + cid()) } else { callback() } } // 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.callback = function() { var exports = [] var uris = mod.resolve() for (var i = 0, len = uris.length; i < len; i++) { exports[i] = cachedMods[uris[i]].exec() } // 回调函数的入参对应依赖模块的返回值 if (callback) { callback.apply(global, exports) } delete mod.callback } mod.load() }
Module.preload is used to preload plug-ins provided by seajs. It is not a main function and can be ignored. Module.use is the core method. As mentioned before, this method creates a new module and sets the callback function, and finally loads all dependent modules of the new module.
The load method of loading dependencies
The load method can be said to be the essence of seajs. This method mainly loads the dependent modules and executes the callback functions of the dependent modules in sequence. The final callback function is the callback of the new module created through seajs.use("./name"), which is mod.callback.
The load method recursively loads dependent modules. If the dependent module also depends on other modules, then load this module. This is achieved through _waitings and _remain in the Module class.
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, mod) var len = mod._remain = uris.length var m // Initialize modules and register waitings for (var i = 0; i < len; i++) { m = Module.get(uris[i]) // 修改 依赖文件 的 _waiting属性 if (m.status < STATUS.LOADED) { // Maybe duplicate: When module has dupliate dependency, it should be it's count, not 1 m._waitings[mod.uri] = (m._waitings[mod.uri] || 0) + 1 } else { mod._remain-- } } // 加载完依赖,执行模块 if (mod._remain === 0) { mod.onload() return } // Begin parallel loading var requestCache = {} for (i = 0; i < len; i++) { m = cachedMods[uris[i]] // 该依赖并未加载,则先fetch,将seajs.request函数绑定在对应的requestCache上,此时并未加载模块 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]() } } } // 依赖模块加载完毕执行回调函数 // 并检查依赖该模块的其他模块是否可以执行 Module.prototype.onload = function() { var mod = this mod.status = STATUS.LOADED if (mod.callback) { mod.callback() } console.log(mod) // Notify waiting modules to fire onload var waitings = mod._waitings var uri, m for (uri in waitings) { if (waitings.hasOwnProperty(uri)) { m = cachedMods[uri] m._remain -= waitings[uri] if (m._remain === 0) { m.onload() } } } // Reduce memory taken delete mod._waitings delete mod._remain }
First initialize the _waitings and _remain attributes of the module. If _remain is 0, it means there is no dependency or the dependency is loaded, and the onload function can be executed; if it is not 0, fetch the unloaded module . There is a little implementation trick here, which is to load all dependencies at the same time: requestCache object saves the loading function: (defined in the fetch function)
if (!emitData.requested) { requestCache ? requestCache[emitData.requestUri] = sendRequest : sendRequest() }
Among them, the sendRequest function is defined as follows:
function sendRequest() { seajs.request(emitData.requestUri, emitData.onRequest, emitData.charset) }
Parallel Load all dependencies. When the dependencies are loaded, the onRequest callback is executed, bubbles up, and loads the dependent dependencies until there are no dependent modules.
When the top-level dependency no longer depends on the module, execute the onload function, set the status to loaded in the function body, execute mod.callback, and check and set the _waitings attribute of the module to determine whether the lower module is still If there is a dependency, if not, the mod.callback of the lower module will be executed. This sequence of backtracking will eventually execute the mod.callback of the anonymous module created through seajs.use.
Example
Use a simple example to demonstrate the above process:
tst.html <script> seajs.use('./b'); </script> ------------------------------------- a.js define(function(require,exports,module){ exports.add = function(a,b){ return a+b; } }) ------------------------------------ b.js define(function(require,exports,module){ var a = require("./a"); console.log(a.add(3,5)); })
Through debugging tools, you can see the order in which onload is executed:
Finally, it can be seen that the status code of the anonymous module is 4, which means that the module has not been executed. Indeed, there is no factory function defined for the anonymous module and cannot be executed.
Module execution The execution of the exec
module is called in the mod.callback defined in seajs.use, and all dependent exec methods are called in sequence to execute the program logic. There are some important keywords or functions of commonJS in the exec method, such as require, exports, etc. Let us take a look:
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 // Create require var uri = mod.uri function require(id) { return Module.get(require.resolve(id)).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 var factory = mod.factory // 工厂函数有返回值,则返回; // 无返回值,则返回mod.exports var exports = isFunction(factory) ? factory(require, mod.exports = {}, mod) : factory 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 exports }
require function obtains the module and executes the module's factory function to obtain the return value. The resolve method of the require function obtains the absolute URL of the corresponding module name, and the async method of the require function asynchronously loads dependencies and executes callbacks. For the return value of the factory method, if the factory method is an object, this is the value of exports; or if the factory method has a return value, it is the value of exports; or the value of module.exports is the value of exports. When the exports value can be obtained, set the status to executed.
It is worth noting: When you want to export an object by assigning a value to exports,
define(function(require,exports,module){ exports ={ add: function(a,b){ return a+b; } } })
is unsuccessful. We judge the value of the final exports by executing the above method. First, The function has no return value. Secondly, mod.exports is undefined, and the final exported exports is undefined. Why does this happen? It is caused by reference assignment in js. The assignment strategy of js is "pass by sharing". Although initially exports === module.exports, when an object is assigned to exports, exports points to the object, but module.exports has not been initialized yet and is undefined, so Something will go wrong.
The correct way to write it is
define(function(require,exports,module){ module.exports ={ add: function(a,b){ return a+b; } } })
Summary
It can be said that the implementation of the core module of seajs has been explained, I have seen a lot of coding skills, and appreciated the ingenuity of the callback mode. , and consideration of details. Every part of the code takes into account the dangers of memory leaks and this pointer reference offset, and takes active precautions. This spirit is worth learning.
For more articles related to the loading of dependencies between seajs modules and the execution of modules, please pay attention to the PHP Chinese website!