37 # commonjs 规范流程梳理

news2024/11/27 8:48:13

require 源码大致过程

  1. mod.require 会默认调用 require 语法
  2. Module.prototype.require 模块的原型上有 require 方法
  3. Module._load 调用模块的加载方法,最终返回的是 module.exports
  4. Module._resolveFilename 解析文件名,将文件名变成绝对路径,默认尝试添加 .js .json
  5. Module._cache 默认会判断是否存在缓存
  6. new Module 创建模块(对象),里面有 id,exports
  7. Module._cache[filename] = module 把模块缓存起来,方便下次使用
  8. module.load 尝试加载模块
  9. module.paths 第三方模块查找的路径
  10. Module._extensions[extension] 获取当前模块的拓展名,策略是根据拓展名调用对应的方法
  11. fs.readFileSync 读取文件的内容
  12. module._compile 去除文本文件 BOM 头,编译文件的内容
  13. Module.wrap 将用户的内容包裹到一个函数中 (function (exports, require, module, __filename, __dirname) { }),用 vm.runInThisContext 创建沙箱环境,将字符串变成函数执行

简单实现一个自己的 require 去加载 js 文件


const path = require("path");
const fs = require("fs");
const vm = require("vm");

function Module(id) {
    this.id = id;
    this.exports = {};
}
Module.warp = function (script) {
    let arr = [`(function (exports, require, module, __filename, __dirname) {`, script, `})`];
    return arr.join("");
};
// 加载策略
Module._extensions = {
    ".js": function (module) {
        console.log("js---->", module);
        // 同步读取
        let content = fs.readFileSync(module.id, "utf-8");
        let fnStr = Module.warp(content);
        console.log("fnStr---->", fnStr);
        let fn = vm.runInThisContext(fnStr);
        console.log("fn---->", fn.toString());
        let exports = module.exports;
        let require = kaimoRequire;
        let __filename = module.id;
        let __dirname = path.dirname(module.id);
        // 改变 this 指向 exports
        fn.call(exports, exports, require, module, __filename, __dirname);
        // 用户会给 module.exports 赋值
    },
    ".json": function (module) {
        console.log("json---->", module);
        // 同步读取
        let content = fs.readFileSync(module.id, "utf-8");
        module.exports = JSON.parse(content);
    }
};

// 把文件名变成绝对路径
Module._resolveFilename = function (filepath) {
    // 根据当前路径实现解析
    let filePath = path.resolve(__dirname, filepath);
    // 判断当前文件是否存在
    let exists = fs.existsSync(filePath);
    if (exists) return filePath;
    // 尝试添加后缀
    let keys = Object.keys(Module._extensions);
    for (let i = 0; i < keys.length; i++) {
        let currentPath = filePath + keys[i];
        // 尝试加载后缀查找
        if (fs.existsSync(currentPath)) {
            return currentPath;
        }
    }
    throw new Error("模块不存在");
};

// 获取文件的后缀名进行加载
Module.prototype.load = function (filename) {
    let extname = path.extname(filename);
    Module._extensions[extname](this);
};

// 缓存模块
Module.cache = {};

// 加载模块
Module._load = function (filepath) {
    // 将路径转化成绝对路径
    let filename = Module._resolveFilename(filepath);
    console.log("filename---->", filename);

    // 获取路径后不要立即创建模块,先检查是否有缓存
    let cacheModule = Module.cache[filename];
    if (cacheModule) {
        return cacheModule.exports;
    }

    // 进行模块的创建,这里需要保证每个模块的唯一性,需要通过唯一路径进行查找
    let module = new Module(filename);
    // 缓存模块
    Module.cache[filename] = module;
    // 模块加载
    module.load(filename);
    return module.exports;
};

// 自己实现的 require 方法
function kaimoRequire(filepath) {
    // 根据路径加载模块
    return Module._load(filepath);
}

// 测试
let k = kaimoRequire("./37/a");

console.log("测试 kaimoRequire 输出 k---->", k);

新建 a.js 文件

let name = "kaimo313";
console.log("okk");
module.exports = name;

在这里插入图片描述

