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

源码网商城

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

  • 时间:2021-12-12 14:43 编辑: 来源: 阅读:
  • 扫一扫,手机访问
摘要:深入探寻seajs的模块化与加载方式
由于一直在使用,所以了解了下seajs的源代码。这里是我对下面几个问题的理解: 1、seajs的require(XXX)的方法是怎样实现模块加载的? 2、为什么需要预加载? 3、为什么需要构建工具? 4、构建前后的代码究竟有些什么区别,为什么要这么做? [b]问题1: seajs的require(XXX)的方法是怎样实现模块加载的?[/b] 代码逻辑比较绕,对源代码的理解放在文章的末尾,这里先简单梳理下模块加载的逻辑: 1、从seajs.use方法入口,开始加载use到的模块。 2、use到的模块这时mod缓存当中一定是不存在的。seajs创建一个新的mod,赋予一些初始的状态。 3、执行mod.load方法 4、一堆逻辑之后走到seajs.request方法,请求模块文件。模块加载完成之后,执行define方法。 5、define方法分析提取模块的依赖模块,保存起来。缓存factory但不执行。 6、模块的依赖模块再被加载,如果继续有依赖模块,则继续加载。直至所有被依赖的模块都加载完毕。 7、所有的模块加载完毕之后,执行use方法的callback. 8、模块内部逻辑从callback开始执行。require方法在这个过程当中才被执行。 [b]问题2:为什么需要预加载?[/b] 我们看到seajs.use方法实际上是在所有依赖模块都加载完了之后才执行callback。可以理解成在业务逻辑代码在执行之前,必须先预加载所有被依赖的模块代码。那么为什么是一个这样必须先做预加载的逻辑? 答案在于逻辑代码里面引用其他模块方法的这个require方法的执行方法: var mod = require(id); 这个语法决定了mod的取得是个同步执行的过程,如果模块代码在此之前没有被预加载的话,就只能采用异步加载回调的方法来实现了,那么整个seajs的执行逻辑将完全会是另一个样子。因为异步你会搞不懂模块的执行顺序,逻辑会变的难以掌控。 [b]问题3:为什么需要构建工具? [/b] 可以看到没有构建前各个依赖模块都是单独加载的。这会产生过多的模块请求,对于页面的加载性能是不利的。构建工具本质上就是为了解决模块合并加载的问题。 [b]问题4:构建前后的代码究竟有些什么区别,为什么要这么做?[/b] 构建工具究竟做了些什么。我们说它本质上是为了解决代码合并加载的问题,那么它所做的只是简单的将各个模块文件合并成一个文件? 当然不是。测试一下,你如果只是简单把几个模块文件合并到一个文件以后,会发现这个文件根本没有办法正常执行。 原因在于define方法的实现。 seajs是推崇定义模块的时候只在define方法传入factory参数的。回顾define方法内部,当没有传入id(姑且等同于模块的url)时,会通过getCurrentScript()方法去取得当前正在执行的这个模块文件的url路径,然后把这个路径作为键值与模块本身一起缓存到cachedMods。这里很关键的一点是,整个seajs内部的这个模块缓存机制其实是依赖每个模块的url来做缓存的键值。require(id)方法,归根结底也是通过url键值到。require(id)方法,归根结底也是通过url键值到cachedMods里面去找相应的模块。这个键值不能重复不能出错,不然模块的对应关系就混乱了。如果把a、b、c几个模块文件简单合并到一个目标文件x之后,getCurrentScript()只能获取到x的路径,三个模块的键值就没法做出区别了,执行肯定出错。 所以如果要把几个模块文件合并在一起,就必须为每个模块明确uri。也就是define方法必须都传入id参数。当id传入的时候,seajs会将这个id转换为url用作缓存的键值。 如果只传id和factory,也就是 define(id, factory),那么deps = undefined,define方法就会去执行parseDependencies(factory.toString())方法提取factory里面的依赖模块,后续会走到解析模块路径,线上单独加载各个模块的逻辑里面去,这个时候就失去了合并加载的意义了。 所以合并加载,define方法必须正确的传入id,deps,factory三个参数才能正确执行。 seajs 所谓CMD的模块定义方法,是提倡大家写模块的阶段都只传factory一个参数的,其他两个参数在后期代码构建的阶段来生成。上面解释了为什么这两参数在构建后是必须的。 至于为什么提倡定义模块的时候只传factory,我看主要是因为手工传入的id和deps参数,极易出错,不便维护。工具可以提高效率并保证参数的正确。 [b]附: 对seajs 主要代码逻辑的理解。[/b] 说明:源代码版本是Sea.js 2.3.0 1、先看看define方法做了些什么 Module.define = function (id, deps, factory) define方法的时候,支持三个参数。其中id,deps是选填的。factory必须。代码里面通过以下逻辑来控制: [img]http://files.jb51.net/file_images/article/201504/2015041409341443.png[/img] 但其实deps是必须的,因为seajs必须知道每个模块依赖了哪些模块,不然无法执行加载。 所以,当factory是函数,并且deps没有被主动传入的时候,就需要使用parseDependencies方法来分析出factory当中的依赖模块了。 [img]http://files.jb51.net/file_images/article/201504/2015041409341444.png[/img] parseDependencies方法做的事情主要就是用一个正则表达式把函数体里面所有require(XXX)里面的XXX提取出来,这也就是这个函数依赖到的所有模块了。 [img]http://files.jb51.net/file_images/article/201504/2015041409341545.png[/img] 方法本身不复杂,但是这个正则表达式不简单: 分析完deps之后,将模块定义存入缓存: [img]http://files.jb51.net/file_images/article/201504/2015041409341546.png[/img] 注意,我们会发现define方法纯粹只是分析模块、存储模块,并没有执行模块。 2、真正执行模块,是在require方法里面。我们接下来看require。 [img]http://files.jb51.net/file_images/article/201504/2015041409341547.png[/img] [img]http://files.jb51.net/file_images/article/201504/2015041409341548.png[/img] 简而言之require方法就是根据id在define定义存储的模块缓存中找到相应的模块,并执行它,获得模块定义返回的方法: [img]http://files.jb51.net/file_images/article/201504/2015041409341549.png[/img] 整个这个大步骤中,有一个很关键的步骤,有必要细说: Module.get(require.resolve(id))。 require一个模块的时候,首先要找到这个模块。 Module.get方法就起这个作用。 [img]http://files.jb51.net/file_images/article/201504/2015041409341550.png[/img] cachedMods里面没有的话,就创建一个新的Module并缓存到cachedMods里面: [img]http://files.jb51.net/file_images/article/201504/2015041409341551.png[/img] define和rquire方法这样看来不算复杂。seajs主要还是模块加载的逻辑有点复杂。 3、seajs真正执行的入口,是use方法: [img]http://files.jb51.net/file_images/article/201504/2015041409341652.png[/img] 通过use方法,从这里的ids开始触发模块的加载和执行。 [img]http://files.jb51.net/file_images/article/201504/2015041409341653.png[/img] 可以看到加载的关键点在mod.load方法。 load方法代码有点长,其中的主要逻辑是:判断mod的当前状态是否为已加载或者加载中。 [img]http://files.jb51.net/file_images/article/201504/2015041409341654.png[/img] 在Module的舒适化函数中,我们可以看到status默认值是0. [img]http://files.jb51.net/file_images/article/201504/2015041409341655.png[/img] 所以没有加载过的新模块,到这里都是: mod.status = STATUS.LOADING 状态设置为加载中,并执行后续加载逻辑。 接来下是获取模块的依赖urls [img]http://files.jb51.net/file_images/article/201504/2015041409341656.png[/img] mod.resolve方法: [img]http://files.jb51.net/file_images/article/201504/2015041409341657.png[/img] Module.resolve方法本质上就是把相对路径、配置的path、别名等转换成一个绝对路径。不贴代码了。 更新模块加载状态。 [img]http://files.jb51.net/file_images/article/201504/2015041409341658.png[/img] 加载模块的逻辑: [img]http://files.jb51.net/file_images/article/201504/2015041409341659.png[/img] 主要是m.fetch方法,里面其他逻辑这里略过。 [img]http://files.jb51.net/file_images/article/201504/2015041409341760.png[/img] 可以看到 seajs.request最终会去执行模块文件的加载: [img]http://files.jb51.net/file_images/article/201504/2015041409341761.png[/img] 当所有依赖模块加载完了之后,执行mod的onload方法 [img]http://files.jb51.net/file_images/article/201504/2015041409341762.png[/img] 这里是 mod.onload()方法 [img]http://files.jb51.net/file_images/article/201504/2015041409341763.png[/img] 到此,seajs的核心逻辑就差不多都看到了。供参考,有理解不到位或者表达不准确的地方,欢迎一起探讨。 以上所述就是本文的全部内容了,希望大家能够喜欢。
  • 全部评论(0)
联系客服
客服电话:
400-000-3129
微信版

扫一扫进微信版
返回顶部