宝哥软件园

深入探索seajs的模块化和加载方法

编辑:宝哥软件园 来源:互联网 时间:2021-09-09

因为一直在用,所以知道seajs的源代码。以下是我对以下问题的理解:

1.seajs的require(XXX)方法是如何实现模块加载的?

2.为什么需要预加载?

3.为什么需要构建工具?

4.施工前后的代码有什么区别,为什么要这样做?

问题SEAJS的require(XXX)方法是如何实现模块加载的?

代码的逻辑相对复杂,对源代码的理解放在文章的最后。在这里,我们将简单梳理一下模块加载的逻辑:

1.从seajs.use方法入口,开始加载要使用的模块。

2.要使用的模块此时不能存在于mod缓存中。Seajs创建了一个新的mod,并给出了一些初始状态。

3.执行mod.load方法

4.在一堆逻辑之后,转到seajs.request方法,请求模块文件。模块加载后,执行define方法。

5.define方法分析提取模块的相关模块并保存它们。缓存工厂,但不执行它。

6.模块的从属模块将被再次加载。如果有相关模块,请继续加载。直到所有相关模块都被加载。

7.加载所有模块后,执行use方法的回调。

8.模块的内部逻辑从回调开始。require方法仅在此过程中执行。

问题2:为什么需要预加载?

我们看到seajs.use方法实际上是在所有依赖模块都被加载之后执行回调的。可以理解,在执行业务逻辑代码之前,必须预加载所有相关模块代码。那么为什么要先预装这样的逻辑呢?

答案在于这个require方法的执行方法,它引用了逻辑代码中的其他模块方法:

var mod=require(id);

这个语法决定了mod的获取是一个同步执行的过程。如果之前没有预加载模块代码,只能通过异步加载回调来实现,整个seajs的执行逻辑会完全不同。因为异步,你无法理解模块的执行顺序,逻辑变得难以控制。

问题3:为什么需要构建工具?

可以看到,每个依赖模块在构建之前都是单独加载的。这会产生过多的模块请求,不利于页面的加载性能。本质上,构建工具是为了解决模块合并和加载的问题。

问题4:施工前后的代码有什么不同,为什么要这样做?

构建工具到底做了什么?我们说它本质上是解决代码合并和加载的问题,那么它所做的只是简单地把每个模块文件合并成一个文件?

当然不是。测试一下,如果你简单的把几个模块文件合并成一个文件,你会发现这个文件根本无法正常执行。

原因在于define方法的实现。

Seajs主张只通过在define方法中传入工厂参数来定义模块。回顾一下define方法内部,当没有传入id(相当于模块的url)时,将通过getCurrentScript()方法获取当前正在执行的模块文件的url路径,然后将该路径作为键值与模块本身一起缓存到cachedMods。这里的关键是整个seajs中的模块缓存机制实际上依赖于每个模块的url作为缓存的键值。归根结底,require(id)方法也是由url键值调用的。require(id)方法,归根结底,是通过url键值在cachedMods中找到相应的模块。这个键值不能重复,不能错,否则会混淆模块的对应关系。如果将模块文件A、B、C简单合并成一个目标文件X,getCurrentScript()只能得到X的路径,无法区分三个模块的键值,那么执行肯定会出错。

因此,如果您想将几个模块文件合并在一起,您必须为每个模块指定URIs。也就是说,定义方法必须全部传入id参数。当id被传入时,seajs会将这个id转换成url,并将其用作缓存的键值。

如果只传递了id和factory,即define(id,factory),那么deps=undefined,define方法将执行Parse Dependencies (factory。tostring())方法提取工厂中的依赖模块,然后它会去解析模块路径并在线单独加载每个模块的逻辑,从而失去了此时合并和加载的意义。

因此,在合并加载中,define方法必须正确传入三个参数:id、deps和factory,然后才能正确执行。

Seajs所谓的CMD的模块定义方法主张每个人在模块编写阶段只传递工厂的一个参数,另外两个参数在代码构建后期生成。以上解释了为什么这两个参数在施工后是必须的。

至于为什么提倡在定义模块时只通过factory,我认为主要是因为手工传入的id和deps参数容易出错,不方便维护。工具可以提高效率,确保参数正确。

附件:对seajs主要代码逻辑的理解。

说明:源代码版本为sea.js2.3.0。

1.让我们看看define方法做了什么

Module.define=function (id,deps,factory)

定义方法时,支持三个参数。其中id和deps是可选的。工厂必须。代码由以下逻辑控制:

事实上,deps是必要的,因为seajs必须知道每个模块依赖于哪些模块,否则它无法执行加载。

因此,当工厂是一个函数并且deps没有被主动传入时,就需要使用parseDependencies方法来分析工厂中的依赖模块。

ParseDependencies方法主要是用一个正则表达式从函数体中的all require(XXX)中提取XXX,也就是这个函数所依赖的所有模块。

方法本身并不复杂,但这个正则表达式并不简单:

分析deps后,将模块定义存储在缓存中:

注意,我们会发现define方法纯粹是一个分析模块,一个存储模块,没有执行模块。

2.真正的执行模块在require方法中。接下来让我们看看require。

简而言之,require方法是根据id在定义定义中存储的模块缓存中找到对应的模块,并执行它以获得模块定义返回的方法:

整个大步骤中有一个关键步骤,所以有必要详细说明:

Module.get(require.resolve(id)).

当你需要一个模块时,你必须首先找到这个模块。Module.get方法可以做到这一点。

如果缓存光盘中不存在该模块,请创建一个新模块并将其缓存在缓存光盘中:

在这种情况下,define和rquire方法并不复杂。Seajs主要是因为模块加载的逻辑有点复杂。

3.seajs真正执行的入口是使用方法:

使用方法从这里的id触发模块的加载和执行。

可以看到加载的关键点在于mod.load方法。

load方法代码有点长,主要逻辑是判断mod当前状态是加载还是正在加载。

在Module的舒适功能中,我们可以看到状态的默认值是0。

因此,所有尚未加载的新模块为:mod.status=STATUS。加载状态设置为加载,并执行后续加载逻辑。

下一步是获取模块的相关URL

mod.resolve方法:

Module.resolve方法本质上转换相对路径、配置路径、别名等。走进一条绝对的道路。没有代码了。

更新模块加载状态。

加载模块的逻辑:

主要是m.fetch方法,这里跳过了其他逻辑。

可以看到seajs.request最终会执行模块文件加载:

当所有相关模块都被加载时,执行mod的onload方法

下面是mod.onload()方法

至此,seajs的核心逻辑也差不多看到了。供参考,有些东西没有很好的理解或表达准确。请大家一起讨论。

以上就是本文的全部内容,希望大家喜欢。

更多资讯
游戏推荐
更多+