内容简介:javasciprt 通过script标签引入代码的方式显得杂乱无章,语言自身毫无组织和约束能力。人们不得不用命名空间等方式人为地约束代码,以求达到安全和易用的目的。javascript有以下缺点:CommonJS就是来弥补这些缺陷的
- javascript先天就缺乏一项功能:模块
javasciprt 通过script标签引入代码的方式显得杂乱无章,语言自身毫无组织和约束能力。人们不得不用命名空间等方式人为地约束代码,以求达到安全和易用的目的。
- 为了让javascript能在服务端有市场,社区为javascript制定了相应的规范——CommonJS
CommonJS规范
CommonJs的出发点
javascript有以下缺点:
-
==没有模块系统==
-
==标准库较少==-比如没有文件系统、I/O流等标准的API
-
==没有标准接口==-几乎没有定义过web服务器或者数据库之类的标准统一接口
-
==缺乏包管理系统==-javascript没有自动加载和安装依赖的能力
CommonJS就是来弥补这些缺陷的
CommonJS大部分规范依旧是草案,但是为javascript开发大型应用程序指明了一条道路
- Node受到CommonJS的影响,CommonJS因Node表现优异而走入各个公司项目里,相互影响和促进
CommonJS的模块规范
分为模块引用、模块定义、模块标识
-
==模块引用==
var math = require('math');
es6已更新一套支持模块引用的语法:import。在调用require时,可以把它放在某个判断条件下,但import不行;在打包编译时,如果require里的文件模块不存在,即便逻辑上不会进入其所在的判断条件,依旧会报错。可以使用try{}catch(e){}来处理
- ==模块定义==
- 提供了exports对象用于导出当前模块的方法或变量
export 是es6的规范,与import一同用,不要弄混了
-
一个模块里还有module对象,代表模块自身;exports是module的属性
-
在Node中,一个文件就是一个模块,将方法挂载在exports对象上作为属性即可定义导出的方式
// math.js exports.add = function(a,b){ return a+b; } exports.sum = function(a,b){ return a+b; } 复制代码
// index.js var math = reqire('math'); exports.add = function(val){ return math.add(val,1); } 复制代码
- ==模块标识==
- 指的是传给require()方法的参数,必须是符合小驼峰命名的字符串,或者以.、..开头的相对路径,或者绝对路径
每个模块具有独立的空间,互不干扰,用户不必考虑变量污染
Node的模块实现
Node没有完全按照CommonJS规范实现,做了一定的取舍并加入自身需要的特性。
- Node引入模块需经历如下3个步骤
- ==路径分析==
- ==文件定位==
- ==编译执行==
- Node中模块分为两块:一类是Node提供的模块,称为==核心模块==;另一类是用户编写的模块,称为==文件模块==
- 核心模块在编译过程中,成为二进制执行文件;Node进程启动时,部分核心模块就直接加载进内存中
开发者引入这部分核心模块时,省略了文件定位、编译执行,且在路径分析中优先判断;故,加载速度最快
- 文件模块是在运行时动态加载,需要路径分析、文件定位、编译执行过程,速度比核心模块慢
先路径分析文件定位、最后再运行,所以前面require一个不存在的文件时,即便有判断条件,依然会报错
优先从缓存加载
- Node对引入过的模块都会进行缓存,以减少二次引入时的开销。==Node缓存的是编译和执行之后的对象==
所以,所有模块exports/export出的的东西,在内存中有且仅有一份,不受exports/export后的表达式/new之类的语句所影响
- require()方法/import语句对于相同模块的二次加载都一律采用缓存优先的方式,这是第一优先级的。核心模块的缓存检查优于文件模块的缓存检查
路径分析和文件定位
因标识符的不同形式,模块的查找和定位有不同程度的差异
- 模块标识符分析
-
标识符分为以下几类:
- ==核心模块==,如http、fs、path
- .或..开始的==相对路径文件模块==
- 以/开始的==绝对路径文件模块==
- ==非路径形式的文件模块==
核心模块
- 优先级仅次于缓存加载,加载速度最快
- 不能将自定义模块的标识符与核心模块标识符定义得一致,除非你换用路径的方式
路径形式的文件模块
- . .. /开始的标识符,均视为文件模块
- require将路径转为真实路径,以此做索引,编译执行后的结果放入缓存
- 加载速度比核心模块慢
自定义模块
-
特殊的文件模块,查找费时,加载速度最慢
-
“模块路径”——Node定位文件模块时定制的查找策略。模块路径是一个路径组成的数组:
[
当前文件目录下的xx目录,
父目录下的xx目录,
父目录下的父目录下的xx目录,
沿路径向上逐级递归,直到根目录下的xx目录
]
类似于js的原型链或作用域链的查找方式。也正因如此,一旦路径越深,查找耗时就越多,所以加载速度最慢
- 文件定位
有缓存的存在和前面的路径分析,文件定位相对比较简单。这里注意一些细节:
-
文件扩展名分析
- 有时标识符没有扩展名,CommonJS规范不允许不包含文件扩展名,但Node会按.js、.json、.node的顺序依次尝试
- 尝试时,会调用fs模块同步阻塞时判断文件是否存在。由于Node单线程,所以这里会引起性能问题——建议.node/.json文件加上扩展名
-
目录分析和包
-
有时没查找到文件,而是一个目录
-
Node会先找当前目录下的package.json,解析出main属性指定的文件名进行定位;如果文件名没有扩展名,则会进入扩展名分析的步骤
解析方式就是JSON.parse()
-
如果main提供的文件名有误,或者没有package.json,则查index(.js、.json、.json)
-
还没定位成功,则按照自定义模块的 模块路径 策略,去父目录查询;如果模块路径数组遍历完毕都没找到,则抛出查找失败的异常
-
模块编译
(这里指的都是文件模块,非核心模块) 当定位到具体文件后,Node会新建一个模块对象,将文件载入并编译。载入方法根据不同文件扩展名而区分
- .js文件:fs模块同步读取后编译执行
- .node文件:这是c/c++编写的扩展文件,通过dlopen()方法加载最后编译生成的文件
- .json文件:fs模块同步读取后用JSON.parse()解析返回结果
- 其余扩展名:当.js文件载入
编译成功的模块会缓存在Module._cache上,以文件路径作为索引
-
javascript模块的编译
- 编译过程中,Node对获取的javascript的文件内容进行了头尾包装,头部添加(function(==exports, require, module, __filename, __dirname==){\n,尾部添加\n})
__filename 完整的文件路径;__dirname 文件目录
-
包装后的代码vm原生模块的runInThisContext()执行,返回一个具体的function对象
-
最后,将当前模块对象的exports、require()、module、文件路径和目录作为参数传入给function对象
执行后exports返回给调用方,其上的任何方法与属性均可被外部调用,但是模块中的其余变量或属性不可。==以此达到模块间的作用域隔离==
这就是Node对CommonJS模块规范的实现
exports的误用 编写代码时,理论上只要这样写就行:
exports = function(){//My Class}; 复制代码
但是这样写是有问题的,我们来看看编译过程:
- 头尾包装:
(function(exports, require, module, __filename, __dirname){ exports = function(){//My Class}; }) 复制代码
看,你把形参改了。。。但是exports最后是要返回给调用方然后被外部调用的,==改形参根本不能真正改变exports对象的内容==
所以,要不老老实实地写:exports.add = ...;或者写module.exports = ...
-
c/c++模块的编译
- 事实上,.node模块不需要编译,因为它是c/c++模块之后编译生成。它只需要加载和执行
- 执行中exports对象与.node模块产生脸型,返回给调用者
- 优势:执行效率高;劣势:编写门槛高
-
JSON文件的编译
- fs模块同步读取JSON文件
- JSON.parse()方法得到对象
- 赋给exports
如果.json文件作为配置文件(如package.json),则不必调用fs模块去读取和解析,直接reqire()引入即可。同理它也是享受缓存的便利,二次引入时没有性能影响
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 深入探究ES6之模块系统
- 针对银行木马BokBot核心模块的深入分析
- nodejs中的子进程,深入解析child_process模块和cluster模块
- 深入理解Webpack核心模块Tapable钩子[同步版]
- 深入浅出node.js总结-模块机制(2)
- 针对TrickBot银行木马新模块pwgrab的深入分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Art of Computer Programming, Volume 4, Fascicle 3
Donald E. Knuth / Addison-Wesley Professional / 2005-08-05 / USD 19.99
Finally, after a wait of more than thirty-five years, the first part of Volume 4 is at last ready for publication. Check out the boxed set that brings together Volumes 1 - 4A in one elegant case, and ......一起来看看 《The Art of Computer Programming, Volume 4, Fascicle 3》 这本书的介绍吧!