commonjs规范

定义

commonjs规范定义了几个点:

  • 1、如何声明一个模块,node中的一个文件就是一个模块
  • 2、每个模块都需要导出最终的结果 module.exports
  • 3、每个模块引用其他的模块的时候,需要使用require 语法

其实就是把文件读取出来之后加一个函数执行,最终返回的是 module.exports

module.exportsexports 对象指定了同一个空间,但是exports 指向改变了 不会导致 module.exports 更改。

//一个空的js文件编译的结果是
(function(){
    module.exports = exports = {};
    return module.exports;
})()

// 如果写成下面的
exports.name = 'romin'
// 导出的结果就是
(function(){
    module.exports = exports = {name:'romin'};
    return module.exports;
})()
复制代码

原理

大概原理就是把引用的文件读取出来,然后执行,但是读取出来的内容肯定都是字符串,怎么才能让字符串执行呢?这时候,可能会想到 eval 。

1、eval 是否可行

eval('console.log('a')')
复制代码

但是 eval 执行不太干净,eval 会从当前的执行上下文中去取相关的变量,举个?:

// a文件
module.exports = name;
// 导出的结果就是,
(function(){
    module.exports = exports = name;
    return module.exports;
})()
// b 文件中引用,读取到了 a 导出的函数,读出来的是字符串,
var n = require('./a');
// eval('(function(){
//   module.exports = exports = name;
//    return module.exports;
//})()')
var name = 'romain';

a 文件中没有有name 这个变量,执行结果就是错的,但是在 b 文件中可以正常是用了。
复制代码

所以 eval 不靠谱

2、new Function

let fn = new Function(`let name = 'romain' \r\n return name`);
let n = fn();// romin
复制代码

node 中也不是这么实现的

3、vm 沙箱 ,nod

终极武器- -vm ,相当于一个虚拟机,它的 runInThisContext 可以让我们的代码在一个干净的上下文中执行。

let vm = require('vm');
let fn = vm.runInThisContext('content//需要执行的代码')
// 下面就是fn
function (exports,require,module,__filename,__dirname){
    /*content*/
}
复制代码
3.1 先了解一个 path
let path = require('path');
console.log(path.resolve('my','file'));
// /Users/mac/Documents/JS/node/my/file
console.log(path.join('my','file'))
// my/file
console.log(__dirname)
// /Users/mac/Documents/JS/node
console.log(__filename)
// /Users/mac/Documents/JS/node/test.js
console.log(path.extname(__filename))
// .js
复制代码
3.2 主要的几个步骤
  • 1、每个模块都有一个 require 方法: Module.prototype.require
  • 2、Module.load -->模块的加载
  • 3、Module._resolveFilename --> 解析出文件的绝对路径
  • 4、Module.__cache --> 模块的缓存,如果这个模块加载过就直接返回 module.exoprts
  • 5、产生模块---> new Module 模块上有两个重要的属性,一个是id,另一个是 exports 对象
  • 6、将模块放入缓存中
  • 7、尝试加载模块,require('./xx.js') require('.json') require('.node')
  • 8、Module._extentions 根据文件的后缀名做对应的处理,读取文件
  • 9、 Module.wrap 把内容包裹起来
  • 10、vm.runInThisContext()
3.2.1
let path = require('path');
let fs = require('fs');
let vm = require('vm');

function Module(id){
  this.id = id
  this.exports ={}
}

// 缓存
Module._cache = {}

function req(id){
  // 转出一个绝对路径, 如果没有后缀,需要依次增加 .js,.json,.node 来舱室加载模块
  let filaname = path.resolve(__dirname,id);
  
  // 如果这个文件可以访问,说明这个文件已经存在,如果抛出异常说明该文件不存在,需要增加后缀
  if(!path.extname(filaname)){
    
    let extentionNames = Object.keys(Module._extentions);

    for(let i =0;i<extentionNames.length;i++){
      filaname = `${filaname}.${extentionNames[i]}`
      try{
        fs.accessSync(filaname);
      }catch(e){
        console.log('不存在')
      }
    }
  }
  
  // 缓存根据的是绝对路径,如果有缓存就直接返回出缓存的
  if( Module._cache[filaname]){
    return  Module._cache[filaname].exports;
  }

  let module = new Module(filaname)
  Module._cache[filaname] = module;
  
  // 尝试 加载模块
  tryModuleLoad(module);
  // 默认返回 module.exports 对象
  return module.exports;
}



Module.wrapper = [
  '(function(exports,require,module,__filename,__dirname){',
  '\n});'
]

Module._extentions = { };

Module._extentions['.js'] = function(module){
  let content = fs.readFileSync(module.id,'utf8');
  // 构建了一个匿名的函数, 但是它现在还是一个字符串
  content = Module.wrapper[0]+content+Module.wrapper[1];
  let fn = vm.runInThisContext(content);
  // function (exports,require,module,__filename,__dirname){let str = 'romin'
      //   module.exports = str;
  // }
  fn.call(module.exports,exports,req,module)
};

Module._extentions['.json'] = function(module){
  module.exports = JSON.parse(fs.readFileSync(module.id,'utf8'))
};


function tryModuleLoad(module){
  let extentions = path.extname(module.id)||'.js' //获取文件的扩展名
  Module._extentions[extentions](module)
}

let str = req('./1.js');
console.log(str); //'romin'
复制代码

4 module.exportsexports 的区别

有人会经常问到 这两者的区别: 看下面的例子

// a.js
let name  = 'romin';
exports.name = name;
console.log(module.exports) // { name: '' }
console.log(exports) // { name: '' }
console.log(module.exports === exports)  //true
复制代码
// b.js
let name = require('./a');
console.log(name); // 'romin'
复制代码

exportsmodule.exports 都是内置空对象,而默认导出的是 module.exports,如果导出的时候没有给 module.exports赋值,那么会自动执行 module.exports = exports;如果说给module.exports进行了赋值,exports 的赋值就没有了意义。

// a.js
let name  = 'xxx';
exports.name = name;
module.exports = 'romin'
console.log(module.exports) // 'romin'
console.log(exports) // { name: 'xxx' }
console.log(module.exports === exports)  //false
复制代码
// b.js
let name = require('./a');
console.log(name); // 'romin'
复制代码

转载于:https://juejin.im/post/5cdcdf79e51d454757162542

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值