第一章: Node.js平台

1.Node.js开发理念

  1. 小核心
  2. 小模块
  3. 小接触面
  4. 简单实用

2.Node.js的工作原理

1.I/O是慢速操作

2.阻塞式I/O

3.非阻塞式的I/O

4.事件多路分离

5.reactor模式

6.Node.js的I/O引擎 - Libuv

7.Node.js的全套结构

3. Node.js平台之中的JavaScript

  • 放心使用最新版的JavaScript
  • 模块系统
  • 访问操作系统中的各项服务
  • 运行原生代码

第二章:模块系统

1. 为什么需要模块

颗粒化文件
复用
封装
便于管理依赖

2.JavaScript与Node.js的模块系统

3.模块系统及其模式

revealing module

通过过IIFE(immediately invoked function expression) 创建一个私有作用于,并且通过闭包的形式将export内容导出

4. CommonJS模块

自制的模块加载器

通过CommonJS系统来加载模块,本身就应该实现同步操作,以确保多个模块能够按照正确的依赖顺序得到引入

  1. const originalRequire = require
  2. const fs = originalRequire('fs')
  3. function loadModule (filename, module, require) {
  4. //同步通过readFileSync读取模块内容
  5. const wrappedSrc =
  6. `(function (module, exports, require) {
  7. ${fs.readFileSync(filename, 'utf8')}
  8. })(module, module.exports, require)`
  9. eval(wrappedSrc)
  10. }
  11. require = function require (moduleName) {
  12. console.log(`Require invoked for module: ${moduleName}`)
  13. const id = require.resolve(moduleName) // ①
  14. if (require.cache[id]) { // ②
  15. return require.cache[id].exports
  16. }
  17. // module metadata
  18. const module = { // ③
  19. exports: {},
  20. id
  21. }
  22. // Update the cache
  23. require.cache[id] = module // ④
  24. // load the module
  25. loadModule(id, module, require) // ⑤
  26. // return exported variables
  27. return module.exports // ⑥
  28. }
  29. require.cache = {}
  30. require.resolve = (moduleName) => {
  31. // reuse the original resolving algorithm for simplicity
  32. return originalRequire.resolve(moduleName)
  33. }
  34. // Load the entry point using our homemade 'require'
  35. require(process.argv[2])

定义模块

module.exports & exports

require函数是同步函数

与之相对应的module.exports 的赋值操作,也必须是同步的

模块解析算法

即对应require.resolve函数

模块缓存

通过缓存能够实现效率的提升
除此之外:

  1. 能够允许在模块之间形成循环依赖关系,
  2. 如果同一个包多次请求载入某个模块,那么得到的总是相同的实例。

    循环依赖问题

    由于require是动态的,执行顺序的不同,导致在循环依赖中可能得到不同的结果

5. 定义模块所用的模式

命名导出模式

exports.xxx = xxx;

函数导出模式

module.exports = () => {}
这种模式可以和命名导出模式结合

类导出模式

将原本命名导出的都放在类中

实例导出模式

利用require()函数的缓存机制,把构造器或工厂所制作的实例缓存起来,以保持其状态,并在多个模式之间共享该实例

通过monkey patching模式修改其他模块或全局作用域

模块可以修改全局范围内的其他模块或对象

6.ECMAScript模块

能支持循环依赖,而且能够异步加载模块;

但是ES模块是静态的,引入模块语句必须放在顶层,而且必须放在控制语句之外;受引用模块,只能使用常量字符串,不能是变量表达式。这使得ESM能够tree-shaking

在nodejs平台中使用ESM

命名导出和命名引入

命名导出支持自动引入与自动补全功能

namespace import
ESM要求用户必须把要引入的那个模块的文件扩展名写出来

命名导出对dead code elimination更加有利

默认导出 & 默认引入

混用命名导出与默认导出

类似react

模块标识符

  • relative specifier
  • absolute specifier file:///opt/nodejs/…
  • bare specifier
  • deep import specifier
  • browser supper http URL import

异步引入

提供应对特殊场景的async import / dynamic import

import() : Promise

详细解释模块的加载过程

载入模块所经历的各个阶段
  1. Construction 找出所有import深度递归加载每个模块的内容,每个模块只访问一次
  2. Instantiation 针对每个导出实体,内存中保留一个带名称引用,但不赋值, 并link各个模块之间的依赖关系
  3. Evaluatiion: 后序的深度优先执行,保证每个执行模块的依赖都已经初始化
    只读的live绑定
    解析循环依赖

