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 相关文章推荐
推荐9款炫酷的基于jquery的页面特效
Dec 07 Javascript
javascript通过获取html标签属性class实现多选项卡的方法
Jul 27 Javascript
jQuery动态添加及删除表单上传元素的方法(附demo源码下载)
Jan 15 Javascript
EasyUI的doCellTip实现鼠标放到单元格上提示单元格内容
Aug 24 Javascript
微信小程序 二维码canvas绘制实例详解
Jan 06 Javascript
ES5学习教程之Array对象
Apr 01 Javascript
AngularJS读取JSON及XML文件的方法示例
May 25 Javascript
ionic 自定义弹框效果
Jun 27 Javascript
es6+angular1.X+webpack 实现按路由功能打包项目的示例
Aug 16 Javascript
一个基于react的图片裁剪组件示例
Apr 18 Javascript
Vue中的Props(不可变状态)
Sep 29 Javascript
微信小程序页面间值传递的两种方法
Nov 26 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
PHP 批量删除数据的方法分析
2009/10/30 PHP
php以post形式发送xml的方法
2014/11/04 PHP
PHP封装的多文件上传类实例与用法详解
2017/02/07 PHP
PHP调用API接口实现天气查询功能的示例
2017/09/21 PHP
javascript 对象的定义方法
2007/01/10 Javascript
jquery 应用代码 方便的排序功能
2010/02/06 Javascript
js模仿hover的具体实现代码
2013/12/30 Javascript
理解JavaScript的变量的入门教程
2015/07/07 Javascript
jQuery实现折叠、展开的菜单组效果代码
2015/09/16 Javascript
JS加载器如何动态加载外部js文件
2016/05/26 Javascript
通过扫描二维码打开app的实现代码
2016/11/10 Javascript
简单实现jQuery上传图片显示预览功能
2020/06/29 jQuery
jquery一键控制checkbox全选、反选或全不选
2017/10/16 jQuery
javascript和php使用ajax通信传递JSON的实例
2018/08/21 Javascript
简单了解vue中父子组件如何相互传递值(基础向)
2019/07/12 Javascript
[00:43]拉比克至宝魔导师密钥展示
2018/12/20 DOTA
python开发之list操作实例分析
2016/02/22 Python
PyQt5每天必学之日历控件QCalendarWidget
2018/04/19 Python
解决python删除文件的权限错误问题
2018/04/24 Python
Python实现的读取电脑硬件信息功能示例
2018/05/30 Python
查看TensorFlow checkpoint文件中的变量名和对应值方法
2018/06/14 Python
python cs架构实现简单文件传输
2020/03/20 Python
window7下的python2.7版本和python3.5版本的opencv-python安装过程
2019/10/24 Python
pyecharts绘制中国2020肺炎疫情地图的实例代码
2020/02/12 Python
Python脚本调试工具安装过程
2021/01/11 Python
Pandas数据分析的一些常用小技巧
2021/02/07 Python
大学新生入学教育方案
2014/05/16 职场文书
篝火晚会策划方案
2014/05/16 职场文书
员工团队活动方案
2014/08/28 职场文书
党员干部观看《周恩来四个昼夜》思想汇报
2014/09/10 职场文书
2014乡镇机关党员个人对照检查材料思想汇报
2014/10/09 职场文书
政风行风整改报告
2014/11/06 职场文书
2014年大学宣传部工作总结
2014/12/19 职场文书
故意杀人罪辩护词
2015/05/21 职场文书
2016年秋季运动会加油稿
2015/12/21 职场文书
Python+Matplotlib图像上指定坐标的位置添加文本标签与注释
2022/04/11 Python