Node对CommonJS的模块规范


Posted in Javascript onNovember 06, 2019

Node能够以一种相对程度的的姿态出现,离不开CommonJS规范的影响。Node借鉴CommonJS的Modules规范实现了一套非常易用的模块系统,NPM对packages规范的完好支持使得Node应用在开发过程中事半功倍。

在Node中引用模块,需要经历如下三个步骤。

1. 路径分析

Node中的模块分为核心模块和文件模块 。

核心模块是由Node提供的模块,它们在Node源代码的编译过程中就编译进了二进制执行文件,在Node进程启动时,核心模块就被直接加载进内存中,所以在引用核心模块时,文件定位和编译执行这两个步骤可以省略,并且在路径分析中优先判断,所以它的加载速度时最快的。通过require引用核心模块时,直接引用即可。如 require('http')

文件模块是用户编写的模块,它是在运行时动态加载的,需要完整的路径分析,文件定位,编译执行的过程,所以它的速度比核心模块慢。引用文件模块的方式分为三种:

1.以.或..开始的相对路径文件模块。

2.以/开始的绝对路径文件模块。

3.非路径形式的文件模块(自定义模块)。

1,2两种方法用于引用用户自己编写的模块,require会将路径转为真实路径,并以真实路径作为索引,将编译执行的结果(对象)存放在缓存中,由于指定了明确的文件位置,其加载速度慢于核心模块,快于自定义模块。第3中方式用于引用下载的第三方模块,这类模块的查找是最费时的。这里有一个 模块路径 的概念。自定义模块的查找速度慢的原因就在于此。

/**
   通过以下代码,可以看出模块路径的生成规则如下:当前目录下的node_modules目录,父目录下的node_modules目录,沿路径向上逐级递归,直到根目录下的node_modules目录。
 */
 //a.js
 console.log(module.paths) 
 //将打印出如下结果
 [ 'H:\\Files\\qiuzhao\\please-offer\\node_modules',
  'H:\\Files\\qiuzhao\\node_modules',
  'H:\\Files\\node_modules',
 'H:\\node_modules' 
 ]

1. require('../a.js')
2. require('/a.js')
3. require('koa')

2. 文件定位

1) 文件扩展名:CommonJS规范允许在标识符中不包含文件扩展名,这时候Node会按照.js,.json,.node的次序补足扩展名,依次尝试。

2)目录分析和包(自定义模块):在分析提供给require的标识符的过程中,在文件扩展名的依次尝试后,依然没有得到对应的文件,却得到一个目录,这在引用自定义模块并沿着模块路径逐个进行查找时经常会出现,此时Node会将目录当做一个包来处理。这种情况下,Node首先会在当前目录下查找package.json(包描述文件),通过JSON.parse()解析出对象后,从中取出main属性指定的文件名进行定位,视情况而定会j扩展名的分析。如果main属性指定的文件名错误或者根本就没有package.json文件,Node会将index当做默认文件名,然后进行扩展名的依次尝试。如果在目录分析的过程中没有成功定位到任何文件,则进入模块路径的下一个路径进行查找,如果模块路径数组遍历完毕仍未找到文件,则抛出错误。

3. 编译执行

在Node中,每个文件都是一个模块,每个模块都是一个对象,这个对象的定义如下:

function Module(id,parent){
    this.id = id
    this.exports = {}
    this.parent = parent
    if(parent&&parent.children){
      parent.push(this)
    }
    this.filename = null
    this.loaded = false
    this.children = []  //当前模块引用的其他模块会存储在这里
  }

在成功定位到文件后,首先Node会 新建一个对象 ,然后会将文件内容载入并编译执行,并 将模块的exports属性返回给调用方 。针对不同扩展名的文件,有不同的载入方法,通过 require.extensions 可以查看系统以及支持的文件加载方式。

1).js文件:通过fs模块 同步 读取文件后编译执行。

在编译该类型的文件时,Node会对获取得文件内容进行头尾的包装,在头部添加 (function(exports,require,module,__filename,__dirname){\n ,在尾部添加 \n}) 。一个正常的js文件会被包装成如下的样子:

(function(exports,require,module,__dirname,__filename){
   ... 
  })  //从这里可以看出,node对模块的实现,也借鉴了前端js经常使用的利用函数作用域还形成一个独立的空间,以防污染全局作用域,这里node包装了这一过程。

包装之后的代码会通过vm原生模块的runInThisContext()方法执行(类似eval),返回一个具体的function对象(runInThisContext()的作用在这里就是声明一个函数),最后,将当前模块对象(别忘了Node在成功定位到文件后,会首先创建一个module对象)的exports属性,require方法,module本身,以及在之前两步中得到的完整文件路径和文件目录作为参数传递给这个函数。这里有一点经典的例子:

//当我们想为模块的输出定义一个全新的对象时
  
  //error
  exports = {}
  
  
  //right
  module.exports = {}
  
  //这样做的原因时,exports和modlue.exports指向的是同一个对象,而exports={}这种方式,不会影响module.exports指向的对象。Node真正返回给调用者的是module.exports
var val = 10
  var chageVal = function(val){
    val = 100
    console.log(val)
  }
  changeVal(val) //100
  console.log(val) //
  
  /----------------------/
  
  var obj = {
    age:12
  }
  
  var changeName = function(obj){
    obj = {
      age:21
    }
    console.log(obj.age)
  }
  
  changeName(obj) //21
  console.log(obj.age) //12
  
  //出现这种现象的原因是,当调用函数时,传入的是变量的副本。

