hey,你的CommonJS规范

hey,你的CommonJS规范

CommonJS规范概述

  1. 一个文件就是一个模块,拥有单独的作用域
  2. 普通方式定义的变量,函数,对象都属于该模块内的私有属性
  3. 通过require来加载其他模块通过module.exports导出的内容
  4. 通过exportsmodule.exports来导出模块中要暴露的内容

简单使用一下CommonJS

  • A文件
let str = 'hello world';
module.exports = str;
复制代码
  • B文件
// 通过require引入A模块中使用exports或module.exports的内容
let a = require('./A');
console.log(a); // => 'hello world'
复制代码

CommonJS的简易实现

先梳理一下流程:

  1. Module._load 加载模块require引入的模块
  2. Module._resolveFilename 通过模块名 解析出一个绝对路径
  3. Module._cache 如果之前require过这个模块 就存到缓存中
  4. new Module 如果缓存中没有 就创建模块 每个模块都有一个exports属性
  5. tryModuleLoad() 尝试加载模块
  6. Module._extensions 依据模块的扩展名执行对应的方法

准备工作

先引入我们需要的node内置模块

// path是专门用来处理路径的模块
let path = require('path');
// fs是专门用来操作文件的模块
let fs = require('fs');
// 我们需要用vm来提供一个将字符串当成变量执行的沙箱环境
let vm = require('vm); 复制代码

创建一个Module类,我们每次引入都会new一个Module实例,引入的其实是这个实例上的exports属性

function Module(p) {
    // 将当前模块的绝对路径当做这个模块的标识符存在实例身上
    this.id = p;
    // exports存的是其他模块require引入的内容
    this.exports = {};
}
复制代码

一些静态属性

// 缓存 我们将每个模块的绝对路径当做key,每次引入都判断这个key存不存在,如果存在就直接返回之前存过的
Module._cache = {}

// 我们根据不同的后缀名执行不同的加载方法
Module._extensions = {
    '.js'() {
        
    },
    '.json'() {
        
    }
}

// 引入的模块是js文件时需要套一个闭包
Module.wrapper = [
    '(function(exports, req, module){'
        
    '})'
];
复制代码

创建一个引入模块的方法,为了与require区别开我们叫req

当我们引入模块的时候,会调用Module._load方法来加载模块

function req(p) {
    return Module._load(p)
}
复制代码

Module._load 加载模块

我们主要的操作都在Module._load方法中执行,在Module._load方法中,首先会把引入的模块通过Module._resolveFilename方法解析出一个绝对路径

Module._load = function(p) {
    let filename = Module._resolveFilename(p);
}
复制代码

Module._resolveFilename 解析文件名 返回一个绝对路径

  • 我们首先判断模块有没有后缀名
    • 如果有后缀名,判断这个文件存在还是不存在,如果存在就返回一个绝对路径,不存在就抛出一个异常
    • 如果没有后缀名,我们依次拼接Module._extensions中的key,如果存在 就返回
// fs.accessSync(path); 判断文件存不存在,如果存在一切正常,如果不存在就会报错,所以用try catch包起来
Module._resolveFilename = function(p) {
    let realPath;
    // 如果有js或json后缀名
    if (/\.js$|\.json$/.test(p)) {
        try {
            realPath = path.resolve(__dirname, p);
            fs.accessSync(realPath);
            return realPath;
        } cache(e) {
            throw new Error('Module not fount')
        }
    } else {
        let exts = Object.keys(Module._extensions);
        for (let i = 0; i < exts.length; i++) {
            // 将后缀名拼上
            let temp = path.resolve(__dirname, p + exts[i]);
            // 判断文件存不存在
            try {
                fs.accessSync(temp);
                // 如果存在就保存一下值 并跳出循环
                realPath = temp;
                break;
            } catch(e) {
                
            }
        }
        
        if (realPath) {
            return realPath;
        }
        throw new Error('Module not found')
    }
}
复制代码

Module._cache 判断有没有加载过

我们之前说过,会把这个模块的绝对路径当做key存到缓存中,现在已经解析出一个绝对路径了,接下来只需要判断缓存中有没有

Module._load = function(p) {
    let filename = Module._resolveFilename(p);
    // 获取缓存
    let cache = Module._cache[filename];
    // 如果有缓存,返回缓存中导出的内容
    if (cache) {
        return cache.exports;
    }
    // 如果之前没有缓存 代表第一次引入
    // 每个实例上都有一个私有属性id,存的是自己的绝对路径(唯一标识),还有一个exports属性 存的的引入的模块导出的内容
    let module = new Module(filename);
    // 加载模块,我们将当前实例传过去
    tryLoadModule(module);
}
复制代码

tryLoadModule() 加载模块

在这个方法中我们只需要通过模块的后缀名去执行Module._extensions中对应的加载方法

// fs.extname() 获取文件后缀名
function tryLoadModule(module) {
    // module.id 存的是当前模块的绝对路径
    let ext = fs.extname(module.id);
    // 执行Module._extensions中对应的加载方法
    Module._extensions[ext](module);
}
复制代码

Module._extensions 真正的读取文件方法

// fs.readFileSync() 读取文件内容
Module._extensions = {
    '.json'(module) {
        // 如果是json文件我们之间读取后赋值给module的exports属性就好了
        module.exports = fs.readFileSync(module.id, 'utf8');
    },
    '.js'(module) {
        // 如果是js文件的话,我们要给他套一个闭包,实现模块化,还记得上面的Module._wrapper属性吗
        let fnStr = Module._wrapper[0] + fs.readFileSync(module.id, 'utf8') + Module._wrapper[1];
        console.log(fnStr);
        // 打印出来的是
        // '(function(exports, req, module){'
        //  通过fs.readFileSync 读取出的内容
        // '})'
    
        // 打印出来的是一个字符串,那么怎么让这个字符串执行呢
        // 1. eval() 2. new Function() 3. vm模块
        // 我们选择使用vm模块的runInThisContext方法,因为这样不依赖上下文环境
        
        let fn = vm.runInThisContext(fnStr);
        // 现在fn就是一个函数了,不是一个字符串了,可以直接执行
        fn.call(module.exports, module.exports, req, module);
        // fn的this是module.exports,所以我们再node中打印this,有时是一个空对象,我们将上个模块导出的内容给到exports属性了,我们最后只需要 return module.exports这个属性就可以了
    }
}
复制代码

结束

Module._load = function(p) {
    let filename = Module._resolveFilename(p);
    let cache = Module._cache[filename];
    if (cache) {
        return cache.exports;
    }
    let module = new Module(filename);
    tryLoadModule(module);
    
    // 返回exports中的内容
    return module.exports;
}

function req(p) {
    // Module._load() 返回的就是上个模块导出的内容,我们在直接return就可以了
    return Module._load(p)
}
复制代码

完结撒花

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/101420.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • codeblocks中文编码问题

    codeblocks中文编码问题其实这是老调重弹的问题了,在windows下面出现中文乱码大多都是编码格式的问题不一致的问题,最简单的就是uft-8和gbk冲突的问题。如果一个文件本来是以utf-8存的,但是以gbk打开,当然会出现乱码了。方法一:用utf-8打开文件linux中中文常用的格式是utf-8,minGW是gcc的编译器,默认是utf-8格式,但是我们打开Setting/Editor/EncodingSetting

    2022年7月26日
    6
  • intellij idea破解版(sw2018破解失败)

    IntelliJIDEA2018破解方法输入如下地址激活:http://www.luochenzhimu.com:1024 参考链接:https://www.7down.com/soft/223192.html

    2022年4月11日
    55
  • 图像视频滤镜算法详解—滤镜初识

    图像视频滤镜算法详解—滤镜初识   所谓滤镜,最初是指安装在相机镜头前过滤自然光的附加镜头,用来实现调色和添加效果,也就是硬件滤镜。我们做的滤镜算法又叫做软件滤镜,是对大部分硬件镜头滤镜进行的模拟,当然,误差也就再所难免,我们的宗旨只是无限逼近。也是这个原因,我们无法再现真实的拍摄场景,无法复原照片中未包含的信息,进而也难以实现某些特殊滤镜效果,诸如偏光镜和紫外线滤色镜(UV)的效果等等。目前,从强大的PHOTO…

    2022年7月22日
    10
  • 使用outlook制作签名

    使用outlook制作签名使用outlook制作签名制作签名的几种方法直接在编辑器中编辑在Word中编辑好了复制过去直接在生成的文件上编辑直接用word新建htm格式编辑图片不清晰问题制作签名的几种方法你好,最近公司需要我制作公司签名,之前就想着不就是签名嘛,多简单的事。但是经过一系列的尝试之后,发现还真不容易。主要是因为outlook新建签名的编辑栏不支持直接使用html编辑。下面我介绍我尝试的几种方法直接在编辑…

    2022年10月16日
    3
  • JavaScript表单提交「建议收藏」

    JavaScript表单提交「建议收藏」表单提交在前端编程阶段中也是一个重点。它是由页面数据保存到后台数据库的重要枢纽,通过表单提交的方式将数据上传到数据库进行保存。同时这些数据也包含用户信息、统计信息、日志信息等等。数据的信息不同,上传的方式也不同。在JavaScript中有四种种表单提交的方式:一、 Form表单手动提交(get与post)在没有任何Js代码的影响下,Form表单本身是自带提交功能的。在form元素标签上有两个…

    2022年10月7日
    4
  • oj在计算机领域中指什么,【计算机专业论文】计算机专业教学中OJ平台的应用(共2762字)…

    oj在计算机领域中指什么,【计算机专业论文】计算机专业教学中OJ平台的应用(共2762字)…摘要:传统的教学模式对计算机专业学生的能力培养存在着诸多问题,而OJ(OnlineJudge在线检测程序源代码)平台为计算机教学提供了新的思路,因为OJ平台在学生日常训练方面有一套行之有效的机制,所以对学生的学习兴趣、分析解决问题能力、创新能力等方面的培养都起到了积极的推动作用,OJ平台还可以对学生实践能力进行最直接的考核,因此将OJ平台引入计算机专业教学,可实现以平台促教学,以平台促教改。关键词…

    2022年6月16日
    30

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号