seajs模块之间依赖的加载以及模块的执行


Posted in Javascript onOctober 21, 2016

本文介绍的是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的次序:

seajs模块之间依赖的加载以及模块的执行

最后可看出,匿名模块的状态码为4,也就是该模块并未执行.确实,也没有给匿名模块定义工厂函数,无法执行.

模块执行之exec

模块执行是在seajs.use中定义的mod.callback中调用的,依次调用所有依赖的exec方法,执行程序逻辑。exec方法中有commonJS的一些重要关键字或者函数,如requireexports等,让我们一看究竟:

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,最终导出的exportsundefined。为什么会出现这种情况呢?是因为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指针引用偏移的危险,做了积极的预防,这种精神值得学习。

对于seajs,前前后后花了不下一个星期来阅读源码,从刚开始的一知半解到如今的拜服,我真切的领会到了设计思想的重要性。之前我不怎么重视实现的技巧性,认为能够实现,不出bug,健壮性良好即可,但是现在我意识到错了,尤其是在load依赖模块那部分实现中,技巧性十足。以上就是本文的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。

Javascript 相关文章推荐
js计算两个时间之间天数差的实例代码
Nov 19 Javascript
jQuery实现流动虚线框的方法
Jan 29 Javascript
Jquery注册事件实现方法
May 18 Javascript
js中数组插入、删除元素操作的方法
Feb 15 Javascript
JavaScript实现省市联动过程中bug的解决方法
Dec 04 Javascript
浅谈Node.js 沙箱环境
May 15 Javascript
vue.js计算属性computed用法实例分析
Jul 06 Javascript
对vue中v-on绑定自定事件的实例讲解
Sep 06 Javascript
爬虫利器Puppeteer实战
Jan 09 Javascript
vue实现树形结构样式和功能的实例代码
Oct 15 Javascript
微信小程序仿抖音短视频切换效果的实例代码
Jun 24 Javascript
如何用JavaScript学习算法复杂度
Apr 30 Javascript
js制作支付倒计时页面
Oct 21 #Javascript
浅谈javascript中的三种弹窗
Oct 21 #Javascript
JS动态给对象添加属性和值的实现方法
Oct 21 #Javascript
AngularJS 与百度地图的结合实例
Oct 20 #Javascript
Bootstrap图片轮播组件Carousel使用方法详解
Oct 20 #Javascript
JavaScript 身份证号有效验证详解及实例代码
Oct 20 #Javascript
AngularJS  双向数据绑定详解简单实例
Oct 20 #Javascript
You might like
浅析Apache中RewriteCond规则参数的详细介绍
2013/06/30 PHP
php字符串函数学习之substr()
2015/03/27 PHP
PHP IDE PHPStorm配置支持友好Laravel代码提示方法
2015/05/12 PHP
PHP加密技术的简单实现
2016/09/04 PHP
window.onbeforeunload方法在IE下无法正常工作的解决办法
2010/01/23 Javascript
THREE.JS入门教程(2)着色器-上
2013/01/24 Javascript
js将控件隐藏的方法及display属性介绍
2013/07/04 Javascript
angularjs实现与服务器交互分享
2014/06/24 Javascript
JS实现鼠标点击展开或隐藏表格行的方法
2015/03/03 Javascript
js实现简洁大方的二级下拉菜单效果代码
2015/09/01 Javascript
JS+CSS实现鼠标滑过时动态翻滚的导航条效果
2015/09/24 Javascript
聊一聊JS中的prototype
2016/09/29 Javascript
Vue自定义指令介绍(2)
2016/12/08 Javascript
使用mint-ui开发项目的一些心得(分享)
2017/09/07 Javascript
vue实现消息的无缝滚动效果的示例代码
2017/12/05 Javascript
vue2.0实现音乐/视频播放进度条组件
2018/06/06 Javascript
vue+echarts实现动态绘制图表及异步加载数据的方法
2018/10/17 Javascript
深入理解react 组件类型及使用场景
2019/03/07 Javascript
Vue组件间通信 Vuex的用法解析
2019/08/05 Javascript
解决layui的使用以及针对select、radio等表单组件不显示的问题
2019/09/05 Javascript
vue 组件内获取actions的response方式
2019/11/08 Javascript
详解Vue的watch中的immediate与watch是什么意思
2019/12/30 Javascript
[02:42]DOTA2英雄基础教程 杰奇洛
2013/12/23 DOTA
linux下安装easy_install的方法
2013/02/10 Python
编写Python脚本使得web页面上的代码高亮显示
2015/04/24 Python
在Python的Django框架中创建语言文件
2015/07/27 Python
python实现字符串连接的三种方法及其效率、适用场景详解
2017/01/13 Python
Python绘制并保存指定大小图像的方法
2019/01/10 Python
python根据字典的键来删除元素的方法
2020/08/16 Python
英国最大的化装舞会服装网站:Fancydress.com
2017/08/15 全球购物
老板电器官方购物商城:老板油烟机、燃气灶、消毒柜、电烤箱
2018/05/30 全球购物
在阿尔卑斯山或希腊度过快乐假期:Alpine Elements
2019/12/28 全球购物
澳大利亚人信任的清洁平台,您的私人管家:Jarvis
2020/12/25 全球购物
孝敬父母的活动方案
2014/08/28 职场文书
志愿服务心得体会
2016/01/15 职场文书
java实现web实时消息推送的七种方案
2022/07/23 Java/Android