总结

  1. require 语法是同步的,用的是 fs.readFileSync
  2. 最终 require 语法返回的是 module.exports
  3. 模块的 exports 和 module.exports 引用的是同一个变量
  4. 模块是动态加载的,每次 require 都会获取最新的导出的结果,可以将 require 写到条件中
  5. 更改 exports 的引用,不会导致 module.exports 的变化
  6. 循环引用的解决方案就是不循环引用,一般不会出现,如果出现只能加载部分数据

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/651031.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

EndNote下载安装与引用

哎&#xff01;写论文这个事真是没有头绪啊&#xff0c;今天研究一下参考文献怎么搞&#xff0c;发现了EndNote&#xff0c;但是这玩意感觉写中文的论文用还可以&#xff0c;英文的不太会用。这里记录一下安装使用过程&#xff0c;方便以后查阅。 EndNote下载安装与引用 EndNot…

React学习[一]

React学习[一] React概述React特点声明式基于组件学习一次&#xff0c;随处可用 React基本使用React使用方法说明 React脚手架意义脚手架初始化项目npx命令介绍 在脚手架中使用react JSXJSX的基本使用JSX使用步骤 JSX中使用JavaScript表达式嵌入式JS表达式 JSX的条件渲染JSX的列…

Model Checking(模型检测)

1. Definition 给定一个系统和一个我们期待拥有的属性P&#xff0c; Model checking 会探索这个系统的每个状态&#xff0c;验证系统是否满足定义的性质。如果满足直接返回True,否则会给出一个反例(counter example)。如果系统被证明是正确的&#xff0c;说明该系统的所有的行…

H3C-HCL模拟器-STP生成树协议实验

一、实验拓扑图 二、实验步骤 1&#xff09;CRT连接并重命名 若遇到连接失败&#xff0c;先在HCL中启动命令行配置 2&#xff09;启动所有设备 3&#xff09;4台交换机重新命令 4&#xff09;查看信息 ① SW1的MAC地址&#xff1a;SW1是根桥 为什么SW1是根桥&#xff1f; HC…

图像坐标转换:一个点绕着另一个点逆时针旋转角度平移后的坐标

图像坐标系&#xff1a;x向右增大&#xff0c; y向下增大。 点A在图像中的坐标&#xff08;x1, y1&#xff09; 点B在图像中的坐标&#xff08;x2, y2&#xff09; 点B绕着点A逆时针旋转a弧度&#xff0c;旋转后的点B坐标为{x (x2 - x1)*cos(a) (y2 - y1)*sin(a) x1&#xf…

java基础——有多少是你不知道的?

java基础——有多少是你不知道的&#xff1f; 一、&&和||二、Integer和int三、String、StringBuffer、StringBuilder的区别四、i1<i居然是成立的&#xff1f;五、一脸懵逼的null问题六、整数除法向上取整你知道多少种&#xff1f;七、这也能运行&#xff1f; 一、&a…

QML 与 Python 交互

在 Qt 中&#xff0c;C 和 QML 交互一般有如下三种方法 上下文属性&#xff1a;setContextProperty( )向引擎注册类型&#xff1a;调用 qmlRegisterType( )QML 扩展插件&#xff1a;虽然有很大的灵活性&#xff0c;但是用 Python 创建 QML 插件比较麻烦&#xff0c;所以这种方法…

【补充:CAN卡通信的下位机-STM32cubeIDE-hal库+STMF1xx+数据发送和接收+中断接收方式+基础样例3】

【CAN卡通信的下位机-STM32cubeIDE-hal库STMF4xx数据发送和接收中断接收方式基础样例3】 1、概述2、实验环境3、问题描述4、大佬指点与解决问题5、实验效果截图6、代码连接7、细节部分8、总结 ) 1、概述 从第一篇F1和F4上采用轮询的方式调试can&#xff0c; 【CAN卡通信的下位…

如何用Jmeter进行接口测试 ,这应该是全网最详细的教程了

一、Jmeter 的使用步骤 打开Jmeter 安装包&#xff0c;进入\bin 中&#xff0c;找到"jmeter.bat", 点击打开即可。 在下图打开的Jmeter 页面中&#xff0c;右键“测试计划” -> “添加” -> "Threads(Users)" -> “线程组”&#xff0c; 建立线…

Allure安装、使用、Jenkins集成