修改其他模块

直接修改命名空间或者默认导出会出现不一致问题

可以使用module包中的syncBuiltinESMExports()

7.ESM与CommonJS之间的区别以及交互的使用技巧

ESM是在严格模式下运行的

ESM不支持CommonJS提供的某些引用

filename, dirname => import.meta (fileURLToPath, dirname)
require => createRequire(module) (倒入json文件
commonjs中this指向exports的引用

在其中一种模块系统里面使用另一种模块

import只能引入Common默认导出的东西

8. 小结

第三章: 回调与事件

Callback模式

continuation-passing style (CPS)

某个函数究竟是同步函数,还是异步函数?

  1. 有的函数无法判断是同步还是异步
  2. 在同一个函数里混用同步与异步所带来的危害
  3. 把API设计成同步的
  4. 通过延迟执行机制确保异步执行

    在Node.js里面定义回调的惯例

  5. 把表示回调的那个参数放在最后

  6. 如果回调结果里面有错误信息时这项信息应总排在首位
  7. 播报错误
  8. 未捕获的异常

Observer 模式

1. EventEmitter

2. 创建并使用EventEmitter

3. 播报错误信息

4. 让任何一个对象都能为监听器观察

5. EventEmitter & memory leak

6. 同步事件与异步事件

和callback模式中同步 / 异步 出现的问题一样
必须保证使用异步注册使得,注册比事件触发提早执行

7. EventEmitter 与 Callback 之间的对比

8. Callback with EventEmitter

把结果和流程都传递给用户

第四章:利用回调实现异步控制流模式

异步编程所遇到的困难

1. 创建简单的网页爬虫

2. callback hell

涉及回调的编程技巧与控制流模式

1. 编写回调逻辑时所应遵循的原则

2. 运用相关的规则编写回调

3. 顺序执行

4. 平行执行

平行执行所带来的race condition 问题

5. 限制任务数量的平行执行

  • 限制某个任务并发数量
  • 限制整个程序的并发上限

    async库

第五章: 利用Promise与async/await实现异步控制流模式

Promise

1. 什么是Promise?

2. Promises/A+ & thenable

3. Promise API

4. 创建Promise

5. 将回调方案改成Promise方案

6. 顺序执行与迭代

7. 平行执行

8. 限制任务数量的平行执行

第六章:用Stream编程

1. 理解stream在Node.js平台中的重要作用

缓冲模式与流模式

流模式在空间占用方便的优势

  • 用缓冲模式的API把文件压缩成GZIP格式
  • 用流模式的API把文件压缩成GZIP格式

    • 易于对接、拼合
    • 消耗的内存数量也是固定的

      流模式在处理时间方便的优势

      stream之间的组合

      2. 开始学习Stream

      流对象的体系结构

      async iterator + EventEmitter

  • Readable

  • Writable
  • Duplex
  • Transform

    Readable Stream

  • 通过流对象读取数据

    • 非流动模式 “readable” readable.read([size])
    • 流动模式 “data”
    • ?异步迭代器
  • 实现自己的Readable Stream

    • _read(size) + this.push
    • 简化版的定制方案
    • 用iterable做数据源构建Readable流
      • 数组
      • generator
      • iterator
      • async iterator

        Writable Stream

  • 向stream中写入数据

  • backpressure
    • drain
  • 实现Writable Stream
  • 简化版的定制方案

    Duplex Stream

    Transform Stream

  • 实现Transform流

  • 简化版的定制方案
  • 通过Transform流实现过滤与聚合

    PassThrough Stream

  • 观察数据的流动情况(数据不处理,原样传递

  • Late piping

    lazy Stream

    lazystream npm pkg

原理是通过passThrough Stream 代理,当首次_read的时候,再创建需要的流

用管道连接流对象

  • 处理管道之中的错误
  • 通过pipeline()方法可以更好地处理管道中的错误

3. 用Stream实现异步控制流模式

顺序执行

用回调控制上游的拉取

无序的平行执行

  • 用stream实现无序的平行执行
  • 实现URL状态检测程序

    无序且带有并发上限的平行执行模式

    控制来自上游的数量

    有序的平行执行

    通过parallel-transform npm pkg

在有限的缓冲区中保存获得的值

管道模式

Combine Streams

pumpify npm pkg

fork Stream

注意不要产生副作用

速度和最小的数据流出管道保持一致

merge Streams

手动关闭Writable Stream

mux/demux模式

packet switching问题

  • 构建一款远程的日志记录器
  • 对象流的mux / demux