В NodeJS есть сложные вопросы на собеседовании, на сколько вы сможете правильно ответить?

Node.js

1. Механизм узлового модуля

1.1 Пожалуйста, расскажите, что такое модуль в узле

В Node каждый файловый модуль представляет собой объект, который определяется следующим образом:

function Module(id, parent) {
  this.id = id;
  this.exports = {};
  this.parent = parent;
  this.filename = null;
  this.loaded = false;
  this.children = [];
}

module.exports = Module;

var module = new Module(filename, parent);

Все модули являются экземплярами Module. Как видите, текущий модуль (module.js) также является экземпляром модуля.

1.2 Пожалуйста, представьте механизм загрузки модуля require

Этот вопрос в основном говорит вам, насколько хорошо интервьюируемый понимает механизм модуля Node. В основном в интервью упоминается

  • 1. Сначала рассчитайте путь к модулю
  • 2. Если модуль в кэше, вынуть кэш
  • 3. Загрузите модуль
  • 4. Атрибут экспорта модуля вывода может быть
// require 其实内部调用 Module._load 方法
Module._load = function(request, parent, isMain) {
  //  计算绝对路径
  var filename = Module._resolveFilename(request, parent);

  //  第一步:如果有缓存,取出缓存
  var cachedModule = Module._cache[filename];
  if (cachedModule) {
    return cachedModule.exports;

  // 第二步:是否为内置模块
  if (NativeModule.exists(filename)) {
    return NativeModule.require(filename);
  }
  
  /********************************这里注意了**************************/
  // 第三步:生成模块实例,存入缓存
  // 这里的Module就是我们上面的1.1定义的Module
  var module = new Module(filename, parent);
  Module._cache[filename] = module;

  /********************************这里注意了**************************/
  // 第四步:加载模块
  // 下面的module.load实际上是Module原型上有一个方法叫Module.prototype.load
  try {
    module.load(filename);
    hadException = false;
  } finally {
    if (hadException) {
      delete Module._cache[filename];
    }
  }

  // 第五步:输出模块的exports属性
  return module.exports;
};

Продолжайте задавать предыдущий вопрос

1.3 Почему при загрузке модуля каждый модуль имеет атрибуты __dirname и __filename?Когда мы создаем новый модуль, мы видим, что эти два атрибута недоступны в части 1.1, так откуда же берутся эти два атрибута?

// 上面(1.2部分)的第四步module.load(filename)
// 这一步,module模块相当于被包装了,包装形式如下
// 加载js模块,相当于下面的代码(加载node模块和json模块逻辑不一样)
(function (exports, require, module, __filename, __dirname) {
  // 模块源码
  // 假如模块代码如下
  var math = require('math');
  exports.area = function(radius){
      return Math.PI * radius * radius
  }
});

То есть каждый модуль будет передавать параметры __filename, __dirname Эти два параметра не находятся в самом модуле, а передаются из внешнего мира.

1.4 Мы знаем, что узел может экспортировать модули двумя способами.Во-первых, в чем разница между export.xxx=xxx и Module.exports={}?

  • экспорт на самом деле module.exports
  • На самом деле, код проблемы 1.3 уже объяснил проблему, а затем я цитирую объяснение Ляо Сюэфэна, надеясь объяснить его более понятно.
module.exports vs exports
很多时候,你会看到,在Node环境中,有两种方法可以在一个模块中输出变量:

方法一:对module.exports赋值:

// hello.js

function hello() {
    console.log('Hello, world!');
}

function greet(name) {
    console.log('Hello, ' + name + '!');
}

module.exports = {
    hello: hello,
    greet: greet
};
方法二:直接使用exports:

// hello.js

function hello() {
    console.log('Hello, world!');
}

function greet(name) {
    console.log('Hello, ' + name + '!');
}

function hello() {
    console.log('Hello, world!');
}

exports.hello = hello;
exports.greet = greet;
但是你不可以直接对exports赋值:

// 代码可以执行,但是模块并没有输出任何变量:
exports = {
    hello: hello,
    greet: greet
};
如果你对上面的写法感到十分困惑,不要着急,我们来分析Node的加载机制:

首先,Node会把整个待加载的hello.js文件放入一个包装函数load中执行。在执行这个load()函数前,Node准备好了module变量:

var module = {
    id: 'hello',
    exports: {}
};
load()函数最终返回module.exports:

var load = function (exports, module) {
    // hello.js的文件内容
    ...
    // load函数返回:
    return module.exports;
};

var exportes = load(module.exports, module);
也就是说,默认情况下,Node准备的exports变量和module.exports变量实际上是同一个变量,并且初始化为空对象{},于是,我们可以写:

exports.foo = function () { return 'foo'; };
exports.bar = function () { return 'bar'; };
也可以写:

module.exports.foo = function () { return 'foo'; };
module.exports.bar = function () { return 'bar'; };
换句话说,Node默认给你准备了一个空对象{},这样你可以直接往里面加东西。

但是,如果我们要输出的是一个函数或数组,那么,只能给module.exports赋值:

module.exports = function () { return 'foo'; };
给exports赋值是无效的,因为赋值后,module.exports仍然是空对象{}。

结论
如果要输出一个键值对象{},可以利用exports这个已存在的空对象{},并继续在上面添加新的键值;

如果要输出一个函数或数组,必须直接对module.exports对象赋值。

所以我们可以得出结论:直接对module.exports赋值,可以应对任何情况:

module.exports = {
    foo: function () { return 'foo'; }
};
或者:

module.exports = function () { return 'foo'; };
最终,我们强烈建议使用module.exports = xxx的方式来输出模块变量,这样,你只需要记忆一种方法。

2. Асинхронный ввод-вывод узла

Большинство идей для ответов в этой главе заимствованы из книги Пу Лингды «NodeJS на простом языке».

2.1 Пожалуйста, представьте процесс цикла событий узла

  • Когда процесс запустится, Node создаст цикл, похожий на while(true), и мы становимся Tick каждый раз, когда выполняется процесс тела цикла.

  • Процесс каждого тика заключается в том, чтобы увидеть, есть ли ожидающее событие. Если есть, удалите событие и связанную с ним функцию обратного вызова. Затем войдите в следующий цикл, если обработки событий больше нет, выйдите из процесса.

2.2 В процессе каждого галочка, как судить, есть ли событие, которое необходимо обрабатывать?

  • В каждом цикле событий есть один или несколько наблюдателей, и процесс определения того, есть ли событие для обработки, заключается в том, чтобы спросить этих наблюдателей, есть ли событие для обработки.

  • В Node события в основном поступают из сетевых запросов, файлового ввода-вывода и т. д. Наблюдатели, соответствующие этим событиям, включают наблюдателей файлового ввода-вывода и наблюдателей сетевого ввода-вывода.

  • Цикл событий представляет собой типичную модель производителя/потребителя. Асинхронный ввод-вывод, сетевые запросы и т. д. являются производителями событий, которые непрерывно снабжают Node различными типами событий, которые доставляются соответствующим наблюдателям, а цикл обработки событий принимает события от наблюдателей и обрабатывает их.

  • Под windows этот цикл создается на основе IOCP, а под *nix он создается на основе многопоточности

2.3 Пожалуйста, опишите весь процесс асинхронного ввода-вывода

Механизм сбора мусора 3, V8's

3.1 Как просмотреть использование памяти V8

Используя process.memoryUsage(), результат выглядит следующим образом

{
  rss: 4935680,
  heapTotal: 1826816,
  heapUsed: 650472,
  external: 49879
}

heapTotalа такжеheapUsedПредставляет использование памяти V8.externalПредставляет использование памяти объектами C++, управляемыми V8, привязанными к Javascript. rss, размер резидентного множества, показывает, сколько физической памяти выделено процессу (доля от общей выделенной памяти).Эта физическая память содержит кучу, стек и сегменты кода.

3.2 Каков предел памяти V8 и почему V8 разработан таким образом

64-битная система занимает 1,4 ГБ, а 32-битная система — 0,7 ГБ. Поскольку стек восстановления мусора объемом 1,5 ГБ, V8 занимает более 50 миллисекунд, а сборка мусора выполняется без увеличения или даже более 1 секунды. Это инцидент, который привел к приостановке потока JavaScript при сборке мусора. При таких затратах производительность и влияние приложения будут снижаться.

3.3 Пожалуйста, кратко расскажите об алгоритме генерации и утилизации памяти в V8

В V8 память в основном делится на два поколения: новое поколение и старое поколение. Объекты молодого поколения имеют более короткое время выживания, а объекты старого поколения имеют более длительное время выживания или объекты, которые находятся в памяти.

3.3.1 Новое поколение

Объекты нового поколения в основном представляют собой мусор, собранный с помощью алгоритма Scavenge. Это алгоритм сборки мусора, реализованный посредством репликации. Он делит память кучи на две части, и каждая часть пространства становится полупространством. Из двух полупространств только одно используется, а другое не используется. Используемое полупространство называется Из пространства, а пространство в неактивном состоянии называется В пространство.

  • Когда начнется сборка мусора, будут проверены уцелевшие объекты в пространстве From, эти уцелевшие объекты будут скопированы в пространство To, а пространство, занятое невыжившими объектами, будет освобождено. После завершения копирования роли пространства From и пространства To меняются местами.

  • Он должен больше подходить для этого алгоритма, потому что жизненный цикл объектов в новом поколении относительно короткий.

  • Когда объект выживает в нескольких копиях, он будет считаться долгоживущим объектом. Объекты с более длительным временем жизни в этом молодом поколении затем перемещаются в старое поколение.

3.3.2 Старое поколение

Старое поколение в основном использует алгоритм сборки мусора по меткам. В отличие от Scavenge, который копирует живые объекты, алгоритм пометки-очистки проходит по всем объектам в куче на этапе пометки и помечает живые объекты, удаляя только мертвые. Живые объекты занимают лишь небольшую часть в молодом поколении, а мертвые объекты занимают лишь небольшую часть в старом поколении, поэтому используется алгоритм маркировки-развертки.

3.3.3 Проблемы с четко обозначенными алгоритмами

Основная проблема заключается в том, что пространство памяти будет появляться в прерывистом состоянии после каждой очистки и повторного использования меток.

  • Такого рода фрагментация памяти вызовет проблемы при последующем выделении памяти.Вполне вероятно, что нужно выделить большой объект.В это время все фрагментированное пространство не может завершить это выделение, и сборка мусора будет запущена заранее, а это коллекция не нужна.
  • Для решения проблемы фрагментации была предложена сортировка разметки. То есть после того, как объект помечен как мертвый, в процессе сортировки живой объект перемещается в один конец, а после завершения перемещения происходит непосредственно очистка памяти за границей.

3.3.4 Что может привести к тому, что V8 не сможет немедленно освободить память

Замыкания и глобальные переменные

3.3.5 Пожалуйста, расскажите о том, что такое утечки памяти, причины распространенных утечек памяти и методы устранения неполадок

что такое утечка памяти

  • Утечка памяти относится к ситуации, в которой программа не может освободить память, которая больше не используется, из-за небрежности или ошибки.
  • Если место утечки памяти критично, оно может занимать все больше и больше бесполезной памяти по мере выполнения обработки, и это увеличение бесполезной памяти приведет к тому, что сервер будет реагировать медленнее.
  • В тяжелых случаях память достигает определенного предела (может быть, верхнего предела процесса, такого как верхний предел v8; это также может быть верхний предел памяти, которую может предоставить система), что приводит к тому, что приложение крушение. Распространенные причины утечек памяти Несколько случаев утечек памяти:

1. Глобальные переменные

a = 10;  
//未声明对象。  
global.b = 11;  
//全局变量引用 
这种比较简单的原因,全局变量直接挂在 root 对象上,不会被清除掉。

2. Закрытие

function out() {  
    const bigData = new Buffer(100);  
    inner = function () {  
        
    }  
} 

Замыкания относятся к переменным в родительской функции, что может привести к утечке памяти, если замыкание не будет освобождено. В приведенном выше примере inner напрямую привязан к корню, поэтому большие данные, генерируемые каждый раз при выполнении функции out, не будут выпущены, что приведет к утечке памяти.

Следует отметить, что приведенный здесь пример — это просто привязка ссылки к глобальному объекту, а фактическая бизнес-ситуация может быть вызвана привязкой к объекту, который можно отследить от корня.

3. Мониторинг событий

Утечки памяти также могут возникать в прослушивателях событий Node.js. Например, многократное прослушивание одного и того же события и забвение удаления (removeListener) приведет к утечке памяти. Эта ситуация может легко возникнуть при добавлении событий к мультиплексируемым объектам, поэтому прослушиватели дублирования событий могут получить следующее предупреждение:

emitter.setMaxListeners() to increase limit 

Например, если значение параметра keepAlive агента в Node.js равно true, это может привести к утечке памяти. Когда Agent keepAlive имеет значение true, используемые ранее сокеты будут использоваться повторно.Если вы добавите прослушиватели событий в сокеты и забудете их очистить, из-за мультиплексирования сокетов события будут повторно отслеживаться и возникнут утечки памяти.

В принципе, это то же самое, что забыть об очистке при добавлении прослушивателей событий. При использовании http-модуля Node.js его повторное использование без поддержки активности не представляет проблемы, и после повторного использования могут возникнуть утечки памяти. Поэтому нужно понимать жизненный цикл объекта, добавляющего прослушиватель событий, и самостоятельно уделить внимание его удалению.

Метод устранения неполадок

Чтобы найти утечку памяти, обычно есть две ситуации:

  • Для утечек памяти, которые можно воспроизвести, пока они используются нормально, это очень простой случай, который можно проверить путем моделирования в тестовой среде.

  • Случайные утечки памяти обычно связаны со специальными входными данными. Стабильное воспроизведение этих входных данных — трудоемкий процесс. Если этот конкретный ввод не может быть обнаружен через журнал кода, рекомендуется распечатать снимок памяти в производственной среде.

  • Следует отметить, это пустая трата памяти операций печати моментальных снимков процессора, онлайн-бизнес может быть затронут. Инструмент моментальных снимков рекомендовал heapdump для сохранения моментальных снимков памяти, используйте devtool для просмотра моментального снимка памяти.

  • При использовании HeapDump, чтобы сохранить снимки памяти, будут только объекты в среде Node.js только будут только не будете нарушены (если используется узел-инспектор, в моментальном уменьшении будет переменная переменная вмешательство).

  • PS: На некоторых версиях Node.js установка heapdump может быть неправильной, для установки рекомендуется использовать npm install heapdump -target=Node.js.

4. Буферный модуль

4.1 Будет ли вновь созданный буфер занимать память, выделенную V8?

Нет, Buffer принадлежит памяти вне кучи, а не выделенной V8.

4.2 Разница между Buffer.alloc и Buffer.allocUnsafe

Базовая память для экземпляров Buffer, созданных Buffer.allocUnsafe, не инициализирована. Содержимое только что созданного буфера неизвестно и может содержать конфиденциальные данные. Используйте Buffer.alloc() для создания экземпляра Buffer с нулевой инициализацией.

4.3 Механизм выделения буферной памяти

Чтобы эффективно использовать запрошенную память, Node использует механизм распределения плит. Slab — это механизм управления динамической памятью. Node использует 8 КБ в качестве границы, чтобы различать, является ли буфер большим объектом или маленьким объектом.Если он меньше 8 КБ, это маленький буфер, а если он больше 8 КБ, это большой буфер.

Например, если в первый раз выделяется буфер размером 1024 байта, Buffer.alloc(1024), то в этом выделении будет использоваться плита, а затем, если вы продолжите использовать Buffer.alloc(1024), места не останется. для блока, использованного в прошлый раз.После исчерпания, поскольку общее количество составляет 8 КБ, 1024 + 1024 = 2048 байт, 8 КБ нет, поэтому продолжайте использовать этот блок для выделения места для буфера.

Если он превышает 8 КБ, то напрямую используйте SlowBuffer нижележащего подземного дворца C++, чтобы предоставить место для объекта Buffer.

4.4 Проблема искажения буфера

Например, содержимое файла test.md выглядит следующим образом:

床前明月光,疑是地上霜,举头望明月,低头思故乡

Когда мы читаем это так, будут искаженные символы:

var rs = require('fs').createReadStream('test.md', {highWaterMark: 11});
// 床前明???光,疑???地上霜,举头???明月,???头思故乡

В общем, вам нужно только установить rs.setEncoding('utf8') чтобы решить проблему искаженных символов

5. веб-сокет

5.1 Каковы преимущества webSocket по сравнению с традиционным http?

  • Клиенту и серверу требуется только одно соединение TCP, используя меньше соединений, чем длинный опрос http.
  • Сервер webSocket может передавать данные клиенту
  • Более легкие заголовки протокола, уменьшающие объем передаваемых данных

5.2 Что происходит при обновлении протокола webSocket, можете ли вы кратко описать это?

Во-первых, соединение WebSocket должно быть инициировано браузером, поскольку протокол запроса представляет собой стандартный HTTP-запрос следующего формата:

GET ws://localhost:3000/ws/chat HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Origin: http://localhost:3000
Sec-WebSocket-Key: client-random-string
Sec-WebSocket-Version: 13

Этот запрос отличается от обычного HTTP-запроса несколькими способами:

  • Адрес GET-запроса — это не /path/, а адрес, начинающийся с ws://;
  • Заголовки запроса Upgrade: websocket и Connection: Upgrade указывают, что это соединение будет преобразовано в соединение WebSocket;
  • Sec-WebSocket-Key используется для идентификации этого соединения, а не для шифрования данных;
  • Sec-WebSocket-Version указывает версию протокола WebSocket.

Впоследствии, если сервер принимает запрос, он возвращает следующие ответы:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: server-random-string

Код ответа 101 указывает, что протокол HTTP этого подключения будет изменен, а измененный протокол — это протокол WebSocket, указанный параметром Upgrade: websocket.

6. https

6.1 Какие порты используются https для связи и для чего эти порты?

  • Порт 443 используется для проверки подлинности сервера и клиента, например, для проверки действительности сертификата.
  • Порт 80 используется для передачи данных (в случае действительной проверки личности используется для передачи данных)

6.2 процесс аутентификации будет включать в себя ключ, симметричное шифрование, асимметричное шифрование, абстрактные концепции, пожалуйста, объясните

  • Ключ: ключевой параметр, который в открытом тексте преобразуется в зашифрованный текст или зашифрованный текст преобразуется в введенные параметры алгоритма открытого текста. Симметричный ключ в ключ асимметричный ключ, используются в симметричном шифровании и асимметричном шифровании.

  • Симметричное шифрование. Симметричное шифрование также известно как шифрование с закрытым ключом, то есть отправитель и получатель информации используют один и тот же ключ для шифрования и расшифровки данных. Симметричное шифрование характеризуется раскрытием алгоритма, быстрым шифрованием и дешифрованием и подходит для шифрования больших объемов данных.Распространенные алгоритмы симметричного шифрования включают DES, 3DES, TDEA, Blowfish, RC5 и IDEA.

  • Асимметричное шифрование. Асимметричное шифрование также называется шифрованием с открытым ключом. Асимметричное шифрование более безопасно, чем симметричное шифрование. Симметрично зашифрованная связь обе стороны используют один и тот же ключ, если ключ одной стороны просочится, то вся связь будет взломана. Асимметричное шифрование использует пару ключей, открытый ключ и закрытый ключ, попарно. Закрытый ключ хранится сам по себе и не может быть передан во внешний мир. Открытый ключ относится к открытому ключу, который может быть получен кем угодно. Зашифруйте с помощью открытого или закрытого ключа и расшифруйте другим.

  • Резюме: итоговый алгоритм также известен как алгоритм хеширования/хеширования. Он преобразует данные любой длины в строку данных фиксированной длины (обычно представленную шестнадцатеричной строкой) с помощью функции. Алгоритм необратим.

6.3 Почему агентства CA должны подписывать сертификаты?

Если вы не подпишете, возникнет риск атаки «человек посередине».После подписания информация в сертификате, такая как открытый ключ, информация о сервере и информация о предприятии, не будет изменена, и можно проверить «легальность» клиента и сервера.

6.4 https-аутентификация — это процесс аутентификации TSL/SSL.

Краткая иллюстрация выглядит следующим образом

7. Технологическая коммуникация

7.1 Пожалуйста, кратко опишите многопроцессорную архитектуру узла

Столкнувшись с недостаточным использованием многоядерных процессоров одним потоком узла, Node предоставляет модуль child_process для реализации репликации процессов.Многопроцессорная архитектура Node представляет собой режим ведущий-ведомый, как показано ниже:

var fork = require('child_process').fork;
var cpus = require('os').cpus();
for(var i = 0; i < cpus.length; i++){
    fork('./worker.js');
}

В linux просматриваем процесс по ps aux | grep worker.js

Это знаменитый режим «мастер-раб», Master-Worker.

7.2 Какие есть способы создания дочерних процессов и кратко расскажем об их отличиях

Методы создания дочерних процессов примерно следующие:

  • spawn(): запустить дочерний процесс для выполнения команды
  • exec(): запускает дочерний процесс для выполнения команд. В отличие от spawn(), его интерфейс отличается. У него есть функция обратного вызова, чтобы узнать статус дочернего процесса.
  • execFlie(): запустить дочерний процесс для выполнения исполняемого файла
  • fork(): аналогично spawn(), разница в том, что для создания дочерних процессов Node необходимо выполнять файлы js.
  • spawn() и exec(), execFile() различаются, вы можете указать свойство тайм-аута, установленное для создания тайм-аута, когда последние два, как только процесс создания больше установленного времени будет убит
  • Разница между exec() и execFile() заключается в том, что exec() подходит для выполнения существующих команд, а execFile() подходит для выполнения файлов.

7.3 Знаете ли вы, что когда spawn создает дочерний процесс, третий параметр имеет параметр stdio, какова функция этого параметра и каково значение по умолчанию.

  • Параметры используются для настройки каналов, установленных между родительским и дочерним процессами.
  • По умолчанию stdin, stdout и stderr дочернего процесса перенаправляются в соответствующие потоки subprocess.stdin, subprocess.stdout и subprocess.stderr объекта ChildProcess.
  • Это эквивалентно установке для options.stdio значения ['pipe', 'pipe', 'pipe'].

7.4 Могу ли я попросить вас реализовать идею о том, что подпроцесс узла уничтожается, а затем автоматически перезапускается код

  • При создании дочернего процесса позвольте дочернему процессу прослушивать событие выхода, и если он будет убит, разветвите его снова.
var createWorker = function(){
    var worker = fork(__dirname + 'worker.js')
    worker.on('exit', function(){
        console.log('Worker' + worker.pid + 'exited');
        // 如果退出就创建新的worker
        createWorker()
    })
}

7.5 На основе 7.4 реализовано ограниченное количество перезапусков, например, я разрешаю перезапуск 5 раз в течение 1 минуты не более, и сигнализирую об эксплуатации и обслуживании.

  • Идея состоит в том, чтобы определить, был ли созданный рабочий процесс перезапущен более 5 раз за 1 минуту при его создании.
  • Таким образом, каждый раз, когда создается рабочий процесс, время создания рабочего процесса должно быть записано и помещено в очередь массива.Каждый раз, когда создается рабочий процесс, должны быть получены первые 5 записей в очереди.
  • Если временной интервал этих 5 записей меньше 1 минуты, значит пора бить тревогу

7.6 Как добиться совместного использования состояния между процессами или совместного использования данных

Я сам никогда не использовал инструмент очереди сообщений, такой как Kafka. Я спросил java. Подобные инструменты можно использовать для достижения межпроцессного взаимодействия. Приветствуется лучший метод, чтобы оставить сообщение.

8. промежуточное ПО

8.1 Если вы использовали два фреймворка Node, koa и egg, пожалуйста, кратко опишите принцип промежуточного программного обеспечения, желательно в коде.

  • Выше приведена схематическая диаграмма, найденная в Интернете, что означает, что промежуточное программное обеспечение выполняется как луковица, а самое раннее используемое промежуточное программное обеспечение размещается на самом внешнем уровне. Порядок обработки слева направо, левый получает запрос, а правый вывод возвращает ответ.
  • Общее ПО промежуточного слоя будет выполняться дважды, первый раз перед вызовом next, а управление передается следующему промежуточному ПО при вызове next. Когда нижестоящего промежуточного ПО больше нет или следующая функция не выполняется, поведение вышестоящего промежуточного ПО в свою очередь будет восстановлено, и вышестоящее промежуточное ПО выполнит код после следующего.
  • Например следующий код
const Koa = require('koa')
const app = new Koa()
app.use((ctx, next) => {
    console.log(1)
    next()
    console.log(3)
})
app.use((ctx) => {
    console.log(2)
})
app.listen(3001)
执行结果是1=>2=>3

Общая идея исходного кода реализации промежуточного ПО koa выглядит следующим образом:

// 注意其中的compose函数,这个函数是实现中间件洋葱模型的关键
// 场景模拟
// 异步 promise 模拟
const delay = async () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve();
    }, 2000);
  });
}
// 中间间模拟
const fn1 = async (ctx, next) => {
  console.log(1);
  await next();
  console.log(2);
}
const fn2 = async (ctx, next) => {
  console.log(3);
  await delay();
  await next();
  console.log(4);
}
const fn3 = async (ctx, next) => {
  console.log(5);
}

