nodejs模块化
一、模块化倒退历程
传统开发常见问题:
- 命名抵触和净化
- 代码冗余,有效申请多
- 文件的依赖关系简单
模块:小而精,利于保护的代码片段
常见模块化标准
CommonJS标准 node.js(同步加载)AMD标准 require.js(异步加载,依赖前置)CMD标准 sea.js(异步加载,就近依赖)ES6模块化 原生(编译时,异步加载)
当下罕用的标准是Commonjs与ESM,参考文档:https://www.cnblogs.com/echoy...
二、CommonJS标准(语言层面上的标准)
三个根底实现:
模块定义 模块标识 模块援用
node.js与CommonJS:
任意一个文件就是一个模块,具备独立的作用域应用require导入其它模块将模块ID传入require实现目标模块定位
CommonJS在nodejs中的实现:
module属性
任意一个js文件就是一个模块,可间接应用module属性id:返回模块标识符,个别是一个绝对路径filename:返回文件模块的绝对路径loaded:返回布尔值,示意模块是否实现加载parent:返回对象,寄存了调用以后模块的模块children:返回一个数组,寄存以后模块调用的其它模块exports:返回以后模块须要裸露的内容paths:返回数组,寄存不同目录下的node_modules的地位
module.exports与exports的区别
两者指向同一个内存地址,当exports从新赋值之后,exports就相当于一个局部变量了
require属性
基本功能是读入并执行一个模块文件,返回这个模块文件的moudule.exports对象resolve:返回模块文件绝对路径extensions:依据不同后缀名执行解析操作main:返回主模块对象
nodejs中CommonJS标准的代码实现:
//主模块// 一、模块的导入与导出const m = require('./m');console.log(m)// 二、module// console.log('no_module',module);// 三、exports// console.log(m)// 四、同步加载// 判断以后模块是否为主模块console.log(require.main === module) // require.main 指向本人//外置模块:// 一、模块的导入与导出const age = 18;const addFn = (x, y) => { return x + y;}// module.exports = {// age,// addFn// }// 二、module// console.log('no_module',module);// 三、exportsexports.name = 'jiang';// 四、同步加载let name = 'lg';// let iIime = Date.now();// while(Date.now() - iIime < 4000) {} // 同步加载,阻塞以后线程exports.nickName = name;console.log('m.js执行了')// 判断以后模块是否为主模块console.log(require.main === module) // require.main parent
三、模块加载
缓存优先准则
模块分类:
内置模块:Node源码编译时写入到了二进制文件中,应用时加载比拟快文件模块:代码运行时,动静加载的,须要经验残缺加载流程
模块加载流程:
路径分析:依据标识符(门路与非门路)确定模块地位(module.paths返回了模块加载策略)文件定位:确定指标模块中具体的文件及文件类型(依照.js-->.json-->.node程序补足拓展名)编译执行:依据文件类型,采纳对应的形式实现文件的编译执(.js编译:将其封装成一个立刻执行函数,并传入exports、module、require等参数执行;.json编译:应用JSON.parse解析),返回exports可用对象
四、VM模块
创立独立运行的沙箱环境
相似沙箱的实现:
const fs = require('fs');const path = require('path');const vm = require('vm');let content = fs.readFileSync(path.join(__dirname,'test.txt'), 'utf-8');// eval// eval(content); content中变量须要用var,能力在这里拜访 // new Function// let fn = new Function('age', 'return age + 1');// console.log(fn(19))let age = 88;vm.runInThisContext(content);console.log(age);
五、文件加载模仿实现
外围逻辑:
路径分析缓存优先文件定位编译执行
代码实现:
const fs = require('fs');const path = require('path');const vm = require('vm');function Module(id) { this.id = id; this.exports = {};}Module.prototype.load = function (filename) { let extname = path.extname(this.id); Module._extensions[extname](this); console.log('111')}Module.wrapper = [ '(function(exports,require,module,__filename,__dirname){', '})']Module._extensions = { '.js'(module) { // 读取 let content = fs.readFileSync(module.id, 'utf-8'); // 包装成函数 content = Module.wrapper[0] + content + Module.wrapper[1]; let compileFn = vm.runInThisContext(content); // 筹备参数值 let exports = module.exports; let dirname = path.dirname(module.id); let filename = module.id; // 执行 compileFn.call(exports, exports, myRequire, module, filename, dirname); }, '.json'(module) { let content = JSON.parse(fs.readFileSync(module.id, 'utf-8')); module.exports = content; },}Module._resolveFilename = function (filename) { // 1 绝对路径 let absPath = path.resolve(__dirname, filename); // 2 判断文件是否存在 if (!fs.existsSync(absPath)) { // 文件定位 let suffix = Object.keys(Module._extensions); for (let i = 0; i < suffix.length; i++) { let s = suffix[i]; let p = absPath + s; if (fs.existsSync(p)) { return p; } } throw new Error(`${filename} is not exists`); } return absPath;}Module._cache = {};function myRequire(filename) { // 1 绝对路径 let mPath = Module._resolveFilename(filename); console.log('mPath', mPath); // 2 缓存优先 let cacheModule = Module._cache[mPath]; if (cacheModule) return cacheModule.exports; // 3 创立空对象,加载模块 let module = new Module(mPath); // 4 缓存已加载的模块 Module._cache[mPath] = module; // 5 执行加载(编译执行) module.load(mPath); // 6 返回数据 return module.exports;}let obj = myRequire('./v');myRequire('./v'); // 缓存优先console.log(obj);