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


Posted in Javascript onApril 14, 2015

由于一直在使用,所以了解了下seajs的源代码。这里是我对下面几个问题的理解:

1、seajs的require(XXX)的方法是怎样实现模块加载的?

2、为什么需要预加载?

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

4、构建前后的代码究竟有些什么区别,为什么要这么做?

问题1: seajs的require(XXX)的方法是怎样实现模块加载的?

代码逻辑比较绕,对源代码的理解放在文章的末尾,这里先简单梳理下模块加载的逻辑:

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方法在这个过程当中才被执行。

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

我们看到seajs.use方法实际上是在所有依赖模块都加载完了之后才执行callback。可以理解成在业务逻辑代码在执行之前,必须先预加载所有被依赖的模块代码。那么为什么是一个这样必须先做预加载的逻辑?

答案在于逻辑代码里面引用其他模块方法的这个require方法的执行方法:

var mod = require(id);

这个语法决定了mod的取得是个同步执行的过程,如果模块代码在此之前没有被预加载的话,就只能采用异步加载回调的方法来实现了,那么整个seajs的执行逻辑将完全会是另一个样子。因为异步你会搞不懂模块的执行顺序,逻辑会变的难以掌控。

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

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

问题4:构建前后的代码究竟有些什么区别,为什么要这么做?

构建工具究竟做了些什么。我们说它本质上是为了解决代码合并加载的问题,那么它所做的只是简单的将各个模块文件合并成一个文件?

当然不是。测试一下,你如果只是简单把几个模块文件合并到一个文件以后,会发现这个文件根本没有办法正常执行。

原因在于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参数,极易出错,不便维护。工具可以提高效率并保证参数的正确。

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

说明:源代码版本是Sea.js 2.3.0

1、先看看define方法做了些什么

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

define方法的时候,支持三个参数。其中id,deps是选填的。factory必须。代码里面通过以下逻辑来控制:

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

但其实deps是必须的,因为seajs必须知道每个模块依赖了哪些模块,不然无法执行加载。

所以,当factory是函数,并且deps没有被主动传入的时候,就需要使用parseDependencies方法来分析出factory当中的依赖模块了。

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

parseDependencies方法做的事情主要就是用一个正则表达式把函数体里面所有require(XXX)里面的XXX提取出来,这也就是这个函数依赖到的所有模块了。

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

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

分析完deps之后,将模块定义存入缓存:

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

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

2、真正执行模块,是在require方法里面。我们接下来看require。

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

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

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

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

整个这个大步骤中,有一个很关键的步骤,有必要细说:

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

require一个模块的时候,首先要找到这个模块。 Module.get方法就起这个作用。

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

cachedMods里面没有的话,就创建一个新的Module并缓存到cachedMods里面:

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

define和rquire方法这样看来不算复杂。seajs主要还是模块加载的逻辑有点复杂。

3、seajs真正执行的入口,是use方法:

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

通过use方法,从这里的ids开始触发模块的加载和执行。

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

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

load方法代码有点长,其中的主要逻辑是:判断mod的当前状态是否为已加载或者加载中。

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

在Module的舒适化函数中,我们可以看到status默认值是0.

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

所以没有加载过的新模块,到这里都是: mod.status = STATUS.LOADING 状态设置为加载中,并执行后续加载逻辑。

接来下是获取模块的依赖urls

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

mod.resolve方法:

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

Module.resolve方法本质上就是把相对路径、配置的path、别名等转换成一个绝对路径。不贴代码了。

更新模块加载状态。

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

加载模块的逻辑:

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

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

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

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

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

当所有依赖模块加载完了之后,执行mod的onload方法

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

这里是 mod.onload()方法

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

到此,seajs的核心逻辑就差不多都看到了。供参考,有理解不到位或者表达不准确的地方,欢迎一起探讨。

以上所述就是本文的全部内容了,希望大家能够喜欢。

