第四课:模块加载系统2

最近比较闲,我就讲下seajs的模块编译_compile过程。

这里紧接着第三课的例子来讲解。首先是a.js的编译

Module.prototype._compile = function() {
126     var module = this
127     // 如果该模块已经编译过,则直接返回module.exports
128     if (module.status === STATUS.COMPILED) {
129       return module.exports
130     }
133     //  1. the module file is 404.
134     //  2. the module file is not written with valid module format.
135     //  3. other error cases.
136     // 这里是处理一些异常情况,此时直接返回null
137     if (module.status < STATUS.SAVED && !hasModifiers(module)) {
138       return null
139     }
140     // 更改模块状态为COMPILING,表示模块正在编译
141     module.status = STATUS.COMPILING
142
143     // 模块内部使用,是一个方法,用来获取其他模块提供(称之为子模块)的接口,同步操作
144     function require(id) {
145         // 根据id解析模块的路径
146         var uri = resolve(id, module.uri)
147         // 从模块缓存中获取模块(注意,其实这里子模块作为主模块的依赖项是已经被下载下来的)
148         var child = cachedModules[uri]
149
150         // Just return null when uri is invalid.
151         // 如果child为空,只能表示参数填写出错导致uri不正确,那么直接返回null
152         if (!child) {
153           return null
154         }
155
156         // Avoids circular calls.
157         // 如果子模块的状态为STATUS.COMPILING,直接返回child.exports,避免因为循环依赖反复编译模块
158         if (child.status === STATUS.COMPILING) {
159           return child.exports
160         }
161         // 指向初始化时调用当前模块的模块。根据该属性,可以得到模块初始化时的Call Stack.
162         child.parent = module
163         // 返回编译过的child的module.exports
164         return child._compile()
165     }
166     // 模块内部使用,用来异步加载模块,并在加载完成后执行指定回调。
167     require.async = function(ids, callback) {
168       module._use(ids, callback)
169     }
170     // 使用模块系统内部的路径解析机制来解析并返回模块路径。该函数不会加载模块,只返回解析后的绝对路径。
171     require.resolve = function(id) {
172       return resolve(id, module.uri)
173     }
174     // 通过该属性,可以查看到模块系统加载过的所有模块。
175     // 在某些情况下,如果需要重新加载某个模块,可以得到该模块的 uri, 然后通过 delete require.cache[uri] 来将其信息删除掉。这样下次          使用时,就会重新获取。
176     require.cache = cachedModules
177
178     // require是一个方法,用来获取其他模块提供的接口。
179     module.require = require
180     // exports是一个对象,用来向外提供模块接口。
181     module.exports = {}
182     var factory = module.factory
183
184     // factory 为函数时,表示模块的构造方法。执行该方法,可以得到模块向外提供的接口。
185     if (util.isFunction(factory)) {
186       compileStack.push(module)
187       runInModuleContext(factory, module)
188       compileStack.pop()
189     }
190     // factory 为对象、字符串等非函数类型时,表示模块的接口就是该对象、字符串等值。
191     // 如:define({ "foo": "bar" });
192     // 如:define(‘I am a template. My name is {{name}}.‘);
193     else if (factory !== undefined) {
194       module.exports = factory
195     }
196
197     // 更改模块状态为COMPILED,表示模块已编译
198     module.status = STATUS.COMPILED
199     // 执行模块接口修改,通过seajs.modify()
200     execModifiers(module)
201     return module.exports
202   }
if (util.isFunction(factory)) {
186       compileStack.push(module)
187       runInModuleContext(factory, module)
188       compileStack.pop()
189     }
这里就是把module.export进行初始化。runInModuleContext方法:
// 根据模块上下文执行模块代码
489   function runInModuleContext(fn, module) {
490     // 传入与模块相关的两个参数以及模块自身
491     // exports用来暴露接口
492     // require用来获取依赖模块(同步)(编译)
493     var ret = fn(module.require, module.exports, module)
494     // 支持返回值暴露接口形式,如:
495     // return {
496     //   fn1 : xx
497     //   ,fn2 : xx
498     //   ...
499     // }
500     if (ret !== undefined) {
501       module.exports = ret
502     }
503   }
执行a.js中的function方法,这时会调用var b = require("b.js"),
require方法会返回b的compile方法的返回值,b模块中又有var c = require(‘c.js‘)。这时会调用c的compile方法,然后调用c的function,c中,如果要暴露对象,或者是return 对象c,则模块c的exports = c。或者直接是module.export = c;总之最后会返回module c.export = c;所以var c = module c.export = c,模块b中,就可以使用变量c调用模块c中的c对象的方法和属性。以此类推,最终a模块也能调用b模块中b对象的属性和方法。不管什么模块,只要使用了module.export = xx模块,其他模块就可以使用require("xx模块"),调用xx模块中的各种方法了。
最终模块的状态会变成module.status = STATUS.COMPILED。
Module.prototype._use = function(ids, callback) {  
  var uris = resolve(ids, this.uri);      //解析[‘./a‘,‘jquery‘]
    this._load(uris, function() {    //把解析出来的a,jquery模块的地址[url1,url2],调用_load方法。
                //util.map : 让数据成员全部执行一次一个指定的函数,并返回一个新的数组,该数组为原数组成员执行回调后的结果
      var args = util.map(uris, function(uri) {

         return uri ? cachedModules[uri]._compile() : null;//如果存在url,就调用_compile方法。
   })    if (callback) { callback.apply(null, args) }   })

}

