题目
什么是前端模块化?你知道哪些前端模块化规范?
解题
说到前端模块化,可能最先映入脑海的就是 CommonJS 规范了,或者还有 ES Module,但这种规范解决了什么问题呢?
什么是前端模块化
随着 Web 应用日益复杂,就造成了需要更多的可管理和可重用的代码,于是模块化编程呼之欲出。也就是说,前端模块化解决了代码杂乱无章的问题,以应对越来越复杂的 Web 应用。
前端模块化规范有哪些?
以历史的发展历程来叙述,可以有以下规范:
- 全局函数式编程
- 命名空间模式
- CommonJS
- AMD
- CMD
- UMD
- ES Module
就来说说这些规范吧
全局函数
顾名思义,通过全局声明变量和函数的方式来管理代码
该规范的缺点:
- 容易造成命名冲突
- 函数之间的依赖关系不明确
- 维护困难,有一定成本
命名空间模式
通过定义全局对象,将所有函数和变量都封装进这个全局对象中
该规范的缺点:
- 虽然解决了命名冲突的问题,但是模块之间的依赖关系仍然不清晰
- 所有依赖都需要在对象内手动进行管理
CommonJS
通过 require 函数加载模块,module.exports 导出模块
在 Node 中,我们经常可以看到 require 和 module.exports(当然,时至今日也可以用 ES Module 来管理代码了),这就是 CommonJS 规范了。
require 函数的作用与实现
- 作用:根据模块的文件路径读取模块文件,然后执行模块代码,最后返回模块的 exports 对象
- 实现:
require 函数在执行模块代码时,会先将模块代码封装进一个函数中,然后调用该函数。
这样做的好处是:可以将模块代码隔离到一个函数作用域中,防止模块内的变量污染全局作用域。
exports 和 module.exports 之间有什么关系呢
一旦 module.exports = { },则 exports 上的任何修改都不会影响 module.exports
require 的查找规则
导入格式:require(path)
-
情况一:
- 如果 path 是一个核心模块,如 fs、http 等模块
- 直接返回核心模块,并且停止查找
-
情况二:
- 如果 path 是以 ./ 或 ../ 或 /(根目录) 开头的
- 先当作文件来查找,后当作目录来查找
- 将 path 当作文件在对应的目录下查找
- 如果有后缀名,则按照后缀名的格式查找对应的文件
- 如果没有后缀名,按照以下顺序:
- 直接查找 path 文件
- 查找 path.js 文件
- 查找 path.json 文件
- 查找 path.node 文件
- 没有找到对应的文件,将 path 当作目录
- 查找 path 目录下的 index 文件
- 查找 path/index.js 文件
- 查找 path/index.json 文件
- 查找 path/index.node 文件
- 如果当作文件和目录都没有找到,则报错:not found
-
情况三:
- 直接是一个 path,并且 path 不是一个核心模块时
- 直接在 node_modules 中去找
- 如果没找到,则报错:not found
module.exports 的作用
- 在 CommonJS 中,每个模块都有一个 module 对象,在该对象中有一个 exports 属性用于导出模块
- 当其他模块通过 require 函数导入一个模块时,其实获取到的就是 module.exports 对象
- module.exports 的初始值是一个空对象:module.exports = { }
AMD
AMD(Asynchronous Module Definition,由 RequireJS 提出),根据名字就能看出,AMD 规范具有异步加载的特性。
通过 define 函数定义模块
优点:
- 支持异步加载,适合浏览器环境
缺点:
- 语法较为复杂
CMD
CMD(Common Module Definition),也是一种适用于浏览器的模块化解决方案
特点:
- 支持异步加载模块
- 适用于浏览器环境
- 同时汲取了 CommonJS 的优点
UMD
UMD(Universal Module Definition),是一种前后端跨平台的模块化解决方案
UMD = CommonJS + AMD
实现原理:
- 先判断是否支持 CommonJS 规范,即 Node 的 exports 对象是否存在,存在则使用
- 再判断是否支持 AMD 规范,即 define 函数是否存在,存在则使用
- 最后如果两者都不存在,则将模块暴露到全局中
ES Module
由 ECMA Script 自己推出的模块化解决方案
通过 import 导入模块、export 导出模块
特点:
- 自动开启严格模式
- 支持异步加载模块
- 具有静态性,使得模块之间的依赖关系更加清晰
- 既可以在 Node 环境中使用,也可以在浏览器环境中使用
异步加载模块
异步加载意味着 JavaScript 引擎遇到 import 时会去获取这个文件,但这个获取的过程是异步的,并不会阻塞主线程的继续执行。
也就是说在 script 标签上设置了 type = module
的代码,相当于在 script 标签上加上了 async 属性
静态性
什么是静态性?
指模块在编译阶段进行静态分析和解析,并且模块的依赖关系是在编译时确定的,而不是在运行时。
这意味着在编译时,模块之间的依赖关系是固定的,不会受到运行时条件或动态变化的影响
静态性带来的优势:
- 静态分析
- 编译器可以在编译时分析模块的依赖关系,并进行优化和静态检查
- 提前解析
- 模块的依赖关系在编译时就已经确定了,因此可以提前解析和加载依赖,减少了运行时的解析和查找时间,提高了性能
- 静态优化
- 由于模块的依赖关系是在编译时确定的,如 tree-shaking 等优化技术就可以在编译时进行优化,从而减少最终的代码体积
ES Module 的跨域问题
通过 ES Module 规范引入一个本地 HTML 文件时,控制台会出现 CORS 跨域问题(这是由于 JavaScript 模块的安全性需要)
如何避免这种跨域问题?
- 开启一个本地服务器
- 通过 IDE 上的类似 Live Server 的工具进行开发
至此,前端模块化的知识算是梳理并总结完了,撒花~