学渣的模块化之路——50行代码带你手写一个common.js规范

简介:

一、简述

  • 一个js文件就是一个模块
  • 会自动把写的代码块套一层闭包
  • 浏览器不兼容CommonJS的根本原因,在于缺少四个Node.js环境的变量.(module,export,require,global)

既然没有,我们就手写一个吧,这里先普及下备用的基础知识,请往下看

二、源码中用到的备用知识

fs.accessSync方法

  • 判断文件是否存在,不存在则报错

  • let fs = require("fs");
    fs.accessSync("./x.js") //当前目录中没有该文件则会报错
    


  • path.extname方法
  • 判断文件的扩展名

  • let path = require("path");
    console.log(path.extname("./c.js"))
    


  • vm.runInThisContext()方法

    • 会创建一个独立的沙箱环境,以执行对参数code的编译,运行并返回结果。vm.runInThisContext()方法运行的代码没有权限访问本地作用域,但是可以访问Global全局对象。


var vm = require('vm');
var localVar = 'initial value';

//在runInThisContext创建的沙箱环境中执行
var vmResult = vm.runInThisContext('localVar = "vm";');
console.log('vmResult: ', vmResult);    //vmResult:  vm
console.log('localVar: ', localVar);    //localVar:  initial value

//在eval中执行
var evalResult = eval('localVar = "eval";');
console.log('evalResult: ', evalResult);	//evalResult:  eval
console.log('localVar: ', localVar);	//localVar:  eval

  • 在上面示例中,分在vm.runInThisContext()方法创建的沙箱环境中和eval()中执行了一段JavaScript代码。 vm.runInThisContext创建的沙箱环境无法访问本地作用域,因此localVar没有被改变。而eval可以访问本地作用域,因此localVar被改变。
  • 有了这些基础知识的储备,已经迫不及待的开始手写源代码了

三、模块化实现

  • 我们想的是构建一个module实例,类似这样{ loaded: false,filename: 绝对路径, exports: 引入模块的结果}
  • loaded检测如果require多次,会进行缓存,
  • filename我们需要把输入的路径变成绝对路径
  • export存放我们加载模块后的结果,至于怎么实现,我们会对应load方法就行实现
  • 最后把export返回,就实现了加载模块
  • 有了这么点想法,我们就一步一步开始实现吧

1、准备工作做好,声明构造函数Module


let path = require('path');
let fs = require('fs');
let vm = require('vm');
// 声明构造函数Module
function Module(filename){
    this.loaded = false; //用于检测是否被缓存过
    this.filename = filename; //文件的绝对路径
    this.exports = {} //模块对应的导出结果
}
//存放模块的扩展名
Module._extensions = ['.js','.json'];
//检测是否有缓存
Module._cache = {};
//拼凑成闭包的数组
Module.wrapper = ['(function(exports,require,module){','\r\n})'];


已经把准备声明的变量和模块声明好,没有使用的先不用理会,下面用到的时候就明白了

2、实现一个require方法,实现加载模块


function req(path) { //自己实现require方法,实现加载模块
    // 根据输入的路径 变出一个绝对路径
    let filename = Module._resolveFilename(path);
    // 通过这个文件名创建一个模块
    let module = new Module(filename);
    module.load(); //让这个模块进行加载 根据不同的后缀加载不同的内容
    return module.exports
}


3、有了这个架构想法,先实现 Module._resolveFilename方法


