文章导读
「从构建产物洞悉模块化原理」- 不要秃头啊 https://juejin.cn/post/7147365025047379981/
相关问题
- 模块化的产生是为了解决什么问题?在什么场景下诞生的
- Web环境中是如何支持模块化的?加载过程时怎么样的
- CommonJS可以加载ES Module导出的内容吗
- ES Module可以加载CommonJS 导出的内容吗
- Webpack内部如何区分一个模块是采用哪种模块化规范
- 一个模块内可以既使用CommonJS,又使用 ES Module 吗?
模块化发展历史
早期JavaScript开发很容易存在全局污染和依赖管理混乱问题。每个加载的js文件都共享变量。
后来可以使用匿名函数自执行的方式,形成独立的块级作用域解决这个问题。这会有新的问题:
- 代码杂乱无章
- 没有合适的规范,每个人都可能会任意命名、模块名称可能相同
- 必须记得每一个模块返回对象命名的问题,才能在其他模块中正确的使用
因此,commonJS规范问世
CommonJS规范
CommonJS具备以下特点:
- 每一个JS文件都是一个单独的模块
- 模块中包含CommonJS规范的核心变量:exports、 module.exports、require
- 使用核心变量,进行模块化开发
//在a.js中导出变量
const name = "不要秃头啊";
const age = "18";
module.exports = { name, age };
//或者:
exports.name = "不要秃头啊";
exports.age = "18";
//在b.js中引用导出的变量
const { name, age } = require("./a.js");
console.log( name , age )
ES Module规范
从ES 6开始,JavaScript才有真正意义上模块化规范
ES Module产生的优势有很多,比如:
- 借助ES Module的静态导入导出优势,实现tree shaking
- 使用import()懒加载的方式实现代码分割
/**
* 导出
*/
export * from 'module'; //重定向导出 不包括 module内的default
export { name1, name2, ..., nameN } from 'module'; // 重定向命名导出
export { import1 as name1, import2 as name2, ..., nameN } from 'module'; // 重定向重命名导出
export { name1, name2, …, nameN }; // 与之前声明的变量名绑定 命名导出
export { variable1 as name1, variable2 as name2, …, nameN }; // 重命名导出
export let name1 = 'name1'; // 声明命名导出 或者 var, const,function, function*, class
export default expression; // 默认导出
export default function () { ... } // 或者 function*, class
export default function name1() { ... } // 或者 function*, class
/**
* 导入
*/
import defaultExport from "module"; // 默认导入
import { a, b, c } from "module"; //解构导入
import defaultExport, { a, b, c as newC } from "module"; //混合导入
import * as name from "module"; //混合导入
var promise = import("module"); //动态导入(异步导入)
CommonJS模块化实现原理
- 定义对象modules,用来存放模块化的源代码
- 定义缓存对象cache
-
定义require函数
- 判断缓存中是否已加载过
- 给定义的module变量和cache[moduledId]赋值同一个对象
- 加载执行模块 给module.exports赋值
- 导出module.exports
- 执行入口函数
//模块定义
var modules = {
"./src/name.js": (module) => {
module.exports = "不要秃头啊";
},
};
var cache = {};
//接受模块的路径为参数,返回具体的模块的内容
function require(modulePath) {
var cachedModule = cache[modulePath]; //获取模块缓存
if (cachedModule !== undefined) {
//如果有缓存则不允许模块内容,直接retuen导出的值
return cachedModule.exports;
}
//如果没有缓存,则定义module对象,定义exports属性
//这里注意!!!module = cache[modulePath] 代表引用的是同一个内存地址
var module = (cache[modulePath] = {
exports: {},
});
//运行模块内的代码,在模块代码中会给module.exports对象赋值
modules[modulePath](module, module.exports, require);
//导入module.exports对象
return module.exports;
}
(() => {
let author = require("./src/name.js");
console.log(author, "author");
})();
ES Module和commonJS可以互相混用吗
- ES Module可以直接通过import引入commonJS
- commonJS不能通过require引入ES Module,经过babel转
commonJS和esModule的区别
- commonJS是被加载的时候运行,esModule是编译的时候运行
- commonJSS输出的是值的浅拷贝, esModule输出值的引用
- commonJS具有缓存,在第一次加载时,会完整运行整个文件并输出一个对象,浅拷贝在内存中,下次加载文件时,直接从内存中取值
/*************** a.js**********************/
let count = 0
exports.count = count; // 输出值的拷贝
exports.add = ()=>{
//这里改变count值,并不会将module.exports对象的count属性值改变
count++;
}
/*************** b.js**********************/
const { count, add } = require('./a.js')
//在支持es6模块的环境下等同于
import { count, add } from './a.js'
console.log(count) //0
add();
console.log(count)//0
/*************** a.js**********************/
export let count = 0;//输出的是值的引用,指向同一块内存
export const add = ()=>{
count++;//此时引用指向的内存值发生改变
}
/*************** b.js**********************/
import { count, add } from './a.js'
console.log(count) //0
add();
console.log(count)//1