源码网商城,靠谱的源码在线交易网站 我的订单 购物车 帮助

源码网商城

javascript框架设计读书笔记之模块加载系统

  • 时间:2020-01-02 06:21 编辑: 来源: 阅读:
  • 扫一扫,手机访问
摘要:javascript框架设计读书笔记之模块加载系统
模块加载,其实就是把js分成很多个模块,便于开发和维护。因此加载很多js模块的时候,需要动态的加载,以便提高用户体验。 在介绍模块加载库之前,先介绍一个方法。 [b]动态加载js方法:[/b]
[url=http://localhost/test/SEAJS/a.js]http://localhost/test/SEAJS/a.js[/url]为例 接下来 : 首先会创建一个Module:
[url=http://ux.sohu.com/topics/50972d9ae7de3e752e0081ff]http://ux.sohu.com/topics/50972d9ae7de3e752e0081ff[/url],这里我说下额外的问题,大家可能知道为什么我们要少用table来布局,因为table在呈现树布局的时候,需要多次计算,而div只需要一次。同时,美的电商面试官告诉我:table需要全部解析完才会显示出来,而div解析多少就显示多少。经查证table中如果有tbody标签,就会按照tbody来分段显示。因此在IE6,7,8中,如果你用innerHTML来创建一个"<table></table>",会自动在里面添加<tbody></tbody>。)。 下载成功后,就会解析执行,执行的是define方法。这里会先执行a模块的代码。 define(id,deps,function(){})方法解析
[u]复制代码[/u] 代码如下:
//define 定义 ,id : 模块id , deps : 模块依赖 , factory   Module._define = function(id, deps, factory) {    //解析依赖关系 // 如果deps不是数组类型,同时factory是函数    if (!util.isArray(deps) && util.isFunction(factory)) { // 函数体内正则匹配require字符串,并形成数组返回赋值给deps      deps = util.parseDependencies(factory.toString())    }   //设置元信息    var meta = { id: id, dependencies: deps, factory: factory }     if (document.attachEvent) {      // 得到当前script的节点      var script = util.getCurrentScript()        // 如果script节点存在      if (script) {          // 得到原始uri地址          derivedUri = util.unParseMap(util.getScriptAbsoluteSrc(script)) }          if (!derivedUri) {              util.log('Failed to derive URI from interactive script for:', factory.toString(), 'warn')          }      }  .........  }
define首先会对factory执行一个判断 ,判断它是否为一个函数(原因是因为define内也可以包括文件,对象) 如果是函数 , 那么 就会通过factory.toString(),得到函数,并通过正则匹配得 a.js的依赖,并把依赖保存在 deps 中 对于 a.js 而言, 它的依赖 是 b.js 所以 deps为 ['./b'] 并对 a.js 的信息进行保存 var meta = { id: id, dependencies: deps, factory: factory } 针对a.js meta = { id : undefined , dependencies : ['./b'] , factory : function(xxx){xxx}} 在 ie 6-9 浏览器中可以拿到当前运行js的路径 但是在标准浏览器中 ,这不可行 ,所以暂时先把元信息赋值给anonymousModuleMeta = meta。 然后触发onload,这时就会调用回调方法callback3,此回调方法就会修改当前回调模块(a.js)的状态值,将其设置为 module.status = STATUS.FETCHED。 再接下来 ,将统一 执行回调队列 callbackList 中的 a.js所对应的回调,也就是onFetched。 onFetched方法会检查a模块是否有依赖模块,因为a依赖于b,所以对模块a所依赖的b.js 执行_load()。 会去下载b模块,这时会先执行jquery的define方法。因为jquery没依赖模块,所以onload回调后。onFetched调用cb方法。 当b按照a一样的过程实现后,就会下载c模块。最终c,b,a模块都下载执行define,并onload结束后,也会调用cb方法,(先c,再b,后c) 所有模块都为ready之后,就会调用callback2方法。 最终回调到callback2,执行a和jquery模块的_compile方法: 首先编译a.js模块,模块a的function执行,因为a里面有require(b.js),因此会去执行b模块的function. 模块 a 的function开始执行 模块 b 的function开始执行 模块 c 的function开始执行 模块 c 的function执行完毕 模块 b 的function执行完毕 模块 a 的function执行完毕 最后执行jquery的function。 编译结束后,就执行callback1,就可以使用a和jquery对象了。 PS:seajs版本已经更新,现在没有_compile方法了。(大家自行去看,我也要去看下) 接着讲下seajs的模块编译_compile过程。 首先是a.js的编译
[u]复制代码[/u] 代码如下:
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   }
[u]复制代码[/u] 代码如下:
if (util.isFunction(factory)) { 186       compileStack.push(module) 187       runInModuleContext(factory, module) 188       compileStack.pop() 189     }
这里就是把module.export进行初始化。runInModuleContext方法:
[u]复制代码[/u] 代码如下:
// 根据模块上下文执行模块代码 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。
[u]复制代码[/u] 代码如下:
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];
[u]复制代码[/u] 代码如下:
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的源码精读。这里我希望有人给我提建议,看什么书,看什么网站,看什么视频能够快速的学习。
  • 全部评论(0)
联系客服
客服电话:
400-000-3129
微信版

扫一扫进微信版
返回顶部