Node.js (Node module principle analysis [detailed])

1. Node module

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

2. Execute the read code from the file

We all know that files can be read through the fs module, but the read data is either binary or string. Neither binary nor string can be executed directly.
But we know that if it is a string, there is a way to execute it in JS: eval or new Function;

2.1, execute code through eval

Disadvantages: There are dependencies, and strings can access external data, which is not safe.

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

2.2. Execute code through new Function

Disadvantages: There are dependencies, and global data can still be accessed, which is not safe.

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. Execute code through the vm virtual machine of NodeJS

  • runInThisContext : Provides a safe environment for us to execute the code in the string. The environment provided by runInThisContext cannot access local variables, but can access global variables (that is, variables on 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 : Provides a safe environment for us to execute the code in the string. The provided environment cannot access local variables, nor can it access global variables (that is, variables on 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. Principle analysis of Node module

When looking at the official implementation principle, it is best to use a lower version of node, and then use debug to view it .
Since a file is a module, since you want to use a module, you must first import the module through require(). So it can be inferred that the function of require() is actually to read the file. So if you want to understand how Node implements modules, you must first understand how to execute the read code.

Node module loading process analysis

  1. A require method is implemented internally .
    function require(path) { return self. require(path); }

  2. Module files are loaded through the static __load method of the Module object .
    Module.prototype.require = function(path) { return Module._load(path, this, /* isMain */ false); };

  3. Through the static _resolveFilename method of the Module object, get the absolute path and add the suffix .
    var filename = Module._resolveFilename(request, parent, isMain);

  4. Determine whether there is a cache based on the path, and if not, create a new Module module object and cache it .
    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. Use the tryModuleLoad method to load the module tryModuleLoad(module, filename) .
    5.1. Take out the module suffix
    var extension = path.extname(filename);
    5.2. Find different methods according to different suffixes and execute the corresponding method, load the module
    Module._extensions[extension](this, filename);
    5.3. Convert if it is JSON Into an object
    module.exports = JSON.parse(internalModule.stripBOM(content));
    5.4. If it is JS, wrap a function
    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});' ]; The code after the function, get the execution result (String – Function)







    var compiledWrapper = vm.runInThisContext(wrapper);
    5.6. Use call to execute the fn function and modify the value of module.exports
    var args = [this.exports, require, module, filename, dirname];
    var result = compiledWrapper.call(this. exports, args);
    5.7, return module.exports
    return module.exports;

Implement a require method yourself .

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
}

Attachment: call method:
insert image description here

おすすめ

転載: blog.csdn.net/weixin_44767973/article/details/127599500