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 相关文章推荐
Cookie 小记
Apr 01 Javascript
基于jquery tab切换(防止页面刷新)
May 23 Javascript
jQuery学习笔记(4)--Jquery中获取table中某列值的具体思路
Apr 10 Javascript
JQuery事件e参数的方法preventDefault()取消默认行为
Sep 26 Javascript
用js代码改变单选框选中状态的简单实例
Dec 18 Javascript
在javascript中实现函数数组的方法
Dec 25 Javascript
捕获和分析JavaScript Error的方法
Mar 25 Javascript
vue中appear的用法
Aug 17 Javascript
纯js实现图片匀速淡入淡出效果
Aug 22 Javascript
微信小程序性能优化之checkSession的使用
Mar 06 Javascript
angular 表单验证器验证的同时限制输入的实现
Apr 11 Javascript
Vue表单之v-model绑定下拉列表功能
May 14 Javascript
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
php过滤HTML标签、属性等正则表达式汇总
2014/09/22 PHP
Yii使用ajax验证显示错误messagebox的解决方法
2014/12/03 PHP
100行PHP代码实现socks5代理服务器
2016/04/28 PHP
基于PHP的加载类操作以及其他两种魔术方法的应用实例
2017/08/28 PHP
解决laravel(5.5)访问public报错的问题
2019/10/12 PHP
PHP实现笛卡尔积算法的实例讲解
2019/12/22 PHP
快速保存网页中所有图片的方法
2006/06/23 Javascript
使用隐藏的new来创建对象
2011/03/29 Javascript
JS getAttribute和setAttribute(取得和设置属性)的使用介绍
2013/07/10 Javascript
JS简单计算器实例
2015/01/20 Javascript
AngularJS基础 ng-non-bindable 指令详细介绍
2016/08/02 Javascript
js实现无缝循环滚动
2020/06/23 Javascript
原生JS实现简单的倒计时功能示例
2018/08/30 Javascript
修改vue+webpack run build的路径方法
2018/09/01 Javascript
vue 项目中当访问路由不存在的时候默认访问404页面操作
2020/08/31 Javascript
Python笔记(叁)继续学习
2012/10/24 Python
Python进程通信之匿名管道实例讲解
2015/04/11 Python
Python实现图片滑动式验证识别方法
2017/11/09 Python
python读取文本中数据并转化为DataFrame的实例
2018/04/10 Python
Python实现简单http服务器
2018/04/12 Python
详解基于django实现的webssh简单例子
2018/07/17 Python
python文件读写代码实例
2019/10/21 Python
NumPy中的维度Axis详解
2019/11/26 Python
使用python模拟高斯分布例子
2019/12/09 Python
Python命名空间namespace及作用域原理解析
2020/06/05 Python
Python字典取键、值对的方法步骤
2020/09/30 Python
在HTML5 canvas里用卷积核进行图像处理的方法
2018/05/02 HTML / CSS
中国跨境电商:Tomtop
2017/03/16 全球购物
标签和贴纸印刷:Lightning Labels
2018/03/22 全球购物
澳大利亚女士时装在线:Rockmans
2018/09/26 全球购物
高级Java程序员面试题
2016/06/23 面试题
幼儿园中班开学寄语
2014/04/03 职场文书
统计学教授推荐信
2014/09/18 职场文书
小学教师节活动总结
2015/03/20 职场文书
药店收银员岗位职责
2015/04/07 职场文书
SQL基础查询和LINQ集成化查询
2022/01/18 MySQL