JS模块化
📚一. 什么是模块化
📣模块化是指解决一个复杂问题时自顶向下逐层把系统划分成若干模块的过程,有多种属性,分别反映其内部特性。 ——维基百科
这是维基百科对模块化的介绍,简单理解就是:
- 将代码拆分成独立的块,然后把这些块连接起来可以通过模块模式来实现。
块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信
📚二. js模块化发展历程
在JS设计之初,本身就是为了满足简单的页面设计: 页面动画 + 表单提交,并无模块化 or 命名空间的概念。但随着CPU、浏览器性能得到了极大的提升,页面的复杂程度也显著提高,JS的模块化需求也日益增加。
js模块化的发展一共经历了以下几个阶段:
✏️2.1 幼年期:无模块化
在发展之初,没有模块化的概念,但随着业务逻辑的复杂度增加,我们也遇到了一些需求,比如:
- 开始需要在页面中加载不同的JS:动画、组件、格式化
- 多种js文件会被分在不同的文件中
- 不同的文件又被同一个模板所引用
🎈作用:将不同的业务逻辑(js文件),引入到同一个页面上去
这种文件分离拆分是最基础的模块化(第一步)
✒️2.2 成长期:模块化前夜 - IIFE(语法侧的优化)
IIFE: 立即调用函数表达式
📒编码: 将数据和行为封装到一个函数内部, 通过给window添加属性来向外暴露接口
🎈作用: 数据是私有的, 外部只能通过暴露的方法操作
为按照模块模式提供必要的封装,ES6之前的模块会使用函数作用域和IIFE将模块定义封装在匿名闭包中。
例子:1
2
3
4
5let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
}
利用函数的块级作用域 - 隔离区
1 | (() => { |
🐛问题: 独立模块本身的额外依赖,如何优化?
方案: 依赖其他模块的传参型
1
2
3
4
5
6
7
8
9const iifeModule = ((dependencyModule1, dependencyModule2) => {
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
}
console.log(count);
increase();
})(dependencyModule1, dependencyModule2);
将依赖作为参数,传入到独立模块。
2.3 🪡成熟期:CJS - Commonjs
2.3.1 🌭说明:
CommonJS
规范概述了同步声明依赖的模块定义。CommonJS模块语法不能直接在浏览器中直接运行。
2.3.2 基本语法
暴露模块:
1
2module.exports = value
exports.xxx = value引入模块:
1
require(xxx)
CommonJS 模块定义需要使用
require()
指定依赖
❗无论一个模块在require()中被引用多少次,模块永远是单例。模块第一次加载后会被缓存,后续加载会取得缓存的模块。
每个模块内部,module对象代表当前模块,它的exports属性(即module.exports)是对外的接口(暴露出去)。加载某个模块,其实是加载该模块的module.exports属性。
2.3.3 加载机制
- CommonJS 用于 node 端,是同步加载的,也就是说,只有加载完成,才能执行后面的操作。
- 输入的是被输出的值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
2.3.4 优点
CommonJs率先在服务端实现了,从框架层面解决了依赖、全局变量污染的问题
2.3.5 缺点
针对了服务端的解决方案。异步拉取依赖处理不是很完美
2.3.6 面试问题
🐛exports 与 module.exports
为了方便,Node为每个模块提供一个exports变量,指向module.exports。这等同在每个模块头部,有一行这样的命令。1
var exports = module.exports;
exports其实是module.exports的引用 ,可以直接在exports对象上添加相关的方法。
2.4 💁 AMD规范
CommonJS以服务端为目标环境,能够一次性把所有模块都加载到内存中,而异步模块定义(AMD)的模块定义系统则以浏览器为目标执行环境,只需要考虑网络延迟的问题。
经典实现框架:require.js
2.4.1 🌭说明:
AMD规范是非同步加载模块,允许指定回调函数。
2.4.2 基本语法
1. 定义暴露模块:1
define([module-name?], [array-of-dependencies?], [module-factory-or-object]);
其中:
- module-name: 模块标识,可以省略。
- array-of-dependencies: 所依赖的模块,可以省略。
- module-factory-or-object: 模块的实现,或者一个JavaScript对象。
2. 引入使用模块:1
2
3
4require(['module1', 'module2'], function(m1, m2){
//使用m1/m2
})
2.4.3 模块的加载机制
AMD 依赖于 requirejs,是异步加载的,是提前加载,立即加载
2.4.4 优点
适合在浏览器中加载异步模块的方案
2.4.5 缺点
引入成本
2.5 💁 CMD规范
CMD是SeaJS 在推广过程中对模块定义的规范化产出, CMD规范专门用于浏览器端,模块的加载是异步的,模块使用时才会加载执行。CMD规范整合了CommonJS和AMD规范的特点。
2.5.1 🥯基本语法
定义暴露模块:1
2
3
4
5
6//定义没有依赖的模块
define(function(require, exports, module){
exports.xxx = value
module.exports = value
})
1 | //定义有依赖的模块 |
引入使用模块:1
2
3
4
5
6
7define(function (require) {
var m1 = require('./module1')
var m4 = require('./module4')
m1.show()
m4.show()
})
2.5.2 🥨模块的加载机制
CMD 依赖于 seajs ,是异步加载,延后加载,就近加载,用时加载
2.5.3 🥐优点
按需加载,依赖就近
2.5.4 🥗缺点
依赖打包,加载逻辑存在于每个模块中,扩大了模块体积,同时功能上依赖编译
2.5.5 🐛问题
AMD 和 CMD 的区别有哪些?
- 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible.
- CMD 推崇依赖就近,AMD 推崇依赖前置。
- AMD 的 API 默认是一个当多个用,CMD 的 API 严格区分,推崇职责单一.
2.6 ✒️ES6模块化
2.6.1 🍡基本语法
1. 导出
export命令1
export const obj = {name: 'E1e'};
默认导出 export default命令1
2
3export default {n
ame: 'E1e'};
2. 引入1
2// 引入普通导出
import { obj } from './test.js';
1 | // 引入默认导出 |
2.6.2 🍭模块的加载机制
- 模块支持异步加载
- 同一个模块如果加载多次,将只执行一次。
- 既可以通过浏览器原生加载,也可以与第三方加载器和构建工具一起加载
2.6.3 🍬优点
通过一种最终统一各端的形态,整合了js模块化的通用方案
2.6.4 🍫局限性
本质上还是运行时的依赖分析
1.浏览器渲染流程
2.数据包是如何完整的发送到应用程序
3.浏览器的进程和线程
4.重定向与负载均衡
5.jwt鉴权机制
6.深入理解URL
7.HTTP报文是什么
8.React SSR 的实现