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 相关文章推荐
jquery cookie实现的简单换肤功能适合小网站
Aug 25 Javascript
Javascript基础知识(一)核心基础语法与事件模型
Sep 29 Javascript
bootstrap表单按回车会自动刷新页面的解决办法
Mar 08 Javascript
js传递数组参数到后台controller的方法
Mar 29 Javascript
详解vue如何使用rules对表单字段进行校验
Oct 17 Javascript
详解angular2 控制视图的封装模式
Dec 27 Javascript
简单了解JavaScript中常见的反模式
Jun 21 Javascript
浅析webpack-bundle-analyzer在vue-cli3中的使用
Oct 23 Javascript
vue动态禁用控件绑定disable的例子
Oct 28 Javascript
Vue基于iview实现登录密码的显示与隐藏功能
Mar 06 Javascript
JavaScript中的各种宽高属性的实现
May 08 Javascript
Axios取消重复请求的方法实例详解
Jun 15 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下使用iconv需要注意的问题
2010/11/20 PHP
php增删改查示例自己写的demo
2013/09/04 PHP
PHP.ini安全配置检测工具pcc简单介绍
2015/07/02 PHP
8个必备的PHP功能开发
2015/10/02 PHP
php is_executable判断给定文件名是否可执行实例
2016/09/26 PHP
lib.utf.js
2007/08/21 Javascript
JQuery优缺点分析说明
2011/04/10 Javascript
js的压缩及jquery压缩探讨(提高页面加载性能/保护劳动成果)
2013/01/29 Javascript
Js 代码中,ajax请求地址后加随机数防止浏览器缓存的原因
2013/05/07 Javascript
js实现二级菜单渐隐显示
2015/11/03 Javascript
原生JS实现图片左右轮播
2016/12/30 Javascript
浅谈jQuery中的$.extend方法来扩展JSON对象
2017/02/12 Javascript
JS中IP地址与整数相互转换的实现代码
2017/04/10 Javascript
Angular 4 指令快速入门教程
2017/06/07 Javascript
微信小程序 配置顶部导航条标题颜色的实现方法
2017/09/20 Javascript
jQuery判断网页是否已经滚动到浏览器底部的实现方法
2017/10/27 jQuery
微信小程序中如何计算距离某个节日还有多少天
2019/07/15 Javascript
Vue简单封装axios之解决post请求后端接收不到参数问题
2020/02/16 Javascript
[43:03]完美世界DOTA2联赛PWL S2 PXG vs Magma 第二场 11.21
2020/11/24 DOTA
python实现逻辑回归的方法示例
2017/05/02 Python
使用pandas把某一列的字符值转换为数字的实例
2019/01/29 Python
python 判断文件还是文件夹的简单实例
2019/06/10 Python
PyQt5如何将.ui文件转换为.py文件的实例代码
2020/05/26 Python
Python函数参数分类原理详解
2020/05/28 Python
Python 实现一个简单的web服务器
2021/01/03 Python
Numpy ndarray 多维数组对象的使用
2021/02/10 Python
教你如何一步一步用Canvas写一个贪吃蛇
2018/10/22 HTML / CSS
自动化专业个人求职信范文
2013/12/30 职场文书
中专生毕业个人鉴定
2014/02/26 职场文书
公益广告宣传方案
2014/02/28 职场文书
婚礼答谢宴主持词
2014/03/14 职场文书
乡镇安全生产月活动总结
2015/05/08 职场文书
会计专业2019暑假实习报告
2019/06/21 职场文书
2019年度开业庆典祝福语大全!
2019/07/05 职场文书
vue使用Google Recaptcha验证的实现示例
2021/08/23 Vue.js
最新最全的手机号验证正则表达式
2022/02/24 Javascript