目录 一、allure介绍 二、安装allure服务 三、安装pytest、allure-pytest 插件 四、生成报告 五、allure其他使用 5.1 给测试报告添加各种附件 5.2 添加用例标题和描述信息 5.3 添加链接 5.4 标记测试用例 5.5 优先级 六、allure和jenkins集成 一、allure介绍 all…

2023年5月青少年软件编程(图形化) 等级考试试卷(三级)

青少年软件编程&#xff08;图形化&#xff09; 等级考试试卷&#xff08;三级&#xff09; 一、 单选题(共 25 题&#xff0c; 共 50 分) 1.关于变量&#xff0c; 下列描述错误的是&#xff1f; &#xff08; &#xff09; A.只能建一个变量 B.变量可以隐藏 C.变量可以删除 D.…

【抽样调查】实验

文章目录 1、数组矩阵简单抽样&#xff08;1&#xff09;构造数组&#xff08;2&#xff09;构造矩阵&#xff08;3&#xff09;产生来自正态分布的随机数&#xff08;4&#xff09;从正态总体中抽取若干个样本&#xff08;5&#xff09;对矩阵的行或列进行统计计算 2、R软件作…

输入信号、冲激响应与卷积

输入信号与冲激响应的离散卷积 系统冲激响应&#xff1a; h ( t ) ∑ τ 0 ∞ x ( t ) δ ( t − τ ) h(t)\sum_{\tau0}^{\infty}x(t)\delta(t-\tau ) h(t)τ0∑∞​x(t)δ(t−τ) 上式中 h ( t ) h(t) h(t)是冲激信号输入到系统后系统的输出&#xff0c;也是系统对外在激…

stl容器vector笔记

Vector 一、初始化二、常用方法1. 访问元素at()、下标、data()、front()、back()2. push_back()、pop_back()尾部增删元素3. insert()在pos前插入元素&#xff0c;返回插入位置4. erase()擦除元素&#xff0c;返回擦除元素后的元素位置5. clear()清空容器6. resize()改变容器元…

C语言中函数返回数组(一维和二维)

文章目录 函数返回一维数组函数返回二维数组 C语言中函数返回数组是很重要的一种应用&#xff0c;有时候在程序中调用函数返回数组可以更容易的实现我们想要的某些操作&#xff0c;比如一次返回多个值&#xff0c;这篇文章带来的是C语言中函数返回一维数组和二维数组的例子。 函…

Python自动化测试框架我到底应该学哪一个?

企业中&#xff0c;自动化必定会演变成搭建测试框架&#xff0c;这是为什么呢&#xff1f; 可能有一些刚刚进入软件测试行业的朋友还不理解什么是测试框架&#xff0c;没关系&#xff0c;首先我们知道一点&#xff0c;为什么自动化会演变成搭建测试框架呢&#xff1f; 第一个…

Ribbon和 Nacos服务注册中心

✅作者简介&#xff1a;大家好&#xff0c;我是Cisyam&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Cisyam-Shark的博客 &#x1f49e;当前专栏&#xff1a; 微服务探索之旅 ✨特色专…

软件测试面试自己都不上心,就不要抱怨别人对你冷眼旁观

昨日表哥恳请帮他的学生投递一下开发岗的简历&#xff0c;举手之劳&#xff0c;这忙必须得帮。但当发来学生的简历后&#xff0c;我吐槽了“这简历平平无奇&#xff0c;没有任何亮点&#xff0c;如何令人另眼相看&#xff1f;”表哥说&#xff0c;学生经历不多&#xff0c;总不…

离散数学题目收集整理练习(期末过关进度20%)

✨博主&#xff1a;命运之光 &#x1f984;专栏&#xff1a;离散数学考前复习&#xff08;知识点题&#xff09; &#x1f353;专栏&#xff1a;概率论期末速成&#xff08;一套卷&#xff09; &#x1f433;专栏&#xff1a;数字电路考前复习 ✨博主的其他文章&#xff1a;点击…

一文看懂什么是广告联盟,未来可期吗?

日常学习一些行业内的相关基础知识&#xff0c;可以在工作中更好地理解业务。在互联网广告行业中&#xff0c;广告联盟是很重要的存在&#xff0c;我们今天一起来了解下什么是广告联盟吧。 文中GG联盟广告联盟 GG广告 一. 定义 GG联盟通指网络GG联盟&#xff0c;指集合中小网…