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 相关文章推荐
javascript 面向对象编程 聊聊对象的事
Sep 17 Javascript
Firebug入门指南(Firefox浏览器)
Aug 21 Javascript
jQuery控制iFrame(实例代码)
Nov 19 Javascript
node.js中的fs.truncate方法使用说明
Dec 15 Javascript
jQuery表单验证功能实例
Aug 28 Javascript
Bootstrap学习笔记之js组件(4)
Jun 12 Javascript
AngularJS实现动态编译添加到dom中的方法
Nov 04 Javascript
JavaScript 中的 this 简单规则
Sep 19 Javascript
Vue单页及多页应用全局配置404页面实践记录
May 22 Javascript
vue v-model实现自定义样式多选与单选功能
Jul 05 Javascript
element-ui table行点击获取行索引(index)并利用索引更换行顺序
Feb 27 Javascript
elementui的el-popover修改样式不生效的解决
Jun 30 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
Ha0k 0.3 PHP 网页木马修改版
2009/10/11 PHP
PHP中file_exists()判断中文文件名无效的解决方法
2014/11/12 PHP
php中时间函数date及常用的时间计算
2017/05/12 PHP
javascript数组的扩展实现代码集合
2008/06/01 Javascript
JQUERY 实现窗口滚动搜索框停靠效果(类似滚动停靠)
2013/03/27 Javascript
JavaScript验证图片类型(扩展名)的函数分享
2014/05/05 Javascript
javascript面向对象之共享成员属性与方法及prototype关键字用法
2015/01/13 Javascript
深入理解JavaScript系列(35):设计模式之迭代器模式详解
2015/03/03 Javascript
JavaScript监听文本框回车事件并过滤文本框空格的方法
2015/04/16 Javascript
基于JavaScript代码实现pc与手机之间的跳转
2015/12/23 Javascript
100多个基础常用JS函数和语法集合大全
2017/02/16 Javascript
vue2.0的contextmenu右键弹出菜单的实例代码
2017/07/24 Javascript
vue实现移动端图片裁剪上传功能
2020/08/18 Javascript
jquery操作select常见方法大全【7种情况】
2019/05/28 jQuery
JS浮点数运算结果不精确的Bug解决
2019/08/01 Javascript
Vue-cli3.X使用px2 rem遇到的问题及解决方法
2019/08/08 Javascript
微信公众号生成新浪短网址的实现(快速生成)
2019/08/18 Javascript
Openlayers实现点闪烁扩散效果
2020/09/24 Javascript
在Python操作时间和日期之asctime()方法的使用
2015/05/22 Python
python实现简单的单变量线性回归方法
2018/11/08 Python
python解释器pycharm安装及环境变量配置教程图文详解
2020/02/26 Python
Elasticsearch py客户端库安装及使用方法解析
2020/09/14 Python
5分钟让你掌握css3阴影、倒影、渐变小技巧(小编推荐)
2016/08/15 HTML / CSS
捷克建筑材料网上商店:DEK.cz
2021/03/06 全球购物
Java如何调用外部Exe程序
2015/07/04 面试题
医药工作者的求职信范文
2013/09/21 职场文书
中西医结合临床医学专业大学生自荐信
2013/09/28 职场文书
《池塘边的叫声》教学反思
2014/04/12 职场文书
体育节口号
2014/06/19 职场文书
整顿机关作风心得体会
2014/09/10 职场文书
学校师德师风整改措施
2014/10/27 职场文书
2014年护士长工作总结
2014/11/11 职场文书
人力资源部岗位职责
2015/02/11 职场文书
大学生暑假实习总结
2015/07/13 职场文书
仅仅使用 HTML/CSS 实现各类进度条的方式汇总
2021/11/11 HTML / CSS
python游戏开发Pygame框架
2022/04/22 Python