Javascript 相关文章推荐
javascript之函数直接量(function(){})()
Jun 29 Javascript
Jquery提交表单 Form.js官方插件介绍
Mar 01 Javascript
Jquery使用Firefox FireBug插件调试Ajax步骤讲解
Dec 02 Javascript
使用js画图之圆、弧、扇形
Jan 12 Javascript
浅谈利用JavaScript进行的DDoS攻击原理与防御
Jun 04 Javascript
React Router基础使用
Jan 17 Javascript
基于Vue过渡状态实例讲解
Sep 14 Javascript
Bootstrap Table实现定时刷新数据的方法
Aug 13 Javascript
利用JavaScript的Map提升性能的方法详解
Aug 14 Javascript
vue Tab切换以及缓存页面处理的几种方式
Nov 05 Javascript
JS中准确判断变量类型的方法
Jun 01 Javascript
vue+element_ui上传文件,并传递额外参数操作
Dec 05 Vue.js
javascript数组去重的方法汇总
Apr 14 #Javascript
JavaScript字符串常用类使用方法汇总
Apr 14 #Javascript
JavaScript 表单处理实现代码
Apr 13 #Javascript
JavaScript 事件绑定及深入
Apr 13 #Javascript
JavaScript 事件对象介绍
Apr 13 #Javascript
JavaScript 事件入门知识
Apr 13 #Javascript
JavaScript 动态加载脚本和样式的方法
Apr 13 #Javascript
You might like
php中使用url传递数组的方法
2015/02/11 PHP
求帮忙修改个php curl模拟post请求内容后并下载文件的解决思路
2015/09/20 PHP
php实时倒计时功能实现方法详解
2017/02/27 PHP
PHP等比例压缩图片的实例代码
2018/07/26 PHP
PDO::getAvailableDrivers讲解
2019/01/28 PHP
JavaScript 一行代码,轻松搞定浮动快捷留言-V2升级版
2010/04/02 Javascript
javascript使用onclick事件改变选中行的颜色
2013/12/30 Javascript
JS实现的简洁纵向滑动菜单(滑动门)效果
2015/10/19 Javascript
快速学习AngularJs HTTP响应拦截器
2015/12/31 Javascript
JS+CSS实现闪烁字体效果代码
2016/04/05 Javascript
jQuery实现表格与ckeckbox的全选与单选功能
2016/11/24 Javascript
jQuery的中 is(':visible') 解析及用法(必看)
2017/02/12 Javascript
详解Node.js开发中的express-session
2017/05/19 Javascript
使用vue-cli(vue脚手架)快速搭建项目的方法
2018/05/21 Javascript
[03:07]2015国际邀请赛选手档案EHOME.rOtK 是什么让他落泪?
2015/07/31 DOTA
[01:01:51]EG vs VG Supermajor小组赛B组 BO3 第二场 6.2
2018/06/03 DOTA
[00:10]DOTA2全国高校联赛速递
2018/05/30 DOTA
python 快速排序代码
2009/11/23 Python
Python multiprocessing.Manager介绍和实例(进程间共享数据)
2014/11/21 Python
简单理解Python中的装饰器
2015/07/31 Python
使用Python求解最大公约数的实现方法
2015/08/20 Python
利用Python抓取行政区划码的方法
2016/11/28 Python
使用TensorFlow实现SVM
2018/09/06 Python
Python django框架应用中实现获取访问者ip地址示例
2019/05/17 Python
新手如何发布Python项目开源包过程详解
2019/07/11 Python
python调用支付宝支付接口流程
2019/08/15 Python
Python大数据之使用lxml库解析html网页文件示例
2019/11/16 Python
django inspectdb 操作已有数据库数据的使用步骤
2021/02/07 Python
TripAdvisor瑞典:全球领先的旅游网站
2017/12/11 全球购物
城市规划毕业生求职信
2013/10/10 职场文书
大学四年职业生涯规划书范文
2014/01/02 职场文书
新农村建设典型材料
2014/05/31 职场文书
解放思想演讲稿
2014/09/11 职场文书
2015年六年级班主任工作总结
2015/10/15 职场文书
Vue全家桶入门基础教程
2021/05/14 Vue.js
centos8安装MongoDB的详细过程
2021/10/24 MongoDB