读书笔记之《深入浅出 node》

本文详细介绍了 Node.js 的模块机制,包括CommonJS规范、模块的实现、核心模块等内容,强调了事件循环在异步I/O中的作用。此外,还探讨了异步编程的挑战与解决方案,如事件发布/订阅模式,以及内存控制和Buffer对象的使用。通过对Node.js的深入理解,有助于提升Web应用的性能和稳定性。
摘要由CSDN通过智能技术生成

第一章 node 简介

node 构建的高性能 Web 服务器的特点是:事件驱动、非阻塞 I/O;

node 擅长 I/O 密集型应用的场景,对于 CPU 密集型的应用,如何合理调度是关键;

node 是单线程、单进程的语言,单线程的缺点如下:

  1. 无法利用多核 CPU;
  2. 错误会引起整个应用退出,应用的健壮性值得考验;
  3. 大量计算占用 CPU 导致无法继续调用异步 I/O;

但是 node 也可以使用多线程,借助 child_process 模块,与之类似得是在浏览器端的 Web Workers;

第二章 模块机制

CommonJS 规范 - 里程碑

请与 CMD 规范区分开;

2.1 CommonJS 的模块规范

CommonJS 的模块导出和引入的机制使得用户完全不必考虑变量污染。

1. 模块引用

require 方法引入一个模块的 API 到当前的上下文中;

2. 模块定义

对应引用的功能,上下文提供了 exports 对象用于导出当前模块的方法或变量,并且它是唯一的导出的出口;

在模块中,还存在一个 module 对象,他代表模块自身,而 exports 是 module 的属性;

3. 模块标识

模块标识其实就是传递给 require() 方法的参数,必须符合小驼峰命名的字符串,或者以 ... 开头的相对路径或者是绝对路径。

意义在于:将类聚的方法和变量等限定在私有的作用域中,同时支持引入和导出功能以顺畅地链接上下游依赖;

2.2 模块的实现

在引入模块的过程中,需要经历如下 3 个步骤:

  1. 路径分析;
  2. 文件定位;
  3. 编译执行;

模块被分为两类:核心模块(node 提供的) 和 文件模块(用户编写的);

  • 核心模块在 node 源码编译时就被编译成了二进制,运行时直接加载于内存中,文件定位和编译执行被省略,且在路径分析中优先判断,因此加载速度是最快的;
  • 文件模块则是在运行时动态加载,需要完整的路径分析、文件定位和编译执行的过程,速度比核心模块慢;

node 对引入过的模块(编译和执行后的对象)都会进行缓存(Module._cache),以减少二次引入时的开销;

无论是核心模块还是文件模块,require() 方法对相同的模块都是缓存优先的,这是第一优先级,核心模块的缓存检查会先于文件模块;

在分析路径模块时,require() 方法会将路径转为真实路径,并以真实路径作为索引,将编译执行后的结果放到缓存中,以使二次加载时更快;

1. 路径分析

模块路径:node 在定位文件模块的具体文件时制定的查找策略,具体表现为一个路径组成的数组;

规则如下:

  • 当前文件目录下的 node_modules 目录;
  • 父目录下的 node_modules 目录;
  • 父目录的父目录下的 node_modules 目录;
  • 沿路径向上逐级递归,直到根路径下的 node_modules 目录;
2. 文件定位
  1. 文件扩展名分析,node 会按照 .js.json.node 的次序依次补足扩展名;
  2. 目录分析和包,如果文件扩展名分析之后,没有找到文件却得到了目录,会将目录当做处理,查找 package.json,基于 JSON.parse() 解析出包描述对象,从中取出 main 属性指定的文件名进行定位,若 main 属性指定文件名错误、没有 main 属性、没有 package.json 文件,则会将 index 作为默认文件名,依次查找 index.js/index.node/index.json 文件;
3. 模块编译
  1. .js:通过 fs 模块同步读取文件后编译执行,对 js 文件进行了头尾包装(这也是为什么在每个 js 文件中都可以使用 require, module, exports, __filename 和 __dirname 变量);
(function (exports, require, module, __filename, __dirname) {
   
  var math = require("math");
  exports.area = function (radius) {
   
    return Math.PI * radius * radius;
  };
});

由于在编译 js 文件时,node 对 js 文件进行了头尾包装,因此不要直接将方法或对象直接赋值于 exports,这是因为 exports 对象是通过形参的方式传入的,直接赋值会改变形参的引用,不会改变被当做参数的原 exports 对象(作用域外);

// 错误的赋值
exports = function () {
   
  // My Class
};
// 正确的赋值
module.exports = function () {
   
  // My Class
};
  1. .node:这是用 C/C++ 编写的扩展文件,用 dlopen() 方法加载最后的编译生成的文件;
  2. .json:通过 fs 模块同步读取文件后用 JSON.parse() 解析返回结果;

2.3 核心模块

node 的核心模块分为:C/C++ 编写的模块(在 src 目录下)和 JavaScript 编写的模块(在 lib 目录下);

由此,我们在编写 node 模块时也需要将 C/C++ 模块放到 src 文件夹下,将 JavaScript 模块放到 lib 目录下;

在编译所有 C/C++ 文件之前,编译程序需要将所有的 JavaScript 模块文件编译为 C/C++ 代码(借助 js2c.py 工具);

由纯 C/C++ 编写的部分统一称为内建模块,由于一般内建模块不会直接被用户调用,因此未将内建模块算作模块类别之一;

文件模块依赖核心模块,核心模块依赖内建模块;

JavaScript 核心模块主要有两类职责:

  1. C/C++ 内建模块的封装层和桥阶层,供文件模块的引用;
  2. 纯粹的功能模块,不需要与底层打交道;

C/C++ 内建模块会根据平台的不同(windows 和 *nix 平台),对于调用一些底层操作,需要借助 libuv 库,这也是 node 可以实现跨平台的诀窍;

第三章 异步 I/O

操作系统的内核对于 I/O 只有两种方式:阻塞与非阻塞;

libeio: 采用线程池与阻塞 I/O 模拟异步 I/O,这也是 node 对于异步 I/O 采用的方式;

3.3 Node 的异步 I/O

1. 事件循环

进程启动时,node 便会创建一个类似于 while(true) 的循环,每执行一次循环体的过程称为 Tick;

node 事件循环一次 Tick 的操作顺序图如下:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值