什么是模块化
模块化开发的最终目的是将一个程序分成一个一个独立的模块,这个模块里面有自己的逻辑代码,有自己的作用域,可以将自己希望暴露出去的变量、函数、对象导出给其他模块使用,也可以通过某种方式,引入外部模块的变量、函数、对象在自己的模块中使用
这个划分模块进行开发程序的过程就是模块化开发
早期的JavaScript是没有模块化开发这个概念的,但是随着现在前端发展的越来越快,前端能做的事情不只是简单的表单验证了,SPA的出现,前端页面变得更加复杂,需要处理前端路由、状态管理等等一系列的复杂需求,Node的出现,JavaScript也可以编写复杂的后端程序,我们先来看看没有模块化带来的问题
没有模块化带来的问题
这里可以看到我在foo.js这个文件声明了一个uname变量,在bar.js的这个文件直接去读取值的时候,居然是可以读取的,而不是输出undefined,假设一个场景,当一个项目需要多人开发的时候,如果这个时候没有模块化,而是像上面一样可以随意读取其他文件的变量时,那会出现一个很严重的问题,当代码多的时候,维护则需要花费很多时间,这肯定是不便于我们开发的
所以JavaScript非常需要模块化
这里我会主要讲解CommonJS和ES6的模块化
CommonJS
CommonJS这个词做前端的小伙伴应该并不陌生,我记得第一次看到这个词的时候是在招聘信息上面
CommonJS是一种规范,最初提出来是在最初提出来是在浏览器以外的地方使用,并且当时被命名为ServerJS,后来为了 体现它的广泛性,修改为CommonJS,平时我们也会简称为CJS
Node.js是CommonJS在服务器端一个具有代表性的实现
Browserify是CommonJS在浏览器中的一种实现;
webpack打包工具具备对CommonJS的支持和转换;
在 Node.js 模块系统中,每个文件都被视为独立的模块
这些独立的模块通过: exports, module.exports, require这几个核心变量来实现导入,导出,进行模块化开发。
exports和module.exports进行导出当前模块需要暴露出去的成员,require进行导入当前模块需要另一个模块的成员
现在,我们就可以将上面的案例进行改造成模块化开发的方式
这里实现了模块化开发的思路
那么问题来了,从exports到require这里做了什么呢
如果我们直接输出这个exports的时候其实可以看到他就是一个空对象
从exports到require 这里就是做了一个对象引用的赋值(也有人喜欢这种方式为浅拷贝)
这里我大概画了一下内存图,画的比较粗糙哈哈
那么问题又来了,module.exports和exports有什么关系呢,exports已经可以实现导出了呀,为什么还有一个module.exports呢
CommonJs中没有module.exports这个概念的,但是Node.js中为了实现模块的导出,Node.js使用的是Module的类,每一个模块都是Module的一个实例
可以看到Module里面是有exports这个成员的
我们改造一下上面的代码
在这里大家是不是会一头雾水,为什么uname是等于xiaoliu,而不是xiaozhu,因为我们在之前使用的exports导出成员的方法,其实本质Node.js会做将exports赋值给module.exports,等于module.exports = exports这个操作,但是我们导出变量的方式是一个对象的形式,等于是重新在开辟一块内存空间,所以会使用后面赋值的xiaozhu这个值
至于为什么有exports还要有module.exports,因为exports是CommonJs规范要求的,Node.js则在Module这个类中是实现了exports,保留exports主要是为了让熟悉CommonJs规范的开发者在Node.js中也可以使用exports这个关键字
当然,CommonJS也是有缺陷的,CommonJS加载模块是同步的,这就意味着我们模块内容什么时候显示取决于模块文件的加载速度,如果这个东西运用到浏览器的话是非常致命的,因为浏览器加载js文件需要先从服务器将文件下载下来,之后在加载运行,同步意味着后续的js代码都无法正常运行。
所以在浏览器中,我们都不会使用CommonJS规范
ES Module
ES Module和CommonJS的模块化有不同之处
ES Module采用了import和export关键字进行导入导出,import负责导入,export负责导出
ES Module还采用了编译期的静态类型检测和动态引用的方式
ES Module是异步加载模块的
ES Module将自动采用严格模式
这里需要说明的两点
- export和import导入或者导出并不是使用对象的方式进行导出的,而是使用一个大括号{}
- 在这里导入文件的时候需要写出文件的后缀名,在Node.js中是使用require这种方式进行导入模块,而require函数会自动帮我们查找文件,没有后缀名的话,1. 先查找是否有该文件名的文件存在,2. 查找该文件名.js后缀名文件,3. 查找该文件名.json后缀名文件 4. 查找该文件名的.node后缀名文件,如果这些都没查找到,则会把这个文件名当作是一个文件夹名称去寻找该文件下是否有,index.js,index.json,index.node这几个文件
default默认导出
我们刚刚的导出都是有名字的导出,在导入的时候需要知道导出的时候是什么名字,而默认导出(default export)则不需要,但是一个模块只能有一个默认导出
import关键字动态加载
一般情况来说我们的import导入的时候是不能放入逻辑代码的,因为ES Module需要在js引擎解析时就知道模块的依赖关系(而不是运行时),但是我们有时候有确实需要动态加载模块的话可以尝试以下这种做法
export导出的是对象的引用,而且在export的时候会在内存开辟一块模块环境记录(module environment record)的空间,这里export和import的变量是实时绑定的
这里需要注意的是export的时候是使用{}(大括号)进行导出的,不是对象的形式,这里需要注意,所以不能在main.js中去修改num的值,如果在main.js中修改export导出的值的话会报类型错误,但是export导出的成员是本身就是一个对象的话,那么是可以在main.js中修改这个对象里面的值
最后
简单总结一下:
CommonJS是同步加载的,ES Module是异步加载的 CommonJS是通过module.exports进行导出(这里导出的是一个对象), require进行导入,ES Module是通过export进行导出(这里用的是{}来进行导出的),import进行导入,export和import的变量是实时绑定的
模块化这里的内容我也是学习了很多优秀的博客和公众号内容才写出这篇文章进行记录,希望可以帮助到大家
|