为什么需要模块化?
首先模块化是什么?我个人的理解就是一个作用域,在这个作用域内的变量,方法不会受到其他环境影响,接下来我来看一段代码。
foo.js
var names = 'kobe'
console.log(names)
main.js
console.log(names)
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./foo.js"></script>
<script src="./main.js"></script>
</body>
</html>
我们去打开index.html发现会输出两个kobe,不是单独引入了两个js文件?其实在js中是没有模块化这么一说,那假如我在开发foo.js,小王在开发main.js,那一些变量命名重复怎么办或者一些函数都命名一样。这上线不就是bug爆了,这么看来模块化是非常有必要的,毕竟自己写的代码不想影响到别人。 对于模块化自然就有了些规范,CommonJS,AMD,CMD,ES6模块化,好那就依次来验证一下模块化的好处。
CommonJS和Node
CommonJS有自己的一套规范,Node是 CommonJS在服务端的一种体现。
- 在node中每个文件就是一个单独的模块
exports
和module.exports
可以负责对模块中的内容进行导出require
函数可以帮助我们导入其他模块
exports
foo.js
let user = 'kobe'
let age = 41
function say(name) {
return `${name}你好`
}
exports.user = user
exports.age = age
exports.say = say('kobe')
main.js
let bar = require('./foo')
console.log(bar)
在foo.js打印exports
在main.js中打印bar
,发现两者打印内容一样,其实node中实现commonJS的本质就是对象的引用赋值,require函数的作用就是把exports对象进行拷贝(注意这是浅拷贝)
module.exports
- CommonJS中是没有module.exports的概念的
- Node中为了实现模块的导出使用的是Module的类,每一个模块都是Module的一个实例
Node中真正用于导出的其实根本不是exports,而是module.exports,并且内部是将exports赋值module.exports
require的查找规则
首先我们require(X)
- X是一个模块(path,http),直接返回模块,停止查找
- X是一个文件(./ , ../, /开头)
- 如果有后缀名,则返回后缀名文件
- 如果没有后缀名,则按以下顺序查找
- 直接查找文件X
- 查找X.js文件
- 查找X.json文件
- 查找X.node文件
- 没有找到对应的文件,将X作为一个目录
- 查找X/index.js文件
- 查找X/index.json文件
- 查找X/index.node文件
- 如果没有找到,那么报错:not found
- 直接是一个X(没有路径),并且X不是一个核心模块。那么它将向上级目录查找node_modules文件。直到查找到根目录下。如果上面的路径中都没有找到,那么报错:not found。
模块的加载过程
- 模块在被第一次引入时,模块中的js代码会被运行一次
- 模块被多次引入时,会缓存,最终只加载(运行)一次 每个模块对象module都有一个属性:loaded。为false表示还没有加载,为true表示已经加载
- 如果有循环引入,那么加载顺序是什么?
main.js -> a.js -> c.js -> d.js -> e.js -> b.js
Node采用的是深度优先算法去加载
CommonJS规范缺点
-
CommonJS加载模块是同步的:
- 同步的意味着只有等到对应的模块加载完毕,当前模块中的内容才能被运行。
- 这个在服务器不会有什么问题,因为服务器加载的js文件都是本地文件,加载速度非常快。
ES Module的解析过程
对于ES模块,分为三个步骤:
- 构建 — 查找、下载并将所有文件解析为模块记录。
- 实例化 — 对模块记录进行实例化,并且分配内存空间,解析模块的导入和导出语句,把模块指向对应的内存地址。
- 运行 — 运行代码,计算值,并且将值填充到内存地址中。 详情请点击
以上是近期参考一些视频和文字作出的总结,加深自己对模块化的了解。