阅读本文之前,请投票支持这款 全新设计的脚手架 ,让 Java 再次伟大!
Iterator 迭代器模式
Node 中的迭代器是一种约定,这个约定包涵三个部分:
一. 如果一个对象拥有一个名为 Symbol.iterator 的方法,那这个对象就是一个可迭代对象 (iterable)
class MyIterable {[Symbol.iterator]() {return {};}
}
二. Symbol.iterator 方法应该返回一个被称为 iterator 的按照约定实现的对象
{// 每次调用时都返回一个表示迭代状态的对象next() {return {done: true // true:false 表示迭代完成value: "element" // 迭代出的元素}}
}
三. iterator 需要记录被迭代对象的迭代位置,同时又不能将迭代位置等内部属性暴露到外部
// 被迭代对象
function MyIterable() {this[Symbol.iterator] = function () {// 通过闭包来管理 iterator 的迭代位置let position = 0;// 返回一个 iteratorreturn {// 调用 iterator.next() 获取当前迭代对象的迭代状态的next() {position++;return {done: false, // true:false 表示迭代完成value: "element", // 迭代出的元素};},};};
}
任何对象无论是否是集合结构,只要符合上述约定的对象就是可迭代对象。
比如下面这个可遍历所有英文大写字母的对象就是一个标准的可迭代对象
function AlphabetIterator() {this[Symbol.iterator] = function () {const A_CHAR_CODE = 65;const Z_CHAR_CODE = 90;let currentCharCode = A_CHAR_CODE;return {next: () => {if (currentCharCode <= Z_CHAR_CODE) {const currentChar = String.fromCharCode(currentCharCode);currentCharCode++;return { value: currentChar, done: false };} else {return { done: true };}},};};
}const alphabet = new AlphabetIterator();
for (const iterator of alphabet) {console.log(iterator);
}// output: ABCDE...Z
除了 for...of
的方式以外,也可以使用更加直观的手工调用方式
const iterator = new AlphabetIterator()[Symbol.iterator]();
let iteratorStatus = null;
while (!(iteratorStatus = iterator.next()).done) {console.log(iteratorStatus);
}
Generator 生成器
生成器函数是一种特殊的函数,调用这种函数不会立即得到返回值,而是会返回一个 iterable 对象
function* fruitGenerator() {yield "peach";yield "watermelon";return "summer";
}for (const iterator of fruitGenerator()) {console.log(iterator);
}
// peach
// watermelon
使用 for of
关键字可以迭代这个生成器函数创建的 iterable 对象,紧跟 yield 关键字的值就是每次迭代的元素。
除了 for of
以外,还可以通过手工的方式迭代这个生成器对象,因为生成器函数并不是什么魔法,调用该函数返回的对象除了可迭代 (iterable) 以外,还是一个 iterator 对象
let fruitGeneratorObj = fruitGenerator();
console.log(fruitGeneratorObj.next());
console.log(fruitGeneratorObj.next());
console.log(fruitGeneratorObj.next());// output
{ value: 'peach', done: false }
{ value: 'watermelon', done: false }
// 手工调用 iterator 能够获取到 done:true 的结果
{ value: 'summer', done: true }
由于使用生成器函数要比构建一个满足约定的可迭代对象简单的多,所以生成器函数常被用来替代普通的迭代器
function AlphabetIterator() {this[Symbol.iterator] = function* () {const A_CHAR_CODE = 65;const Z_CHAR_CODE = 90;let currentCharCode = A_CHAR_CODE;while (currentCharCode <= Z_CHAR_CODE) {const currentChar = String.fromCharCode(currentCharCode);currentCharCode++;yield currentChar;}return;};
}let alphabetIterator = new AlphabetIterator();
for (const iterator of alphabetIterator) {console.log(iterator);
}
异步迭代器与流
迭代器和生成器除了同步以外还有异步的版本。如果一个 iterator 返回了一个 Promise 那这个迭代器就是异步的。
for await...of
语法迭代一个实现了 @@asyncIterator
方法的异步可迭代对象,使用的方法就像下面这样
const asyncIterator = iterable[Symbol.asyncIterator]();
let iterationResult = await asyncIterator.next();
while (!iterationResult.done) {console.log(iterationResult.value);iterationResult = await asyncIterator.next();
}
迭代器与流的结构很相似——都是一小块一小块的处理资源。所以很多时候他们都可以互换,并支持被 for await...of
语法迭代
async function main() {const stream = process.stdin.pipe(split());for await (const line of stream) {console.log(line);}
}
Middleware 中间件
中间件差不多相当于责任链设计模式(Chain of Responsibility),不过 Node 语境下的中间件一般是异步的处理函数,而注册中间件的过程就像把流拼接成一个管道,所以用「架构化的异步管道处理」来形容更加贴切一点
async function combine(req) {return "hello " + req;
}async function flip(req) {return req.split("").reverse().join("");
}function Manager() {this.middleWares = [];this.use = (middleWare) => {this.middleWares.push(middleWare);}this.execute = async (req) => {for (const f of this.middleWares) {req = await f(req);console.log(req);}}
}let manager = new Manager();
manager.use(combine);
manager.use(flip);
await manager.execute("chuck");
console.log("finish");
// output ==>
// hello chuck
// kcuhc olleh
// finish
命令设计模式
在 Node 中谈命令就像单例一样有点多余。因为 js 的闭包函数天生就是一个 command
// create command
function createTask(target, ...args) {return () => {// execute targettarget(...args);}
}const arg = "chuck";
const command = createTask((arg) => console.log(arg), arg);
command();
// output ==> chuck
结语
- 迭代器和可迭代对象代表的迭代器设计模式是一种约定。
- 生成器就是一个实现了
@@iterator
的迭代器。 - 迭代器和生成器既可以是同步也可以是异步。
- node 中的闭包函数天生就是命令式的。