const middlewares = [fn1, fn2, fn3];

// compose 实现洋葱模型
const compose = (middlewares, ctx) => {
  const dispatch = (i) => {
    let fn = middlewares[i];
    if(!fn){ return Promise.resolve() }
    return Promise.resolve(fn(ctx, () => {
      return dispatch(i+1);
    }));
  }
  return dispatch(0);
}

compose(middlewares, 1);

9. Другое

Сейчас прохожу основной API версии ноды 12, много новых открытий, таких как

  • В модуле fs.watch функция обратного вызова события имеет параметр, который является именем инициированного события, но независимо от того, что я добавляю или удаляю, запускается событие переименования (если изменение является событием обновления, удалите удалить событие, а rename — это событие переименования, поэтому было бы неплохо, если бы семантика была ясна). Позже я нашел в Интернете модуль node-watch, в котором есть соответствующие события для добавления, удаления и модификации, а также эффективно поддерживает рекурсивные файлы watch.
  • Модуль util имеет метод обещания, который позволяет функции следовать стилю обратного вызова с первым исключением, то есть (err, value) => ... Функция обратного вызова является последним параметром и возвращает функцию, возвращаемое значение которой является обещанная версия.
const util = require('util');
const fs = require('fs');

const stat = util.promisify(fs.stat);
stat('.').then((stats) => {
  // 处理 `stats`。
}).catch((error) => {
  // 处理错误。
});

