另一种失败

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

  相信大家平时写代码都使用过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 

 

 

posted on 2018-09-26 17:39  另一种失败  阅读(997)  评论(0编辑  收藏  举报