Node.js(Node模块原理分析【详细】)

1、Node模块

1. 在CommonJS规范中一个文件就是一个模块。
2. 在CommonJS规范中通过exports暴露数据。
3. 在CommonJS规范中通过require()导入模块。

2、执行从文件读取代

我们都知道通过fs模块可以读取文件,但是读取到的数据要么是二进制,要么是字符串。无论是二进制还是字符串都无法直接执行。
但是我们知道如果是字符串,在JS中是有办法让它执行的:eval或者new Function;

2.1、通过eval执行代码

缺点:存在依赖关系,字符串可以访问外界数据,不安全。

let str = 'console.log("hello")'
eval(str) // hello
// 存在依赖关系, 字符串可以访问外界数据,不安全
let name = "lgg";
let str1 = "console.log(name);";
eval(str1); // lgg

2.2、通过new Function执行代码

缺点:存在依赖关系,依然可以访问全局数据,不安全。

let str = "console.log('aaaa');";
let fn = new Function(str);
console.log(fn);
/*anonymous() {
    console.log('aaaa');
  }
*/
fn(); // aaaa
// 存在依赖关系, 字符串可以访问外界数据,不安全
let name = "lgg";
let str = "console.log(name);";
let fn = new Function(str);

2.3、通过NodeJS的vm虚拟机执行代码

  • runInThisContext:提供了一个安全的环境给我们执行字符串中的代码。runInThisContext提供的环境不能访问本地的变量,但是可以访问全局的变量(也就是global上的变量)。
const vm = require('vm')
let str = "console.log('lgg')"
vm.runInThisContext(str) // lgg

let name1 = 'lgg'
let str1 = "console.log(name1)"
vm.runInThisContext(str1) // name is not defined

global.name2 = 'lgg'
let str2 = "console.log(name2)"
vm.runInThisContext(str2) // lgg
  • runInNewContext:提供了一个安全的环境给我们执行字符串中的代码。提供的环境不能访问本地的变量,也不能访问全局的变量(也就是global上的变量)。
let name1 = "lgg"
let str1 = "console.log(name1)"
vm.runInNewContext(str1) // name1 is not defined

global.name2 = "lgg"
let str2 = "console.log(name2)"
vm.runInNewContext(str2) // name2 is not defined

3、Node模块原理分析

在查看官方的实现原理是最好用低版本的node,然后用debug查看
既然一个文件就是一个模块,既然想要使用模块必须先通过require()导入模块。所以可以推断出require()的作用其实就是读取文件。所以想要了解Node是如何实现模块的,必须先了解如何执行读取到的代码。

Node模块加载流程分析

  1. 内部实现了一个require方法
    function require(path) {
    return self.require(path);
    }

  2. 通过Module对象的静态__load方法加载模块文件
    Module.prototype.require = function(path) {
    return Module._load(path, this, /* isMain */ false);
    };

  3. 通过Module对象的静态_resolveFilename方法, 得到绝对路径并添加后缀名
    var filename = Module._resolveFilename(request, parent, isMain);

  4. 根据路径判断是否有缓存, 如果没有就创建一个新的Module模块对象并缓存起来
    var cachedModule = Module._cache[filename];
    if (cachedModule) {
    return cachedModule.exports;
    }
    var module = new Module(filename, parent);
    Module._cache[filename] = module;
    function Module(id, parent) {
    this.id = id;
    this.exports = {};
    }

  5. 利用tryModuleLoad方法加载模块tryModuleLoad(module, filename)
    5.1、取出模块后缀
    var extension = path.extname(filename);
    5.2、根据不同后缀查找不同方法并执行对应的方法, 加载模块
    Module._extensions[extension](this, filename);
    5.3、如果是JSON就转换成对象
    module.exports = JSON.parse(internalModule.stripBOM(content));
    5.4、如果是JS就包裹一个函数
    var wrapper = Module.wrap(content);
    NativeModule.wrap = function(script) {
    return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
    };
    NativeModule.wrapper = [
    '(function (exports, require, module, __filename, __dirname) { ',
    ‘\n});’
    ];
    5.5、执行包裹函数之后的代码, 拿到执行结果(String – Function)
    var compiledWrapper = vm.runInThisContext(wrapper);
    5.6、利用call执行fn函数, 修改module.exports的值
    var args = [this.exports, require, module, filename, dirname];
    var result = compiledWrapper.call(this.exports, args);
    5.7、返回module.exports
    return module.exports;

自己实现一个require方法

const path = require('path')
const fs = require('fs')
const vm = require('vm')

class NJModule {
  constructor(id) {
    this.id = id; // 保存当前模块的绝对路径
    this.exports = {}  
  }
}
NJModule._cache = {}
NJModule.wrapper = ['(function (exports, require, module, __filename, __dirname) { ', '\n});'];
NJModule._extensions = {
  '.js': function(module) {
    // 1、读取js代码
    let script = fs.readFileSync(module.id)
    // 2、将JS代码包裹到函数中
    /*
    (function (exports, require, module, __filename, __dirname) {
    exports.名 = 值;
    });
    * */
    let strScript = NJModule.wrapper[0] + script  + NJModule.wrapper[1]
    // 3、将字符串转换成JS代码
    let jsScript = vm.runInThisContext(strScript )
    // 4、执行转换后的JS代码
    // var args = [this.exports, require, module, filename, dirname];
    // var result = compiledWrapper.call(this.exports, args);
    jsScript.call(module.exports, module.exports) // 在函数中使用的exports就是对象(即module.exports)的exports。因为是对象为引用关系。
    
  },
  '.json': function(module) {
      let json = fs.readFileSync(module.id);
      let obj = JSON.parse(json);
      module.exports = obj
  } 
}

function tryModuleLoad(module) {
  // 4.1取出模块后缀
  let extName = path.extname(module.id)
  NJModule._extensions[extName](module)
}

function njRequire(filePath) {
  // 1.将传入的相对路径转换成绝对路径
  let absPath = path.join(__dirname, filePath)
  // 2.尝试从缓存中获取当前的模块
  let cachedModule = NJModule._cache[absPath]
  if(cachedModule) {
    return cachedModule.exports;
  }
  // 3.如果没有缓冲就自己创建一个njModule对象,并缓存起来
  var module = new NJModule(absPath)
  NJModule._cache[absPath] = module
  // 4.利用tryModuleLoad方法加载模块
  tryModuleLoad(module);
  // 5.返回模块的exports
  return module.exports
}

附:call方法:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值