- A+
所属分类:Web前端
Commonjs
什么是 CommonJs
- CommonJs 是 js 模块化的社区规范
模块化产生的原因
- 随着前端页面复杂度的提升,依赖的第三方库的增加,导致的 js 依赖混乱,全局变量的污染,和命名冲突
- 单个 js 文件内容太多,导致了维护困难,拆分成为多个文件又会发生第一点描述的问题
- v8 引擎的出现,让 js 有了媲美编译型语言的运行速度,大大激励了前端开发者
CommonJS 的使用环境
- nodejs 实现了 CommonJS 模块化规范
CommonJs 有哪些规定
- 每一个文件就是一个模块
- 模块中的变量和函数不会污染全局(解决了全局污染和命名冲突)
- 提供给外部使用的内容需要导出
- 使用其他模块的内容需要导入 (模块的导入和导出共同解决了 js 依赖混乱的问题)
- 模块不会重复加载,模块第一次导入后会将第一次导入的结果缓存,下次导入时,直接使用缓存的结构
- 省略一些细碎的内容在下面代码中提及.....
commonJS 语法
- 导入
//这是导入一个模块,module.js;commonjs中规定require导入模块时可以省略.js后缀 const module1 = require("./module1"); //如果没有寻找到dir.js文件,而发现了dir路径,则寻找dir路径下package.json 的main属性指定的文件 //如果package.json未指定路径,则触发默认规则 依次查找查找 index.js index.json const module2 = require("./dir"); //如果require不是相对路径,则会去node_module中寻找该模块,重复module1 和module2 的步骤 //如果没有node_modules 或node_modules 中不存在模块则继续向上级目录寻找node_modules,直到根目录 const module3 = require("module3");
- 导出
module.exports = { //这里输入导出的内容 }; //这也是导出 exports.a = "a"; //注意 module.exports导出和exports[属性名]导出不可共存 //module.exports会覆盖掉exports导出的内容
简易实现类 nodejs 模块化环境
const fs = require("fs"); const Path = require("path"); const vm = require("vm"); const ModuleStack = []; function isRootDirectory(path) { // Windows 根路径 const windowsRootDirectory = /^[a-zA-Z]:\$/; // Unix/Linux 根路径/ const unixRootDirectory = /^//; return windowsRootDirectory.test(path) || unixRootDirectory.test(path); } function isRelativeDirectory(path) { //匹配 ../ 或者 ./开头的路径 const relativeDirectory = /^(../|./).+/; return relativeDirectory.test(path); } // 计算node_modules路径 let computePaths = (dirname) => { let paths = []; let path = dirname; let node_modules = "./node_modules"; while ( !isRootDirectory(path) || !paths.includes(Path.resolve(path, node_modules)) ) { paths.push(Path.resolve(path, node_modules)); path = Path.resolve(path, "../"); } return paths; }; function myRequire(path) { let truelyPath; if (isRelativeDirectory(path)) { // 获取真实路径 truelyPath = Path.resolve(__dirname, path); } else { //获取可能的node_modules路径 let paths = computePaths(__dirname); for (const item of paths) { truelyPath = Path.resolve(item, path); if (fs.existsSync(truelyPath)) { break; } } if (!truelyPath) { throw new Error("Can't find module " + path); } } // 如果缓存中有,直接返回 if (myRequire.cache[truelyPath]) { return myRequire.cache[truelyPath].exports; } // 读取文件内容 const content = fs.readFileSync(path, "utf-8"); // 包装代码 const wrapper = [ "(function (exports, require, module, __filename, __dirname) { n", "n})", ]; // 拼接代码 const wrapperContent = wrapper[0] + content + wrapper[1]; // 获取文件路径和文件名 let dirname = Path.dirname(truelyPath); let filename = truelyPath; let parentModule = ModuleStack.length > 0 ? ModuleStack[ModuleStack.length - 1] : null; // 模块对象 const Module = { id: Object.keys(myRequire.cache).length > 0 ? filename : ".", path: dirname, exports: {}, parent: parentModule, filename: filename, loaded: false, children: [], paths: computePaths(dirname), }; if (parentModule) { parentModule.children.push(Module); } //模块入栈 ModuleStack.push(Module); // 需要运行的函数 const moduleScope = vm.runInThisContext(wrapperContent); // 运行代码 moduleScope.call( Module.exports, Module.exports, myRequire, Module, filename, dirname ); // 标记模块已加载 Module.loaded = true; //模块出栈 ModuleStack.pop(); // 缓存模块 myRequire.cache[truelyPath] = Module; return Module.exports; } myRequire.cache = Object.create(null);
模块化的意义
- 解决在模块化出现之前的js依赖混乱,全局污染命名冲突的问题
- 模块化的出现让js代码可以拆分为多个模块共同协作,单个js文件过长的问题,降低了维护难度。
- 模块化的出现让js开发大型项目出现了可能
ps:当前内容为学习commonjs理解,内容正确性请谨慎甄别。