2).node文件:这是使用C/C++编写的扩展文件,通过dlopen()方法加载最后编译执行的结果。dlopen()方法在不同平台下有不同的实现,通过libuv兼容层封装。实际上,.node的模块文件不需要编译,因为它是编写C/C++模块之后编译生成的,这里只有加载和执行的过程,没有编译的过程。在执行的过程中,模块的exports对象与.node模块产生联系,然后返回给调用者。

3).json文件:通过fs模块同步读取文件后,使用JSON.parse()解析后返回结果。 这种类型的文件是三者中编译最简单的,Node利用fs模块同步读取文件内容后,调用JSON.parse()将其解析成对象,然后将其赋值给模块对象的exports,以供外部调用。

4).其他扩展名文文件:被当做.js文件进行处理。

模块的缓存

与前端浏览器会缓存静态脚本文件已提高性能一样,Node对引用的模块都会进行缓存,以减少二次引入时的开销,不同之处在于,浏览器缓存的是文件,Node缓存的是编译和执行后的对象。require()方法对相同模块的二次加载一律采用缓存优先的方式,这是第一优先级的。每一个编译成功的模块都会将其文件路径做为索引缓存在Module._cache对象上,Module._cache会被赋值给require()方法的cache属性,所以可以通过require.cache还查看已经缓存的模块。如果不想使用缓存的模块,可以在被引用的模块内添加 delete require.cache[module.filename] 。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
页面js遇到乱码问题的解决方法是和无法转码的情况
Apr 30 Javascript
jQuery中innerHeight()方法用法实例
Jan 19 Javascript
浅谈javascript 函数内部属性
Jan 21 Javascript
javascript实现禁止鼠标滚轮事件
Jul 24 Javascript
jQuery实现Email邮箱地址自动补全功能代码
Nov 03 Javascript
使用Node.js处理前端代码文件的编码问题
Feb 16 Javascript
AngularJS 霸道的过滤器小结
Apr 26 Javascript
通过构造函数实例化对象的方法
Jun 28 Javascript
使用JS实现导航切换时高亮显示的示例讲解
Aug 22 Javascript
puppeteer实现html截图的示例代码
Jan 10 Javascript
微信小程序 数据缓存实现方法详解
Aug 26 Javascript
vue实现一个获取按键展示快捷键效果的Input组件
Jan 13 Vue.js
VUE 组件转换为微信小程序组件的方法
Nov 06 #Javascript
vuex存储复杂参数(如对象数组等)刷新数据丢失的解决方法
Nov 05 #Javascript
解决vue.js提交数组时出现数组下标的问题
Nov 05 #Javascript
js+html实现点名系统功能
Nov 05 #Javascript
vuex 实现getter值赋值给vue组件里的data示例
Nov 05 #Javascript
在Vue mounted方法中使用data变量详解
Nov 05 #Javascript
解决vue项目F5刷新mounted里的函数不执行问题
Nov 05 #Javascript
You might like
在PHP3中实现SESSION的功能(二)
2006/10/09 PHP
php中常用字符串处理代码片段整理
2011/11/07 PHP
php实现通用的从数据库表读取数据到数组的函数实例
2015/03/21 PHP
PHP中的traits简单使用实例
2015/05/13 PHP
php截取视频指定帧为图片
2016/05/16 PHP
PHP中静态变量的使用方法实例分析
2016/12/01 PHP
IE8 下的Js错误HTML Parsing Error...
2009/08/14 Javascript
javascript之典型高阶函数应用介绍
2013/01/10 Javascript
jQuery根据纬度经度查看地图处理程序
2013/05/08 Javascript
javascript使用输出语句实现网页特效代码
2015/08/06 Javascript
基于jQuery实现的双11天猫拆红包抽奖效果
2015/12/01 Javascript
JavaScript定义函数的三种实现方法
2017/09/23 Javascript
Vue2.0中集成UEditor富文本编辑器的方法
2018/03/03 Javascript
vue 指定组件缓存实例详解
2018/04/01 Javascript
Node.js利用console输出日志文件的方法示例
2018/04/27 Javascript
elementUI select组件默认选中效果实现的方法
2019/03/25 Javascript
python实现去除下载电影和电视剧文件名中的多余字符的方法
2014/09/23 Python
python多线程threading.Lock锁用法实例
2014/11/01 Python
使用Python导出Excel图表以及导出为图片的方法
2015/11/07 Python
python里使用正则的findall函数的实例详解
2017/10/19 Python
Python中整数的缓存机制讲解
2019/02/16 Python
基于tensorflow指定GPU运行及GPU资源分配的几种方式小结
2020/02/03 Python
python手机号前7位归属地爬虫代码实例
2020/03/31 Python
解决python脚本中error: unrecognized arguments: True错误
2020/04/20 Python
收藏!10个免费高清视频素材网站!【设计、视频剪辑必备】
2021/03/18 杂记
CSS3实现文字描边的2种方法(小结)
2020/02/14 HTML / CSS
Brookstone美国官网:独特新奇产品
2017/03/04 全球购物
Hunter Boots美国官方网站:赫特威灵顿雨靴
2018/06/16 全球购物
英国和国际包裹递送:ParcelCompare
2019/08/26 全球购物
怎么处理XML的中文问题
2015/03/26 面试题
寒假实习自荐信
2014/01/26 职场文书
化工见习报告范文
2014/10/31 职场文书
婚礼答谢礼品
2015/01/20 职场文书
食品安全责任书范本
2015/05/09 职场文书
母婴行业实体、电商模式全面解析
2019/08/01 职场文书
Spring mvc是如何实现与数据库的前后端的连接操作的?
2021/06/30 Java/Android