zoukankan      html  css  js  c++  java
  • 浅谈node之require加载模块原理

      相信大家平时写代码都使用过require,那么今天我们简单的写写这个原理。

      首先先了解下前端有几种模块分别是干什么的:前端模块规范有三种:CommonJs,AMD和CMD。

      1.CommonJs用在服务器端,AMD和CMD用在浏览器环境
      2.AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。
      3.CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。
      4.AMD:提前执行(异步加载:依赖先执行)+延迟执行
      5.CMD:延迟执行(运行到需加载,根据顺序执行)
      下面以最常用的commonjs为例,看他从加载到使用都经历了些什么。
      先看看commonjs 规范
        1.每一个js文件都是一个模块
        2.导出的模块的方式 都使用 module.exports
        3.引入模块 require
      下面就开始写我们第一个包  
    let str = 'hello world';
    module.exports = str;

      开始使用第一个包,这个里面要注意的一点就是如果是我们自己写的文件模块要写路径

    let str = require('./str.js');
    console.log(str);

      好了,自己的模块现在就写出来了,那么我们现在看看这个里面都发生了些什么。

      如果是第一次加载,首先是Module._load模块加载,Module._resolveFilename 模块解析文件名解析出一个绝对路径出来使用tryModuleLoad()尝试加载模块,先看有没有后缀名,没有后缀名则加上后缀名去目录底下寻找这个文件,找到了就开始读取文件内容执行文件代码(本质上就是创建一个匿名函数创建一个沙箱,沙箱里面执行代码并返回结果当模块被加载的时候),并创建这个模块没找到或者代码异常不合法则会抛出错误,并把这个模块加入到Module._cache模块进行缓存,多次require只会走一次这个这个读取流程,如果不是第一次加载那么会读Module._cache模块的缓存。

      不过代码执行的时候会有些问题,比如常见的eval和new Function,但是这个会有问题,因为eval是不干净的执行,eval是依赖于上下文环境可能会污染变量。

    let a = 'aaa';
    eval('console.log(a)')

      new function会把模块变成匿名函数,缺点也是不干净执行,依赖于上下文关系,容易变成互相引用。所以node里面执行代码就使用了vm这个沙箱,这个沙箱不依赖于外部环境,他的原理就是创建一个闭包开始执行函数并返回结果。

      我们按照这个思路来写下:

    let fs = require('fs');
    let vm = require('vm');
    let path = require('path');
    function Module(id) {
      this.id = id;
      this.exports = {}
    }
    Module.wrapper = [
      "(function (exports, require, module, __filename, __dirname) {",
      "})"
    ]
    Module.wrap = function (script) {
      return Module.wrapper[0] + script+ Module.wrapper[1];
    }
    Module._extensions = {
      '.js':function (module) {
        let content = fs.readFileSync(module.id, 'utf8');
        let funcStr = Module.wrap(content);
        let fn = vm.runInThisContext(funcStr);
        fn.call(module.exports,module.exports,req,module); // exports = {}
      },
      '.json':function (module) {
        module.exports = JSON.parse(fs.readFileSync(module.id, 'utf8'));
      }
    }
    // 解析文件名
    Module._resolveFilename = function (p) {
       if((/.js$|.json$/).test(p)){
         // 以js或者json结尾的
         return path.resolve(__dirname, p);
       }else{
        // 没有后后缀  自动拼后缀
         let exts = Object.keys(Module._extensions);
         let realPath;
           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){
            throw new Error('module not exists');
           }
           return realPath
       }
    }
    Module._cache = {};
    function tryModuleLoad(module){
      let ext = path.extname(module.id);//扩展名
      // 如果扩展名是js 调用js处理器 如果是json 调用json处理器
      Module._extensions[ext](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); // 没有模块就创建模块
      Module._cache[filename] = module;// 每个模块都有exports对象 {}
    
      //尝试加载模块
      tryModuleLoad(module);
      return module.exports
    }
    function req(p) {
      return Module._load(p); // 加载模块
    } 
    
    let str1 = req('./str.js');
    str2 = req('./str.js'); // 缓存靠的就是绝对路径来缓存的

      ok,这个就是require原理,学了包那么就自己写一个包发布到npm上开始试(作)验(死)之旅吧。

      最后是整体node加载包的流程图,有兴趣的同学可以写写试试看(*^▽^*)。

      node模块加载策略。

      

       文件模块查找规则

        

      首先你得有个npm账号,这个去npm官网上注册一个就可以了,选个文件夹npm init然后开始写代码,写完代码之后npm login 输入你的npm信息之后npm publish这样就完事了,如果你想删除自己的包npm --force unpublish 包名。

       最后推荐一个包nrm,这个包可以切换下载包的源,里面不用配置直接使用,安装方法npm i nrm -g,查看包源nrm ls ,切换包源nrm use cnpm 

  • 相关阅读:
    Java 中的四种引用
    vue 移动端的开发
    使用java语言实现一个动态数组(详解)(数据结构)
    深度长文回顾web基础组件
    告诉你如何回答"线上CPU100%排查"面试问题
    超实用的mysql分库分表策略,轻松解决亿级数据问题
    【源码解析】凭什么?spring boot 一个 jar 就能开发 web 项目
    java中的守护线程
    Excel导入导出工具(简单、好用且轻量级的海量Excel文件导入导出解决方案.)
    spring-data-redis-cache 使用及源码走读
  • 原文地址:https://www.cnblogs.com/qiaohong/p/9708389.html
Copyright © 2011-2022 走看看