//如果没写扩展名,我们给它默认添加扩展名
Module._resolveFilename = function (p) {
    p = path.join(__dirname,p);
    if(!/\.\w+$/.test(p)){
        //如果没写扩展名,尝试添加扩展名
        for(let i=0;i

4、此时的filename是存在这个文件的绝对路径,接下来对不同后缀名加载不同的内容


Module.prototype.load = function () {
  // 加载模块本身 js按照js加载 json按照json加载
  let extname = path.extname(this.filename); //判断后缀名
  Module._extensions[extname](this); //把module实例传过去
}

5、先实现加载.json后缀的方法,直接文件读取,并赋值给module.export


Module._extensions['.json'] = function (module) {
    let content = fs.readFileSync(module.filename,'utf8');
    module.exports = JSON.parse(content);
};


写了这么多,我们先来测试下.json结尾的文件,有没有被写入到module.exports中


6、实现以.js结尾的后缀名的方法


// 后缀名为js的加载方法
Module._extensions['.js'] = function (module) {
    //取出加载模块的内容
    let content = fs.readFileSync(module.filename,'utf8');
    // 形成闭包
    let script = Module.wrap(content);
    //让js代码执行
    let fn = vm.runInThisContext(script);
    fn.call(module,module.exports,req,module)
};


7、我们来看下Module.wrap(content)如何进行代码解析的


Module.wrap = function(content){
    return Module.wrapper[0] + content +Module.wrapper[1];
};


是不是很简单,只是简单的js字符串拼接成闭包的函数,然后让其进行执行。到目前为止,基本的功能已经实现了,我们先来测试下。但是我们还没实现缓存,接下来继续


8、最后我们在处理下缓存,就完美结束啦


//检测是否有缓存
Module._cache = {};
//改造下req方法
function req(path) { //自己实现require方法,实现加载模块
    // 根据输入的路径 变出一个绝对路径
    let filename = Module._resolveFilename(path);
    if(Module._cache[filename]){ //有缓存直接取缓存中的
        return Module._cache[filename].exports;
    }
    // 通过这个文件名创建一个模块
    let module = new Module(filename);
    module.load(); //让这个模块进行加载 根据不同的后缀加载不同的内容
    Module._cache[filename] = module; //第一次加载先缓存
    return module.exports
}

这样一来就完美实现了,共不到50行代码,开不开心,愉不愉快。最后贴上源码供大家参考,别忘记喜欢哦

9、源码贴上(仅供参考)

let path = require('path');
let fs = require('fs');
let vm = require('vm');
// 声明构造函数Module
function Module(filename){
    this.loaded = false; //用于检测是否被缓存过
    this.filename = filename; //文件的绝对路径
    this.exports = {} //模块对应的导出结果
}
//存放模块的扩展名
Module._extensions = ['.js','.json'];
//检测是否有缓存
Module._cache = {};
//拼凑成闭包的数组
Module.wrapper = ['(function(exports,require,module){','\r\n})'];
//如果没写扩展名,我们给它默认添加扩展名
Module._resolveFilename = function (p) {
    p = path.join(__dirname,p);
    if(!/\.\w+$/.test(p)){
        //如果没写扩展名,尝试添加扩展名
        for(let i=0;i



原文发布时间为:2018年06月25日

原文作者:言sir

本文来源:掘金  如需转载请联系原作者

相关文章
|
消息中间件 弹性计算 物联网
MQTT常见问题之发布MQTT主题消息失败如何解决
MQTT(Message Queuing Telemetry Transport)是一个轻量级的、基于发布/订阅模式的消息协议,广泛用于物联网(IoT)中设备间的通信。以下是MQTT使用过程中可能遇到的一些常见问题及其答案的汇总:
|
数据可视化 Linux 数据中心
服务器版Rstudio-server初体验丨随时随地云端处理数据,不再担心电脑崩盘重启了!
服务器版Rstudio-server初体验丨随时随地云端处理数据,不再担心电脑崩盘重启了!
|
JavaScript 前端开发
React craco 解决 webpack < 5 used to include polyfills for node.js core ...
React craco 解决 webpack < 5 used to include polyfills for node.js core ...
1038 0
|
11月前
|
机器学习/深度学习 数据采集 算法
机器学习在生物信息学中的创新应用:解锁生物数据的奥秘
机器学习在生物信息学中的创新应用:解锁生物数据的奥秘
869 36
|
机器学习/深度学习 自然语言处理 PyTorch
PyTorch 中的动态图与静态图:理解它们的区别及其应用场景
【8月更文第29天】深度学习框架中的计算图是构建和训练神经网络的基础。PyTorch 支持两种类型的计算图:动态图和静态图。本文旨在阐述这两种计算图的区别、各自的优缺点以及它们在不同场景下的应用。
3378 0
|
JavaScript 前端开发
React craco 解决 webpack < 5 used to include polyfills for node.js core ...
React craco 解决 webpack < 5 used to include polyfills for node.js core ...
1102 0
|
移动开发 JSON JavaScript
一文带你了解和使用webpack(2024年11月)
欢迎来到我的博客!我是瑞雨溪,一名热爱JavaScript和Vue的大一学生。自学前端已有两年半的时间,目前正努力向全栈开发迈进。如果你在我的博客中有所收获,欢迎关注我,我会持续更新更多优质内容。你的支持是我最大的动力!🎉🎉🎉
318 1
一文带你了解和使用webpack(2024年11月)
|
机器学习/深度学习 自然语言处理 数据处理
通过深度学习识别情绪
通过深度学习识别情绪(Emotion Recognition using Deep Learning)是一项结合多模态数据的技术,旨在通过分析人类的面部表情、语音语调、文本内容等特征来自动识别情绪状态。情绪识别在人机交互、健康监测、教育、娱乐等领域具有广泛的应用。
1594 8
|
前端开发 测试技术 定位技术
【专栏:HTML 与 CSS 实战项目篇】构建一个企业级网站:HTML 与 CSS 实战
【4月更文挑战第30天】本文介绍了使用HTML和CSS构建企业级网站的实战步骤,包括项目概述、页面结构设计、HTML结构搭建、CSS样式设计、具体页面实现、优化与提升。通过合理布局、美观样式和响应式设计,创建现代、简洁的网站,包含主页、关于我们、产品展示、新闻动态和联系我们等页面。优化图片和代码,确保性能,助力企业在数字时代树立良好形象并提升沟通效率。
564 7
|
JavaScript 前端开发 开发者
CommonJS 和 ES6 Module:一场模块规范的对决(上)
CommonJS 和 ES6 Module:一场模块规范的对决(上)

热门文章

最新文章