9.1 Разные мысли

  • Crypto модуль, вы можете изучить базовые знания криптографии, например, что такое алгоритмы дайджеста (md5, sha1, sha256, соленый md5, sha256 и т. д.), а затем вы можете спросить, как использовать md5 для имитации соленого алгоритма md5, затем Вы можете Спросить, в чем разница между алгоритмами aes и eds в алгоритме шифрования (crypto.createCiphe), какие существуют режимы блочного шифрования (такие как ECB, CBC, почему ECB не рекомендуется), какой режим блочного шифрования (CMM) находится в узел, и эти алгоритмы шифрования В чем смысл заполнения и вектора, а потом можно задать процесс ЭЦП и https (зачем вам CA, зачем вам симметричное шифрование для шифрования открытого ключа и т.д. )
  • tcp/ip, вы можете задать множество основных вопросов, например, какой протокол использует канальный уровень для получения физического адреса (arp) на основе IP-адреса, что такое шлюз, как используется протокол ICMP в ip, трехстороннее рукопожатие tcp, и процесс разрыва в четыре раза Что это такое, как tcp управляет ретрансляцией, что происходит с TCP при блокировке сети и т.д., разница между udp и tcp, что такое широковещательная и многоадресной рассылки в udp и какой модуль используется для достижения многоадресной рассылки в node.
  • ОС, основа, связанная с операционной системой, что такое процесс io (чтение данных с жесткого диска в память ядра, а затем память ядра передает данные в память процесса приложения, вызывающего io), система фон Неймана Что это такое , разница между процессом и потоком и т. д. (Недавно я смотрел учебник Ma Ge по Linux, потому что я не профессионал, я прослушал много базовых компьютерных знаний и получил много пользы. Рекомендуется пойти в билибили посмотреть)
  • Знание операционной системы, связанной с Linux (узел включает в себя фон, хотя это промежуточная платформа и не использует базу данных, но базовые операции с Linux по-прежнему требуются)
  • Мониторинг производительности узла (я тоже учусь)
  • Тест, поскольку используется структура яйца, есть очень полный документ для обучения модульному тестированию, опустите эту часть.
  • База данных может задать некоторые вопросы, например, каков уровень транзакций, каков уровень транзакций по умолчанию для mysql и какие проблемы возникнут, а затем ответить на несколько письменных тестовых вопросов по запросам mysql. . . И общие методы оптимизации, вы использовали инструмент orm mysql узла? . . (Например, я сам смотрел видео mysql junior + advanced Shang Silicon Valley, и эта книга обязательна к знанию для mysql. Я изучил ее из хобби... На самом деле я не сражался)