Мы все знаем, что Nodejs следуетCommonJS
норма, когда мыrequire('moduleA')
Когда, как модуль получает модуль по имени или пути? Во-первых, давайте поговорим о трех понятиях: ссылка на модуль, определение модуля и идентификация модуля.
1 CommonJS
Технические характеристики
1.1 Справочник по модулям
предоставленный контекст модуляrequire()
Внедрение внешних модулей, кажущаяся простой функция require на самом деле выполняет большую внутреннюю работу. Пример кода выглядит следующим образом:
//test.js
//引入一个模块到当前上下文中
const math = require('math');
math.add(1, 2);
1.2 Определение модуля
Контекст модуля обеспечиваетexports
Объект используется для импорта и экспорта методов или переменных текущего модуля, и это единственный экспорт. В модуле естьmodule
Объект, представляющий сам модуль,exports
является свойством модуля.Файл - это модуль, вы можете определить метод экспорта, смонтировав метод в качестве атрибута экспорта:
//math.js
exports.add = function () {
let sum = 0, i = 0, args = arguments, l = args.length;
while(i < l) {
sum += args[i++];
}
return sum;
}
Это может быть какtest.js
Вызовите свойство или метод модуля после require().
1.3 Идентификация модуля
Идентификаторы модулей передаются给require()
Параметр метода, который должен быть строкой в верблюжьем регистре или начинаться с.
,..
Относительный или абсолютный путь в начале не может иметь файлового суффикса..js
.
2. Реализация модуля узла
Чтобы внедрить модуль в Node, вам необходимо пройти следующие четыре шага:
- анализ пути
- Позиционирование файла
- Скомпилировать и выполнить
- добавить память
2.1 Анализ пути
Модули в Node.js могут получать ссылки на модули по пути или имени файла.Ссылка на модуль сопоставляется с путем к файлу js.. Модули в Node делятся на две категории:
- Одним из них является модуль, предоставляемый Node, который называетсяосновной модуль(встроенные модули), встроенные модули предоставляют разработчикам некоторые общие API-интерфейсы, и они предварительно загружаются при запуске процесса Node.
- Другая категория — пользовательские модули, называемыефайловый модуль. Подобно сторонним модулям или локальным модулям, установленным через NPM, каждый модуль предоставляет общедоступный API. чтобы разработчики могли импортировать. Такие как
const mod = require('module_name')
const { methodA } = require('module_name')
После выполнения Node загрузит встроенные модули или модули, установленные через NPM. Функция require возвращает объект, а API, предоставляемый объектом, может быть функциями, объектами или свойствами, такими как функции, массивы или даже объект JS любого типа.
Основной модуль — это исходный код Node, скомпилированный в двоичный исполняемый файл в процессе компиляции. Эти модули загружаются в память при запуске Node, поэтому два шага: расположение файла, компиляция и выполнение опускаются при введении основного модуля, и он имеет приоритет при анализе пути, поэтому скорость загрузки основного модуля является самый быстрый. Файловые модули динамически загружаются во время выполнения и работают медленнее, чем основные модули.
Вот механизм загрузки и кэширования модуля узла:
1. Загрузите встроенный модуль (основной модуль)
2. Загрузите файловый модуль (файловый модуль)
3. Загрузите модуль каталога файлов (модуль папки)
4. Загрузите модули в node_modules
5. Автоматически кэшировать загруженные модули
1. Загрузите встроенный модуль
Встроенные модули Node скомпилированы в двоичную форму, и на них можно ссылаться напрямую по имени, а не по пути к файлу. Если сторонний модуль имеет то же имя, что и встроенный модуль, встроенный модуль перезапишет сторонний модуль с таким же именем. Поэтому вам нужно быть осторожным, чтобы при именовании не было того же имени, что и у встроенного модуля. Например, получить модуль http
const http = require('http')
Возвращаемый http — это встроенный модуль Node, который реализует функцию HTTP.
2. Загрузите файловый модуль
абсолютный путь
const myMod = require('/home/base/my_mod')
или относительный путь
const myMod = require('./my_mod')
Обратите внимание, что расширение здесь игнорируется.js
, следующие эквивалентны
const myMod = require('./my_mod')
const myMod = require('./my_mod.js')
3. Загрузите модуль каталога файлов
Вы можете напрямую потребовать каталог, предполагая, что существует каталог с именем папки, например
const myMod = require('./folder')
На этом этапе Node будет искать весь каталог папки, предположит, что папка является пакетом, и попытается найти файл определения пакета package.json. Если каталог папки не содержитpackage.json
файл, Node будет считать, что основным файлом по умолчанию являетсяindex.js
, который загружаетindex.js
. еслиindex.js
Не существует, то нагрузка не удастся.
4. Загрузите модули в node_modules
Если имя модуля не является путем и не является встроенным модулем, Node попытается перейти в текущий каталог.node_modules
Поиск в папке. Если текущий каталогnode_modules
не найден, Node запустится из родительского каталогаnode_modules
Поиск внутри и так далее рекурсивно до корневого каталога.
5. Автоматически кэшировать загруженные модули
Node будет кэшировать загруженные модули без необходимости повторного поиска каждый раз. Ниже приведен пример
// modA.js
console.log('模块modA开始加载...')
exports = function() {
console.log('Hi')
}
console.log('模块modA加载完毕')
//init.js
var mod1 = require('./modA')
var mod2 = require('./modA')
console.log(mod1 === mod2)
Командная строкаnode init.js
воплощать в жизнь:
模块modA开始加载...
模块modA加载完毕
true
Видно, что хотя require выполняется дважды, modA.js по-прежнему выполняется только один раз. mod1 и mod2 идентичны, т. е. обе ссылки указывают на один и тот же объект модуля.
Сначала загрузить из кеша
Точно так же, как браузеры кэшируют статические js-файлы, Node также кэширует импортированные модули, разница в том, что браузеры кэшируют только файлы, а nodejs кэширует скомпилированные и выполненные объекты (кэш-память)require()
Вторичная загрузка того же модуля всегда будет использовать метод приоритета кеша, который является первым приоритетом.Проверка кеша основного модуля предшествует проверке кеша файлового модуля.
Исходя из этого: мы можем написать модуль, записывающий долгоживущие переменные. Например: я мог бы написать модуль, который записывает количество посещений интерфейса:
let count = {}; // 因模块是封闭的,这里实际上借用了js闭包的概念
exports.count = function(name){
if(count[name]){
count[name]++;
}else{
count[name] = 1;
}
console.log(name + '被访问了' + count[name] + '次。');
};
мы маршрутизируемaction
илиcontroller
Цитирую так:
let count = require('count');
export.index = function(req, res){
count('index');
};
Вышеприведённое завершает статистику количества обращений к интерфейсу, но это всего лишь демонстрация, т.к. данные хранятся в памяти, и она будет очищена после перезагрузки сервера. Реальный счетчик должен сочетаться с постоянным хранилищем.
Прежде чем перейти к поиску пути, необходимо описатьmodule path
Эта концепция в Node.js. Для каждого загружаемого файлового модуля при создании объекта модуля модуль будет иметь свойство paths, значение которого вычисляется из пути к текущему файлу. мы создаемmodulepath.js
Такой файл, его содержимое:
// modulepath.js
console.log(module.paths);
Мы помещаем его в любой каталог и выполняем команду node modulepath.js, и мы получим следующий вывод.
[ '/home/ikeepstudying/research/node_modules',
'/home/ikeepstudying/node_modules',
'/home/node_modules',
'/node_modules' ]
2.2 Местоположение файла
1. Анализ расширения файла
передачаrequire()
Если параметр не имеет расширения файла в методе, Node нажмет.js
,.json
,.node
Найдите расширение дополнения, попробуйте по очереди.
Во время попытки необходимо вызватьблокировка модуля фсчтобы определить, существует ли файл. Поскольку выполнение Node является однопоточным, именно здесь возникают проблемы с производительностью. если.node
Или файлы .json можно добавить с расширением, чтобы немного ускорить процесс. Еще одна хитрость: синхронизация с кешем.
2. Анализ каталогов и пакеты
require()
После анализа расширения файла соответствующий файл может быть не найден, но найден каталог, в это время Node будет рассматривать каталог как пакет.
Сначала Node просматривает каталог подпорной стены.package.json
,пройти черезJSON.parse()
Разберите объект описания пакета и извлеките имя файла, указанное основным атрибутом для позиционирования. Если в основном атрибуте указано неправильное имя файла или отсутствуетpachage.json
файл, Node будет использовать index в качестве имени файла по умолчанию.
Короче говоря, еслиrequire
Файлы с абсолютными путями не будут проходить через каждый файл при поискеnode_modules
каталог, который является самым быстрым. В остальном процесс выглядит следующим образом:
1. Изmodule path
В качестве ссылки для поиска берется первый каталог в массиве.
2. Найдите файл прямо в каталоге и завершите поиск, если он существует. Если его нет, перейдите к следующему поиску.
3. Попробуйте добавить.js
,.json
,.node
Ищите по суффиксу, если есть файл, заканчивайте поиск. Если он не существует, перейдите к следующей записи.
4. Попробуйте поставитьrequire
Параметры ищутся пакетом, читаем каталогpackage.json
файл, получитьmain
Файл, указанный параметром.
5. Попытайтесь найти файл, если он существует, завершите поиск. Если он не существует, выполните третий поиск.
6. Если он продолжает выходить из строя, выньте его.module path
Следующий каталог в массиве используется в качестве базового поиска, повторяя шаги с 1 по 5.
7. Если он продолжает давать сбой, повторите шаги с 1 по 6 до последнего значения в пути к модулю.
8. Если это все еще не удается, сгенерируйте исключение.
Весь процесс поиска очень похож на поиск цепочки прототипов и поиск области действия. К счастью, Node.js реализует механизм кэширования для поиска пути, иначе, поскольку каждое определение пути выполняется синхронно и с блокировкой, это приведет к серьезному снижению производительности.
После успешной загрузки кеш с путем к модулю
2.3 Компиляция модуля
Каждый модуль файла module представляет собой объект, который определяется следующим образом:
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
компиляция файлов
.json
Метод вызова файла следующий:JSON.parse
//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
};。
Тем не менее, чиновник не поощряет загрузку пользовательских расширений таким образом, но ожидает компиляции других языков или файлов в файлы JavaScript перед загрузкой.
js
компиляция модулейВ процессе компиляции Node оборачивает содержимое файла javascript, полученного с помощью head и tail, и оборачивает содержимое файла в функцию:
(function (exports, require, module, __filename, __dirname) {
var math = require(‘math‘);
exports.area = function(radius) {
return Math.PI * radius * radius;
}
})
Упакованный код будет передавать собственный модуль vmrunInThisContext()
Метод выполняется (с четким контекстом, не загрязняет глобальную), возвращает конкретный объект функции и, наконец, передает параметры для выполнения и возвращает после выполненияmodule.exports
.
Core Module Compilation
Основные модули делятся наC/C++
Написание и написание JavaScript две части, гдеC/C++
Файлы помещаются в каталог src проекта Node, а файлы JavaScript помещаются в каталог lib.
1. Дамп как код C/C++
Node использует инструмент js2c.py, который поставляется с V8, преобразует весь встроенный код 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 также проходят процесс упаковки головы и хвоста во время процесса введения, а затем объект экспорта выполняется и экспортируется. Отличия от файловых модулей заключаются в том, как получен исходный код (модули ядра загружаются из памяти) и где кэшируются результаты выполнения.
исходный файл основного модуля 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 = {};
3 import
а такжеrequire
просто скажиimport
а такжеrequire
существенная разница
import
спецификация модуля ES6,require
Это спецификация модуля commonjs, я не буду вводить подробное использование, я просто хочу поговорить об их самых основных различиях.import — статическая (время компиляции) загрузка модулей, require (время выполнения) — динамическая загрузка, тогда в чем разница между статической нагрузкой и динамической нагрузкой?
При статической загрузке код уже выполняется при компиляции, а динамическая загрузка выполняется после компиляции при запуске кода, так в чем конкретно суть? Давайте сначала поговорим об импорте, следующий код
import { name } from 'name.js'
// name.js文件
export let name = 'jinux'
export let age = 20
Приведенный выше код означаетmain.js
представлен в файлеname.js
Переменные, экспортируемые файлом, выполняются на этапе компиляции кода, и код выглядит следующим образом:
let name = 'jinux'
Это мое собственное понимание, на самом деле, непосредственноname.js
Код вmain.js
файл, как вmain.js
как заявлено в файле.
Давайте снова посмотрим на требование
var obj = require('obj.js');
// obj.js文件
var obj = {
name: 'jinux',
age: 20
}
module.export obj;
require находится в стадии выполнения, вам нужно загрузить весь объект obj в память, а затем использовать, какая переменная используется.Давайте сравним это здесьimport
,import
Это статическая загрузка.Если ввести только имя, возраст не будет введен, поэтому он импортируется по запросу, и производительность лучше.
4 nodejs очищают, требуют кеша
При разработке приложений nodejs вы столкнетесь с неприятной вещью, то есть после изменения данных конфигурации вы должны перезапустить сервер, чтобы увидеть измененные результаты.
Так вот вопрос, крепкий экскаватор Какой? О, нет!Нет!Нет!Чтобы сделать после того, как изменить файл, перезагрузите сервер автоматически.
server.js
Фрагмент из:
const port = process.env.port || 1337;
app.listen(port);
console.log("server start in " + port);
exports.app = app;
Допустим, мы сейчас такие, фрагмент app.js:
const app = require('./server.js');
Если мы запустили сервер в server.js, мы остановим сервер, который можно вызвать в app.js
app.app.close()
Но когда мы снова вводим server.js
app = require('./server.js')
Когда вы обнаружите, что не используете последний файл server.js, причиной является механизм кэширования require, когда первый вызовrequire('./server.js')
время кэшируется.
Что нам делать в это время?
Следующий код решает проблему:
delete require.cache[require.resolve('./server.js')];
app = require('./server.js');