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的一些重要关键字或者函数,如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依赖模块那部分实现中,技巧性十足。以上就是本文的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。

(0)

相关推荐

  • seajs1.3.0源码解析之module依赖有序加载

    这里是seajs loader的核心部分,有些IE兼容的部分还不是很明白,主要是理解各个模块如何依赖有序加载,以及CMD规范. 代码有点长,需要耐心看: 复制代码 代码如下: /** * The core of loader */ ;(function(seajs, util, config) { // 模块缓存 var cachedModules = {} // 接口修改缓存 var cachedModifiers = {} // 编译队列 var compileStack = [] // 模

  • 深入探寻seajs的模块化与加载方式

    由于一直在使用,所以了解了下seajs的源代码.这里是我对下面几个问题的理解: 1.seajs的require(XXX)的方法是怎样实现模块加载的? 2.为什么需要预加载? 3.为什么需要构建工具? 4.构建前后的代码究竟有些什么区别,为什么要这么做? 问题1: seajs的require(XXX)的方法是怎样实现模块加载的? 代码逻辑比较绕,对源代码的理解放在文章的末尾,这里先简单梳理下模块加载的逻辑: 1.从seajs.use方法入口,开始加载use到的模块. 2.use到的模块这时mod缓

  • seajs实现强制刷新本地缓存的方法分析

    本文实例讲述了seajs实现强制刷新本地缓存的方法.分享给大家供大家参考,具体如下: 1.为什么 由于每次上传js文件到服务器后用户本机存在本地缓存,导致用户需要强制清除缓存或者等待缓存失效才能使用新功能,极其不友好. 2.原理 seajs配置参数中有map属性为文件映射功能,其作用是通过seajs加载的文件映射为一个新的名称加载,形如 var version="0.0.1"; seajs.config({//seajs配置声明 map:[ [".js","

  • seajs中模块的解析规则详解和模块使用总结

    seajs github 模块标识已经说的相对清楚了.但并没有面面俱到,特别是当你需要手写 [模块ID]和[模块依赖]的时候,或者自己写自动化工具来做 transport 的时候(ps:spm貌似适应性不是很强也不易用,毕竟每个项目的目录结构可能相差很大,且不易改变.当然如果他的定位是包管理工具就别指望它来做你的项目的自动化构建工具了),ID的解析规则就需要了解透彻了.注意事项:1. 顶级标识始终相对 base 基础路径解析.2. 绝对路径和根路径始终相对当前页面解析.3. require 和

  • SeaJS 与 RequireJS 的差异对比

    "历史不是过去,历史正在上演.随着 W3C 等规范.以及浏览器的飞速发展,前端的模块化开发会逐步成为基础设施.一切终究都会成为历史,未来会更好."--引用玉伯原文最后一段话,我个人也非常赞同.既然谈到了"未来",我个人认为:前端 js 模块如果继续发展,其模块格式很可能会成为未来 WEB 一种标准规范,产生多种实现方式.就好比 JSON 格式一样,最终成为标准.被浏览器原生实现. 谁更有能成为未来的异步模块标准?SeaJS 遵循 CMD 规范,RequireJS 遵

  • SeaJS入门教程系列之完整示例(三)

    一个完整的例子上文说了那么多,知识点比较分散,所以最后我打算用一个完整的SeaJS例子把这些知识点串起来,方便朋友们归纳回顾.这个例子包含如下文件: 1.index.html--主页面.2.sea.js--SeaJS脚本.3.init.js--init模块,入口模块,依赖data.jquery.style三个模块.由主页面载入.4.data.js--data模块,纯json数据模块,由init载入.5.jquery.js--jquery模块,对 jQuery库的模块化封装,由init载入.6.s

  • Sea.JS知识总结

    SeaJS是一个遵循CommonJS规范的JavaScript模块加载框架.是一款现代的用于Web开发的模块加载工具,提供简单.极致的模块化体验.Sea.js 由阿里.腾讯等公司共同维护. 使用 Sea.js的好处: Sea.js 追求简单.自然的代码书写和组织方式,具有以下核心特性: 简单友好的模块定义规范:Sea.js 遵循 CMD 规范,可以像 Node.js 一般书写模块代码. 自然直观的代码组织方式:依赖的自动加载.配置的简洁清晰,可以让我们更多地享受编码的乐趣. Sea.js 还提供

  • 把jQuery的类、插件封装成seajs的模块的方法

    注:本文使用的seajs版本是2.1.1一.把Jquery封装成seajs的模块 复制代码 代码如下: define(function () { //这里放置jquery代码 把你喜欢的jquery版本放进来就好了 return $.noConflict();}); 调用方法:这样引进就可以像以前一样使用jquery 复制代码 代码如下: define(function (require, exports, module) {    var $ = require('./js/jquery');

  • SeaJS入门教程系列之SeaJS介绍(一)

    前言SeaJS是一个遵循CommonJS规范的JavaScript模块加载框架,可以实现JavaScript的模块化开发及加载机制.与jQuery等JavaScript框架不同,SeaJS不会扩展封装语言特性,而只是实现JavaScript的模块化及按模块加载.SeaJS的主要目的是令JavaScript开发模块化并可以轻松愉悦进行加载,将前端工程师从繁重的JavaScript文件及对象依赖处理中解放出来,可以专注于代码本身的逻辑.SeaJS可以与jQuery这类框架完美集成.使用SeaJS可以

  • SeaJS入门教程系列之使用SeaJS(二)

    下载及安装 要在项目中使用SeaJS,你所有需要做的准备工作就是下载sea.js然后放到你项目的某个位置.SeaJS项目目前托管在GitHub上,主页为 https://github.com/seajs/seajs/ .可以到其git库的build目录下下载sea.js(已压缩)或sea-debug.js(未压缩).下载完成后放到项目的相应位置,然后在页面中通过<script>标签引入,你就可以使用SeaJS了. SeaJS基本开发原则 在讨论SeaJS的具体使用前,先介绍一下SeaJS的模块

  • Seajs的学习笔记

    1.简介 Seajs,一个Web模块加载框架,追求简单.自然的代码书写和组织方式,:Sea.js 遵循 CMD 规范,模块化JS代码.依赖的自动加载.配置的简洁清晰,可以让程序员更多地专注编码. 2.优缺点 优点:1).提高可维护性.2).模块化编程.3).动态加载,前端性能优化 缺点:1).学习文档偏少且混乱,会更改团队使用JS的编写习惯,必须使用模块化编程.2).不太适合团队目前的情况,多JS文件但少改动,动态加载优势和模块化优势不明显.3). 需要配套使用SPM工具,JS的打包和管理工具.

  • seaJs的模块定义和模块加载浅析

    SeaJS 是由玉伯开发的一个遵循 CommonJS 规范的模块加载框架,可用来轻松愉悦地加载任意 JavaScript 模块和css模块样式.SeaJS非常小巧,小巧在于压缩和gzip后体积只有4K,而且接口和方法也非常少,SeaJS 就两个核心:模块定义和 模块的加载及依赖关系.SeaJS非常强大,SeaJS可以加载任意 JavaScript 模块和css模块样式,SeaJS会保证你在使用一个模块时,已经将所依赖的其他模块载入到脚本运行环境中.玉伯的说法,SeaJS可以让你享受写代码的乐趣,

随机推荐