seajs模块之间依赖的加载以及模块的执行
本文介绍的是seajs模块之间依赖的加载以及模块的执行,下面话不多说直接来看详细的介绍。
入口方法
每个程序都有个入口方法,类似于c的main函数,seajs也不例外。系列一的demo在首页使用了seajs.use() ,这便是入口方法。入口方法可以接受2个参数,第一个参数为模块名称,第二个为回调函数。入口方法定义了一个新的模块,这个新定义的模块依赖入参提供的模块。然后设置新模块的回调函数,用以在loaded状态之后调用。该回调函数主要是执行所有依赖模块的工厂函数,最后在执行入口方法提供的回调。
// 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用于预加载seajs提供的插件plugins,非主要功能,可以忽略。而Module.use则是核心方法,该方法正如之前所说,创建新的module并设置回调函数,最后加载新模块的所有依赖模块。
加载依赖之load方法
load方法可谓是seajs的精华所在。该方法主要加载依赖模块并依次执行依赖模块的回调函数,最终执行的回调函数则是通过seajs.use(“./name”)创建的新模块的回调,也就是mod.callback 。
load方法递归加载依赖模块,如果依赖模块还依赖其他模块,则再加载这个模块。这是通过Module类中的_waitings和_remain来实现的。
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 }
首先初始化模块的_waitings和_remain属性,如果_remain为0,则意味着没有依赖或者依赖已加载,可以执行onload函数;如果不为0,则fetch未加载的模块。在这里有个实现的小技巧,就是同时加载所有依赖:requestCache对象保存加载函数:(在fetch函数中定义)
if (!emitData.requested) { requestCache ? requestCache[emitData.requestUri] = sendRequest : sendRequest() }
其中,sendRequest函数定义如下:
function sendRequest() { seajs.request(emitData.requestUri, emitData.onRequest, emitData.charset) }
并行加载所有依赖,当依赖加载完毕,执行onRequest回调,向上冒泡,加载依赖的依赖,直至没有依赖模块。
当最上层的依赖已没有依赖模块时,执行onload函数,在函数体内设置状态为loaded,执行mod.callback,并检查并设置该模块的_waitings属性,判断下层模块是否还有依赖,若没有则执行下层模块的mod.callback,这一依次回溯,最终将会执行通过seajs.use创建的匿名模块的mod.callback。
例证
通过一个简单的例子,论证上述过程:
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)); })
通过调试工具,可以看出执行onload的次序:
最后可看出,匿名模块的状态码为4,也就是该模块并未执行.确实,也没有给匿名模块定义工厂函数,无法执行.
模块执行之exec
模块执行是在seajs.use中定义的mod.callback中调用的,依次调用所有依赖的exec方法,执行程序逻辑。exec方法中有commonJS的一些重要关键字或者函数,如require,exports等,让我们一看究竟:
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函数获取模块并执行模块的工厂函数,获取返回值。require函数的resolve方法则是获取对应模块名的绝对url,require函数的async方法异步加载依赖并执行回调。对于工厂方法的返回值,如果工厂方法为对象,则这就是exports的值;or工厂方法有返回值,则为exports的值;or module.exports的值为exports的值。当可以获取到exports值时,设置状态为executed。
值得注意的一点:当想要通过给exports赋值来导出一个对象时
define(function(require,exports,module){ exports ={ add: function(a,b){ return a+b; } } })
是不成功的.我们通过执行上述方法来判断最终导出exports的值.首先,函数没有返回值,其次,mod.exports为undefined,最终导出的exports为undefined。为什么会出现这种情况呢?是因为js中引用赋值所造成的。js的赋值策略是“按共享传递”,虽说初始时exports === module.exports,但是当给exports赋一个对象时,此时exports指向该对象,module.exports却仍未初始化,为undefined,因此会出错。
正确的写法为
define(function(require,exports,module){ module.exports ={ add: function(a,b){ return a+b; } } })
总结
可以说,seajs的核心模块的实现已讲解完毕,见识了不少编码技巧,领略了回调模式的巧妙,以及于细微处的考量。代码的每一处都考虑到了内存泄露和this指针引用偏移的危险,做了积极的预防,这种精神值得学习。

Alat AI Hot

Undresser.AI Undress
Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover
Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool
Gambar buka pakaian secara percuma

Clothoff.io
Penyingkiran pakaian AI

AI Hentai Generator
Menjana ai hentai secara percuma.

Artikel Panas

Alat panas

Notepad++7.3.1
Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina
Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1
Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6
Alat pembangunan web visual

SublimeText3 versi Mac
Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Topik panas