这时args = [module a.export, module jquery.export];
seajs.use([‘./a‘,‘jquery‘],function(a,$){
    var num = a.a;
    $(‘#J_A‘).text(num);
})
这时function中的a和$就是module a.export和module jquery.export。

因为本人现在在研究jquery源码和jquery框架设计,因此共享一些经验:jquery源码,我在网上看了很多解析,看着看着就看不下去了。意义不大,推荐妙味课堂的jquery源码解析。

司徒正美的javascript框架设计,个人觉得难度大,但是精读后,你就是高级前端工程师了。

玉伯的sea.js,我建议去学习,去用,毕竟是中国人自己做的。我们公司新的项目或者重构,都会使用seajs来做。

接下来就是模块化handbars以及mvc的backbone或者mvvm的angular的源码精读。这里我希望有人给我提建议,看什么书,看什么网站,看什么视频能够快速的学习。

加油!
 





				
时间: 2024-07-29 08:56:39

第四课:模块加载系统2的相关文章

第三课:模块加载系统

模块加载,其实就是把js分成很多个模块,便于开发和维护.因此加载很多js模块的时候,需要动态的加载,以便提高用户体验. 在介绍模块加载库之前,先介绍一个方法. 动态加载js方法: function loadJs(url , callback){ var node = document.createElement("script"); node[window.addEventListener ? "onload":"onreadystatechange&qu

第三章:模块加载系统(requirejs)

任何一门语言在大规模应用阶段,必然要经历拆分模块的过程.便于维护与团队协作,与java走的最近的dojo率先引入加载器,早期的加载器都是同步的,使用document.write与同步Ajax请求实现.后来dojo开始以JSONP的方法设计它的每个模块结构.以script节点为主体加载它的模块.这个就是目前主流的加载器方式. 不得不提的是,dojo的加载器与AMD规范的发明者都是james Burke,dojo加载器独立出来就是著名的require.本章将深入的理解加载器的原理. 1.AMD规范

JavaScript架构设计 2.模块加载系统

2.1 AMD规范 AMD是"Asynchronous Module Definition"的缩写,意为"异步模块定义".重点有两个.异步--有效避免了采用同步加载方式中导致的页面假死现象.模块定义--每个模块必须按照一定的格式编写.主要接口有两个,define与require.define是模块开发者关注的方法,require是模块使用者关注的方法. define的参数情况为define(id?,deps?,factory).第一个为模块ID,第2个为依赖列表,第

关于javascript模块加载技术的一些思考

前不久有个网友问我在前端使用requireJs和seajs的问题,我当时问他你们公司以前有没有自己编写的javascript库,或者javascript框架,他的回答是什么都没有,他只是听说像requirejs和seajs是新东西新技术,很有价值所以想用它. 这位网友的问题引起了我对javascript模块加载技术的思考,上篇文章我给出了自己写的一个javascript库的基本结构,其实写这篇文章的一个起因就是因为我想使用requirejs或者seajs这样的技术来重新设计我写javascrip

Angular Material串串学客户端开发 2 - Node.js模块加载机制Require()

题外话解一下博客标题,因为第一篇文章评论中,有人质疑离题很远,说了半天和Angular Material没有半毛关系.其实我的的中心在后半句<串串学客户端开发>. require() 不要把这里的Require()和RequireJS混为一谈.不过有意思的是,Typescript的模块定义,甚至同时支持这两种模块机制. 导入和使用外部模块,只是简单的一句require(),看看angular/material/docs下的编译文件gulpfile.js的代码片段.对模块导入和使用有个直观的感觉

JavaScript之:模块加载程序的历史与背景

原文:History and Background of JavaScript Module Loaders 作者:Elias Carlston 翻译:leotso 介绍 Web 应用程序的应用程序逻辑不断从后端移到浏览器端.但是,由于富客户端 JavaScript 应用程序的规模变得更大,它们遇到了类似于多年来传统应用所面临的挑战:共享代码以便重用,同时保持架构的隔离分层,并且足够灵活以便于轻松扩展. 这些挑战的一个解决方案是开发 JavaScript 模块和模块加载系统.这篇文章将着重于比较

自研模块加载器(四) 模块资源定位-异步加载

资源定位-动态加载 通过resolve方法进行异步解析,完整解析如下图所示: 根据上篇文章startUp.js代码,我们继续完善本章动态加载资源的代码. (function(global) { var startUp = global.startUp = { version: '1.0.1' } var data = {}; // 获取当前模块加载器配置信息 var cache = {}; // 缓存 //模块的生命周期 var status = { FETCHED: 1, SAVED: 2,

[译文]JOAL教程 第六课 高级加载方式与错误处理

[译文]JOAL教程 原文地址:http://jogamp.org/joal-demos/www/devmaster/lesson6.html 原文作者:Athomas Goldberg 译文:三向板砖 转载请保留以上信息. 本次课程对应的学习笔记:http://blog.csdn.net/shuzhe66/article/details/40303739 第六课 高级加载方式与错误处理 本文是DevMaster.net(http://devmaster.net/)的OpenAL教程对应的JOA

Node.js【6】Web开发、进阶(模块加载、控制流、部署、弊端)

笔记来自<Node.js开发指南>BYVoid编著 实现过程:https://github.com/ichenxiaodao/express-example 第5章 使用Node.js进行Web开发 从零开始用Node.js实现一个微博系统,功能包括路由控制.页面模板.数据库访问.用户注册.登录.用户会话等内容. 会介绍Express框架.MVC设计模式.ejs模板引擎以及MongoDB数据库的操作. 5.1.准备工作 Express(http://expressjs.com/)除了为http