Node系列-上一节事件循环详解
随着前端的发展,页面特效、交互都在前端层面实现,前端的代码逻辑复杂度增加。 写代码不可能一把唆,都写入一个文件当中,这样可读性不强也不利于后期的维护。 需要利用模块的思想将代码进行划分,使其职责单一且可替换。
并且需要防止变量污染全局,防止变量的重命名。
简单提一下模块化从概念引入到今天的一个发展历程。
直接定义依赖和现在流行的CommonJs相似,不同点在于CommonJs中定义一个文件即一个模块,而它则可以再任何文件中定义模块,模块和文件不关联。Dojo的思想。
使用命名空间主要是为了解决变量命名冲突的问题。YUI的思想。
引入闭包模式解决了变量污染的问题。
在文件头加上一些注释标记该文件的依赖,然后在编译时解析引入依赖。
Angular中引入了其思想。
现代流行的模块化解决方案,从Node端再引入到前端。也是本文着重讲解的一个知识点。
RequireJs的思想,核心是依赖前置。
SeaJs的思想,核心是按需加载。不过玉伯现在没维护了,相当于已经废弃了。
兼容了CommonJs和AMD,核心思想是如果在CommonJs环境下,即存在module.exports,不存在define时将函数执行结果交给module.exports实现CommonJs;否则使用AMD环境的define。
现代的模块化方案,Node9+支持,可以通过启动加上flag--experimental-modules使用。
在浏览器端可以通过<script type="module" src="xxx"></script>的方式使用。
本文的重点是解析CommonJs规范的核心思想。
以下涉及到的代码均在仓库中,感兴趣的可以前往调试
首先我们可以先通过断点调试require(path/to/file)方法作为切入点了解其中机制。
准备了两个文件1.case.js和require.js
1.case.js 文件内容
let info = require('./1.require.js')
console.log(info)require.js 文件内容
module.exports = {
name: 'careteen',
age: '23'
}1、点击进入函数内调试两次即可进入

2、进入到了module.js源文件

里面提供了一个mod为Module类的实例,并且提供一个require方法,进入方法内查看
3、首先做了一些路径的断言,然后返回Module._load(),将当前文件名传入,进入方法内查看

4、里面有缓存的功能,由于我们是第一次加载这个文件,没有缓存,所以直接跳过

491行new了一个Module实例,在500行传入tryModuleLoad方法,再进入此方法内查看
5、做了一层try...catch,实际调用实例上的load方法,再进入此方法内查看

6、先会判断是否加载过,防止重复加载。

1标志位上会对文件名处理,我们可以再调试控制台中输入this.paths查看最终的处理结果

实际上就是对查找做了一层层的判断,如果当前目录没有node_modules,去上一级目录查找,如此递归.具体实现如下图

2标志位会取到文件的扩展名根据类别做处理,可进入Module._extensions()方法内查看
7、使用fs.readFileSync()方法同步读取到文件内容

然后再调用实例的_compile方法,进入方法内查看
8、1标志位会将读取的内容调用静态方法wrap进行包裹

包裹方法如下图

包裹后的内容如下

从以上可看出相当于使用了闭包,匿名函数中传入在module实例上的一些属性exports/require/module...
以便在require的时候能读取到。
2标志位使用了vm内置模块沙箱式运行代码。
其功能相当于eval('console.log(name)')和new Function('console.log(name)')。
9、以上步骤就模块化的大致思路,跳出以上所有方法内部。
10、对于文件类型处理还需考虑另外两种情况,实现原理类似。

只不过.json文件读取后会被转成一个JSON对象。
跟着上面的思路我们大致了解到了模块化的几个核心点
fs.readFileSync()读取文件内容.js/json/node三种文件类型.js通过内置模块vm使其沙箱式执行文件内容.json读取后转为JSON对象.node是一个二进制的C++文件,是可以直接运行的下面针对以上各个核心点一一突破
挂一个缓存对象即可
一图胜千言

.js文件左一层包裹
.json读取后转为JSON对象