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);
// 三、exports
exports.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);