《深入浅出Nodejs》笔记——模块机制(1)
前言
这是我读《深入浅出Nodejs》的笔记,真是希望我的机械键盘快点到啊,累死我了。
CommonJS规范
主要分为模块引用、模块定义、模块标识三个部分。
模块引用
上下文提供require()方法来引入外部模块,示例代码如下:
//test.js
//引入一个模块到当前上下文中
var math = require('math');
math.add(1, 2);
模块定义
上下文提供了exports对象用于导入导出当前模块的方法或者变量,并且它是唯一的导出出口。模块中存在一个module对象,它代表模块自身,exports是module的属性。一个文件就是一个模块,将方法作为属性挂载在exports上就可以定义导出的方式:
//math.js
exports.add = function () {
var sum = 0, i = 0, args = arguments, l = args.length;
while(i < l) {
sum += args[i++];
}
return sum;
}
这样就可像test.js里那样在require()之后调用模块的属性或者方法了。
模块标识
模块标识就是传递给require()方法的参数,它必须是符合小驼峰命名的字符串,或者以"."、".."开头的相对路径或者绝对路径,可以没有文件后缀名".js".
Node的模块实现
在Node中引入模块,需要经历如下三个步骤:
- 路径分析
- 文件定位
- 编译执行
在Node中模块分为两类:一是Node提供的模块,称为核心模块;另一类是用户编写的模块,称为文件模块。
核心模块是Node源码在编译过程中编译进了二进制执行文件。在Node启动时这些模块就被加载进内存中,所以核心模块引入时省去了文件定位和编译执行两个步骤,并且在路径分析中优先判断,因此核心模块的加载速度是最快的。
文件模块则是在运行时动态加载,速度比核心模块慢。
优先从缓存加载
和浏览器会缓存静态js文件一样,Node也会对引入的模块进行缓存,不同的是浏览器缓存的是文件,Node缓存的是编译执行之后的对象。
require()对相同模块的二次加载一律采用缓存优先的方式,这是第一优先级的,核心模块缓存检查先于文件模块的缓存检查。
路径分析和文件定位
因为标识符有几种形式,对于不同的标识符,模块的查找和定位有不同程度上的差异。
1.模块标识符分析
模块标识符在Node中主要分为以下几类:
- 核心模块
- 相对路径文件模块
- 绝对路径文件模块
- 非路径形式的文件模块
核心模块
核心模块优先级仅次于缓存加载,因此无法加载一个和核心模块标识符相同的自定义模块。
路径形式的文件模块
以"."、".."开头和"/"开始的标识符,这里都被当作文件模块来处理。require()方法会将路径转为真实路径,并以真实路径作为索引,并将编译执行后的结果存放到缓存中。
自定义模块
自定义模块是指非核心模块,也不是路径形式的标识符。它是一种特殊的文件模块,可能是一个文件或者包的形式。
模块路径是Node在定位文件模块的具体文件时制定的查找策略,具体表现为一个路径组成的数组(module.paths)。这个路径由当前目录开始往上一直到根目录,Node会逐个尝试模块路径中的路径,直到找到目标文件未知,若到达根目录还是没有找到目标文件,则会抛出查找失败的异常。当前文件的目录越深,模块查找耗时越多。
2.文件定位
文件扩展名分析
调用require()方法时若参数没有文件扩展名,Node会按.js、.json、.node的顺寻补足扩展名,依次尝试。
在尝试过程中,需要调用fs模块阻塞式地判断文件是否存在。因为Node是单线程的,这是一个会引起性能问题的地方。如果是.node或者.json文件可以加上扩展名加快一点速度。另一个诀窍是:同步配合缓存。
目录分析和包
require()分析文件扩展名后,可能没有查到对应文件,而是找到了一个目录,此时Node会将目录当作一个包来处理。
首先, Node在挡墙目录下查找package.json,通过JSON.parse()解析出包秒速对象,从中取出main属性指定的文件名进行定位。若main属性指定文件名错误,或者没有pachage.json文件,Node会将index当作默认文件名。
若目录分析没有定位成功任何文件,则自定义模块进入下一个模块路径。
模块编译
每个模块文件模块都是一个对象,它的定义如下:
function Module(id, parent) {
this.id = id;
this.exports = {};
this.parent = parent;
if(parent && parent.children) {
parent.children.push(this);
}
this.filename = null;
this.loaded = false;
this.children = [];
}
对于不同扩展名,其载入方法也有所不同:
- .js 通过fs模块同步读取文件后编译执行。
- .node 这是C/C++编写的扩展文件,通过dlopen()方法加载最后编译生成的文件
- .json 同过fs模块同步读取文件后,用JSON.pares()解析返回结果
- 其他 当作.js
每一个编译成功的模块都会将其文件路径作为索引缓存在Module._cache对象上。
.json文件调用的方法如下:
//Native extension for .json
Module._extensions['.json'] = function(module, filename) {
var content = NativeModule.require('fs').readFileSync(filename, 'utf-8');
try {
module.exports = JSON.parse(stripBOM(content));
} catch(err) {
err.message = filename + ':' + err.message;
throw err;
}
}
Module._extensions会被赋值给require()的extensions属性,所以可以用:console.log(require.extensions);
输出系统中已有的扩展加载方式。
当然也可以自己增加一些特殊的加载:require.extensions['.txt'] = function(){//code};
。
但是从v0.10.6版本开始官方不鼓励通过这种方式自定义扩展名加载,而是期望先将其他语言或文件编译成JavaScript文件后再加载,这样的好处在于不讲烦琐的编译加载等过程引入Node的执行过程。
1.JavaScript模块的编译
在编译的过程中,Node对获取的javascript文件内容进行了头尾包装,将文件内容包装在一个function中:
(function (exports, require, module, _filename, _dirname) {
//js文件内容
});
包装之后的代码会通过vm原生模块的runInThisContext()方法执行(具有明确上下文,不污染全局),返回一个具体的function对象,最后传参执行,执行后返回model.exports.
核心模块
核心模块分为C/C++编写和JavaScript编写的两个部分,其中C/C++文件放在Node项目的src目录下,JavaScript文件放在lib目录下。
JavaScript核心模块的编译过程
1.转存为C/C++代码
Node采用了V8附带的js2c.py工具,将所有内置的JavaScript代码转换成C++里的数组,生成node_natives.h头文件:
namespace node {
const char node_native[] = { 47, 47, ..};
const char dgram_native[] = { 47, 47, ..};
const char console_native = { 47, 47, ..};
const char buffer_native = { 47, 47, ..};
const char querystring_native = { 47, 47, ..};
const char punycode_native = { 47, 47, ..};
...
struct _native {
const char* name;
const char* source;
size_t source_len;
}
static const struct _native natives[] = {
{ "node", node_native, sizeof(node_native)-1},
{ "dgram", dgram_native, sizeof(dgram_native)-1},
...
};
}
在这个过程中,JavaScript代码以字符串形式存储在node命名空间中,是不可直接执行的。在启动Node进程时,js代码直接加载到内存中。在加载的过程中,js核心模块经历标识符分析后直接定位到内存中。
2.编译js核心模块
lib目录下的模块文件也在引入过程中经历了头尾包装的过程,然后才执行和导出了exports对象。与文件模块的区别在于:获取源代码的方式(核心模块从内存加载)和缓存执行结果的位置。
js核心模块源文件通过process.binding('natives')取出,编译成功的模块缓存到NativeModule._cache上。代码如下:
function NativeModule() {
this.filename = id + '.js';
this.id = id;
this.exports = {};
this.loaded = fales;
}
NativeModule._source = process.binding('natives');
NativeModule._cache = {};
《深入浅出Nodejs》笔记——模块机制(1)的更多相关文章
- 浅谈NodeJs的模块机制
J历史 我们都知道,js在刚被创建的时候,只是为了在网页上写一些小脚本而已,比如网页特效,表单验证等等,创立者也许没觉悟到以后的js会发展到如此规模.这是web1.0时代. 在web 2.0时代,各种 ...
- 《深入浅出Nodejs》笔记——模块机制(2)
前言 书上还有很大一部分讲了C/C++模块的编译过程.核心模块编写和C/C++扩展模块的内容,不过我对C++一窍不通因此没有仔细看,如果以后需要再自习看吧. 包与NPM 第三方模块中,模块和模块之间是 ...
- 深入浅出node(2) 模块机制
这部分主要总结深入浅出Node.js的第二章 一)CommonJs 1.1CommonJs模块定义 二)Node的模块实现 2.1模块分类 2.2 路径分析和文件定位 2.2.1 路径分析 2.2.2 ...
- nodejs笔记--模块篇(三)
文件模块访问方式通过require('/文件名.后缀') require('./文件名.后缀') requrie('../文件名.后缀') 去访问,文件后缀可以省略:以"/&qu ...
- 【读书笔记】《深入浅出nodejs》第二章 模块机制
1.什么是模块? 指在程序设计中,为完成某一功能所需的一段程序或子程序:或指能由编译程序.装配程序等处理的独立程序单位:或指大型软件系统的一部分. ----<百度百科> 2.JavaScr ...
- 深入浅出Nodejs读书笔记
深入浅出Nodejs读书笔记 转:http://tw93.github.io/2015-03-01/shen-ru-qian-chu-nodejs-reading-mind-map.html cate ...
- Nodejs:Node.js模块机制小结
今天读了<深入浅出Nodejs>的第二章:模块机制.现在做一个简单的小结. 序:模块机制大致从这几个部分来讲:JS模块机制的由来.CommonJS AMD CMD.Node模块机制和包和n ...
- 通过Anuglar Material串串学客户端开发 - NodeJS模块机制之Module.Exports
module.exports 前文讲到在Angular Material的第二个编译文件docs/gulpfile.js中却看到了一个奇怪的东西module.exports那么module.expor ...
- 深入浅出Nodejs读书笔记(转)
Node简介 这一章简要介绍了Node,从中可以了解Node的发展历程及其带来的影响和价值. 为什么叫Node?起初,Ryan Dahl称他的项目为web.js,就是一个Web服务器,但是项目的发展超 ...
随机推荐
- Go语言的并发和并行
不知道你有没有注意到,这段代码如果我跑在两个goroutines里面的话: package main import ( "fmt" ) func loop(done chan bo ...
- 解决win10 CPU占用高的问题
[PConline 技巧]很多笔记本用户在升级到Win10后,都遇到了这样一个问题,那就是Win10的CPU占用明显高于Win7.这个问题对于台式机可能还算不了什么,顶多就是偶尔卡一下罢了.可由于笔记 ...
- 诱惑当前 你的孩子能hold住吗?
1.打好态度.动机.价值观基础 培养未成熟主体远大的志向,并不是向他们讲一些抽象的道理,而是根据他们的年龄特点,从形象的故事.童话开始,从身边的人物.事例出发,逐渐渗透一些人生的哲理.有一位自觉性非常 ...
- John's trip(POJ1041+欧拉回路+打印路径)
题目链接:http://poj.org/problem?id=1041 题目: 题意:给你n条街道,m个路口,每次输入以0 0结束,给你的u v t分别表示路口u和v由t这条街道连接,要输出从起点出发 ...
- [2009国家集训队]小Z的袜子(hose)(BZOJ2038+莫队入门题)
题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=2038 题目: 题意:中文题意,大家都懂. 思路:莫队入门题.不过由于要去概率,所以我们假 ...
- 阿里云服务器下安装配置 vsftpd —— 基于CentOS 6.3 【简洁版】
原文链接:http://www.tuicool.com/articles/nuiQBja 1.更新yum源 我是直接 yum update 更新的 2.安装vsftp 使用yum命令安装vsftpd ...
- 使用idea+Tomcat搭建servlet服务器
1.使用java 搭建一个简单的Servlet 服务器 https://blog.csdn.net/qq_35164169/article/details/76146655
- Spark-2.3.2【SparkStreaming+SparkSQL-实时仪表盘应用】
应用场景:实时仪表盘(即大屏),每个集团下有多个mall,每个mall下包含多家shop,需实时计算集团下各mall及其shop的实时销售分析(区域.业态.店铺TOP.总销售额等指标)并提供可视化展现 ...
- FindQQByProcess
看网上有许多通过进程寻找QQ号的例子,看了一下,里面涉及的知识点还是比较多,但网上的兼容性不太好,而且没有给出匹配字符的来源,所以自己动手写了一下,顺便给出一些我调试的结果. #include &qu ...
- 【DeepLearning学习笔记】Coursera课程《Neural Networks and Deep Learning》——Week1 Introduction to deep learning课堂笔记
Coursera课程<Neural Networks and Deep Learning> deeplearning.ai Week1 Introduction to deep learn ...