前端模块化
首先为什么需要模块化?模块化解决了什么问题?
其实在其他语言中,模块往往是语言标准的一部分。模块最基本的一个作用就是隔离命名空间,避免出现命名冲突。
所以在js非模块化开发的时候变量命名特别需要注意。所以js的模块化发展是必然的。
那前端的模块化有哪些规范呢?
前端的模块化一直在发展,历史中形成规范其实挺多的。
一个是CommonJS
规范,也就是cjs,用于写nodejs。
一个是ESM
规范,我们前端的项目现在都是用这个。
一个是UMD
规范,一般我们给别人用npm包的时候要按照这个规范打包,它其实是兼容AMD和CommonJS。
还要一个是iife
,一般是前端应用打包时候用的,打出来的js立即执行。
CommonJS和ES Module的区别
模块的静态加载(依赖提前加载)和动态加载(依赖适时加载)
ESM的模块写法是在文件的头部把依赖全部import进来,显然ESM属于静态加载,也就是依赖提前加载好。
CommonJS我们可以在代码任何位置使用requie加载依赖,显然属于动态加载,也就是依赖适时加载。
// a.mjs
console.log("execute a.mjs")
import b from "./b.mjs"
// b.mjs
console.log("execute b.mjs")
export default "B"
输出
execute b.mjs
execute a.mjs
由于a依赖b,所以b会提前加载执行,然后才执行a的代码
// a.js
console.log("execute a.js")
var b = require("./b.js")
// b.js
console.log("execute b.js")
module.exports = "B"
输出
execute a.js
execute b.js
commonjs属于适时加载,先执行a,然后碰到require(b),再加载b执行
循环依赖问题
无论什么模块化规范,都逃不过循环依赖的问题。因为依赖加载的原理不同,所以ESM和CommonJS在循环依赖的表现上也会有所不同。
我们先来看看CommonJS的代码
// a.js
const b = require("./b.js")
module.exports = function(){
console.log("in a.js function")
}
b();
// b.js
const a = require("./a.js")
module.exports = function () {
console.log("in b.js function")
}
a()
// 运行 node a.js 会报错,a() is not a function
首先加载a.js,遇到require(b),这时候去加载b.js, b里遇到require(a),这时候去加载a, 但是由于a里面exports还没执行,所以a是找不到的,所以就会报错a不是一个函数。 但是相同的代码在ESM里却可以执行,因为ESM是依赖提前加载。
// a.mjs
import b from "./b.mjs"
export default function () {
console.log("in a.mjs function")
}
b()
// b.mjs
import a from "./A.mjs"
a()
export default function () {
console.log("in b.mjs function")
}
运行 node a.mjs,输出
in a.mjs function
in b.mjs function
首先扫描a.mjs文件中import/export语句,确定a.mjs导出一个函数,并且import语句依赖于b.mjs 然后再扫描b.mjs,确定b.mjs导出一个函数,并且import语句依赖a.mjs,由于a.mjs已经处理过了,所以可以直接加载,整个依赖关系就加载好了。
ES Module可以做treeShaking
esm模块规范可以做treeShaking,commonjs则不行