Механизм require в nodejs, который разработчики должны понимать

Node.js внешний интерфейс Командная строка JavaScript

Оригинальный адрес:medium.free код camp.org/requireing - нет...

image

Node использует два основных модуля для управления зависимостями модулей:

  • requireМодуль: виден глобально, дополнительное использование не требуетсяrequire('require')
  • moduleМодуль: виден глобально, дополнительное использование не требуетсяrequire('module')

Это можно считатьrequireМодуль — это команда,moduleМодули являются организаторами необходимых модулей. Ссылаться на модули в Node несложно:const config = require('/path/to/file'); requireМодуль предоставляет функцию (как показано выше). когдаrequire()Когда функция передает параметр пути, узел последовательно выполняет следующие шаги:

  • Resolving: Найти абсолютный путь path.
  • Loading: определить содержимое файла.
  • Wrapping: Создает частную область. Обертка гарантирует, что каждый раз, когда вам требуется файл, require и exports являются частными.
  • EvaluatingСсылка :evaluating — это последняя ссылка в виртуальной машине, обрабатывающая загруженный файл.
  • Caching: Во избежание ссылок на один и тот же файл не повторяйте вышеуказанные шаги.

В этой статье автор объяснит различные этапы, упомянутые выше, и влияние этих этапов на разработчиков, разрабатывающих модули узлов, посредством тематических исследований. Сначала создайте папку через терминалmkdir ~/learn-node && cd ~/learn-nodeВсе команды в этой статье находятся в~/learn-nodeв исполнении.

Resolving a local path

Сначала давайте представимmoduleобъект. Читатели могут видеть через REPLmoduleобъект

image

Каждый объект модуля имеет атрибут id для различения разных модулей. Атрибут id обычно представляет собой абсолютный путь, соответствующий модулю, который просто устанавливается в REPL как<repl>. Существует однозначное соответствие между модулями Node и файлами на системном диске. Ссылка на модуль фактически загружает содержимое файла в память. Node поддерживает различные способы ссылки на файлы (например, относительные пути или предварительно настроенные пути).Перед загрузкой содержимого файла в память необходимо найти абсолютный путь к файлу. При прямом обращении без указания путиfind-meКогда модуль:require('find-me');узел будет пройден по очередиmodule.pathsуказан путь для поискаfind-meМодуль:

image

Указанный выше путь — это текущий путь ко всем каталогам в корневом каталоге.node_modulesПуть к папке в дополнение к некоторым устаревшим, но устаревшим путям. Если узел не найден в указанном выше путиfind-me.jsБудет выдано исключение «не удается найти ошибку модуля».

image

Если вы создадите папку node_modules в текущей папке и создадите файл find-me.js, тоrequire('find-me')Вы можете найти меня.

image

Если find-me.js также существует в других путях, например, другой find-me.js существует в папке node_modules в домашнем каталоге пользователя:

image

При выполнении в каталоге обучающего кодаrequire('find-me'), поскольку файл find-me.js находится в каталоге node_modules под кодом обучения, файл find-me.js в домашнем каталоге пользователя не будет загружен и выполнен.

image

Если мы удалим папку node_modules из каталога ~/learn-code, а затем выполним ссылку find-me, то будет использоваться Fine-me под node_modules в домашнем каталоге пользователя:

image

Requiring a folder

Модуль не обязательно должен быть просто файлом, ридер также может создать папку find-me и создать index.js в папке,require('find-me')index.js будет использоваться, когда:

image

Обратите внимание, что, поскольку в текущем каталоге есть find-me, обращение к find-me в это время будет игнорировать node_modules в домашнем каталоге пользователя. При обращении к каталогу он по умолчанию ищет index.js, но мы можем указать, какой файл использовать, через свойство main в package.json. Например, чтобы позволитьrequire('find-me')Чтобы иметь возможность анализировать другие файлы в папке find-me, нам нужно добавить package.json в каталог find-me и указать, какой файл следует анализировать:

image

require.resolve

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

image

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

Relative and absolute paths

В дополнение к разрешению модулей из node_modules, мы также можем размещать модули где угодно, используя относительные пути (./или../начало) или абсолютный путь (/начало) для ссылки на модуль. Например, если find-me.js находится в каталоге lib, а не в каталоге node_modules, мы можем обратиться к find-me следующим образом:require('./lib/find-me');

Parent-child relation between files

Создаватьlib/util.jsИ добавьте строку console.log, чтобы различать и выводить одновременноmoduleОбъект:

image

Добавляем аналогичный код в index.js, а затем выполняем index.js через node. упоминается в index.jslib/util.js:

image

Выполните index.js в узле:

image

Обратите внимание на индексный модуль(id: '.')даlib/utilРодительский модуль модуля. но выводlib/utilМодуль не отображается вindexв подмодуле модуля. заменен на[Circular]Значение, потому что здесь циклическая ссылка. В этот момент, если узел печатаетlib/utilдляindexЕсли это подмодуль, он войдет в бесконечный цикл. Это также может объяснить, почему необходимо просто использовать[Circular]заменитьlib/util. то если вlib/utilссылка в модулеindexчто происходит с модулями. Это то, что циклические ссылки разрешены в узле.

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

exports, module.exports, and synchronous loading of modules

Экспорт в любом модуле — это специальный объект. Обратите внимание, что в приведенных выше результатах каждый раз, когда объект модуля печатается, будет свойство экспорта пустого объекта. Мы можем добавить некоторые свойства к этому конкретному объекту экспорта. Такие какindex.jsа такжеlib/index.jsОткройте свойство id.

image

Теперь снова запустите index.js, и вы увидите новые добавленные свойства в объекте модуля каждого файла:

image

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

image

Запустите index.js еще раз, чтобы увидеть, что объект экспорта стал функцией:

image

Здесь замена объекта экспорта на функцию не черезexports = function(){}завершить. На самом деле мы тоже не можем этого сделать, потому что объект экспорта в модуле простоmodule.exportsцитаты, в то время какmodule.exportsЭто свойство отвечает за разоблачение. Когда мы переназначим объект экспорта, он разорвет паруmodule.exports, что в данном случае просто вводит новую переменную, а не изменяет ее.module.exportsАтрибуты.

Когда мы импортируем модуль, то, что возвращает функция require, на самом делеmodule.exportsобъект. Например, поместите index.js вrequire('./lib/util')изменить на:

image

Приведенный выше код помещаетlib/utilАтрибуты, представленные в, назначаются константам UTIL. когда мы выполняемindex.js, последняя строка возвращает следующий результат:UTIL: { id: 'lib/util' }

Давайте поговорим о загруженном свойстве каждого объекта модуля. До сих пор каждый раз, когда мы печатаем объект модуля,loadedсвойстваfalse. module对象通过loadedСвойство для записи того, какие модули были загружены (loaded — true), а какие модули не загружены (loaded — false). в состоянии пройтиsetImmediateчтобы увидеть информацию о том, что модуль был загружен в следующем цикле событий.

image

Результат выглядит следующим образом:

image

задерживаетсяconsole.logв мы можем видетьlib/util.jsа такжеindex.jsбыл полностью загружен. Когда узел завершит загрузку модуля, объект экспорта также станет завершенным.requiring和loadingПроцесс синхронный. Вот почему мы можем видеть, что модуль загружает сообщение о завершении после цикла.

Это также означает, что мы не можем изменять объект экспорта асинхронно. Например, мы не можем сделать что-то вроде этого:

image

Circular module dependency

Давайте ответим на вопрос о циклической зависимости, упомянутый ранее: что произойдет, если модуль 1 зависит от модуля 2, а модуль 2 зависит от модуля 1 в то же время? Чтобы узнать, мыlibСоздайте два файла в каталоге,module1.jsа такжеmodule2.js, и заставьте их ссылаться друг на друга:

image

при исполненииmodule1.js, вы увидите следующие результаты:

image

мы вmodule1Ссылки, которые еще не полностью загруженыmodule2,из-заmodule2в серединеmodule1Ссылается, когда он не был полностью загруженmodule1, в это время вmodule2Может быть полученexportsОбъекты - это атрибуты перед циркой зависимость (то естьrequire('module2')До). Доступно только в это времяaсвойства, потому чтоbа такжеcсобственность вrequire('module2')Позже.

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

JSON and C/C++ addons

пройти черезrequireфункция, которую мы можем загрузить изначальноJSONа такжеC++расширение. Вам даже не нужно указывать расширение при его использовании. Если расширение файла не указано, узел сначала попытается загрузить.jsдокумент. если.jsфайл не найден, он попытается загрузить.jsonфайл, если он найден.jsonфайл будет проанализирован.jsonдокумент. если.jsonфайл тоже не найден, попытается загрузить.nodeдокумент. Но чтобы избежать семантической двусмысленности, разработчики должны.jsВ случае указания расширения файла.

нагрузка.jsonФайлы полезны для управления статической конфигурацией или периодического чтения конфигурации из внешних файлов. Например, у нас есть следующий файл json:

image

Мы можем использовать его напрямую:

image

Запуск приведенного выше кода выведет:Server will run at http://localhost:8080если узел не найден.jsа также.json, буду искать.nodeфайл и проанализируйте его, проанализировав расширение узла.nodeдокумент.

На официальной документации узла написана C ++.Расширенный случай. Дело выявилоhello()функция, выполнениеhello()функция будет выводитьworld. вы можете использоватьnode-gypПучок.ccкомпилировать файл, встроить в.nodeдокумент. Разработчики должны настроитьbinding.gypсказатьnode-gypчто делать. строительствоaddon.nodeВ случае успеха его можно использовать как любой другой модуль:

image

отrequire.extensionsВы можете видеть, что в настоящее время поддерживаются только три типа расширений:

image

Вы можете видеть, что каждый тип имеет различную функцию загрузки. для.jsиспользование файлаmodule._compileметод, для.jsonиспользование файлаJSON.parseметод, для.nodeиспользование файлаprocess.dlopenметод.

All code you write in Node will be wrapped in functions

Пакет модулей в узле часто неправильно понимают. Прежде чем понять пакет модулей узла, давайте рассмотримexports/module.exportsОтношение. мы можем использоватьexportsвыставлять свойства, но не заменять напрямуюexportsобъект, потому чтоexportsобъект в самый разmodule.exporstцитаты.

image

Точнее,exportsОбъекты являются глобальными для каждого модуля и определяются какmoduleСсылка на свойство объекта. Прежде чем объяснять процесс упаковки узлов, давайте зададим еще один вопрос. В браузере, когда мы объявляем переменную в глобальной среде:var answer = 42;по определениюanswerВ скрипте после переменнойanswerПеременные — это глобальные переменные. В узле такого нет. Когда мы определяем переменную в модуле, другие модули не могут напрямую обращаться к переменным в модуле, так как же переменные локализованы в узле?

Ответ прост. Перед компиляцией модуля node оборачивает код модуля в функцию, что мы можем сделать черезmoduleна объектеwrapperсвойства, чтобы увидеть эту функцию:

image

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

wrapperФункция имеет 5 параметров:exports,require,module,__filenameа также__dirname. По этой же причине эти переменные кажутся глобальными для каждого модуля, хотя на самом деле они независимы от каждого модуля.

Когда узел выполняет функцию-оболочку, эти переменные назначаются правильно.exportsопределяется какmodule.exportsцитаты,requireа такжеmoduleоба указывают на функцию, которая должна быть выполнена,__filenameа также__dirnameПредставляет имя файла и путь к каталогу упакованного модуля.

Если вы запустите модуль с ошибкой, функция-обертка будет видна сразу.

image

Вы можете видеть, что об ошибке сообщается в первой строке функции-оболочки. Кроме того, поскольку каждый модуль обернут функцией, мы можем передатьargumentsдля доступа ко всем параметрам функции-оболочки.

image

Первый параметрexportsобъект, начиная с пустого объекта, за которым следуетrequire/moduleобъект, эти два объекта не являются глобальными переменными, оба связаны сindex.jsСвязанный созданный объект. Последние два параметра представляют путь к файлу и путь к папке.

Возвращаемое значение обернутой функцииmodule.exporst. Внутри функции-оболочки мы можем передатьexportsобъект для измененияmodule.exportsсвойства, но не можетexportsпереназначить, потому чтоexportsПросто ссылка.

Приведенное выше описание эквивалентно следующему коду:

image

Если мы изменимexportsобъект, тоexportsобъект больше неmodule.exportsцитаты. Этот способ ссылки отлично работает не только здесь, но и в javascript.

The require object

requireВ объектах нет ничего особенного.requireэто функциональный объект, который принимает имя модуля или имя пути и возвращаетmodule.exportsобъект. Не стесняйтесь переопределять, если мы хотимrequireобъект. Например, для тестирования мы хотим помокатьrequireПоведение функций по умолчанию, возвращающее фиктивный объект, а не ссылающееся на возврат модуляmodule.exportsобъект. правильноrequireВыполнение задания выполняет следующее:

image

справаrequireПосле переназначения каждый вызовrequire('something')вернет фиктивный объект.requireОбъекты также имеют свои собственные свойства. Ранее мы видели, что мы используем для разрешения путей модулей.resolveсвойства иrequire.extensionsАтрибуты. Кроме тогоrequire.mainАтрибуты используются, чтобы различать, ссылаются ли на текущий модуль или запускают его напрямую. Скажем, мыprint-in-frame.jsВ файле есть одинprintInFrameфункция:

image

Эта функция принимает параметр типа numbernumbericи параметр типа stringheader, функция сначала основана наsizeПараметр печатает указанное число*рамку и печать в рамкеheader. Мы можем использовать эту функцию двумя способами:

  1. Вызовите его прямо из командной строки:~/learn-node $ node print-in-frame 8 Hello, передайте 8 и Hello функции в командной строке и напечатайте 8*составленный кадр и вывод в кадреhello.
  2. requireвызов метода: предположимprint-in-frame.jsразоблачитьprintInFrameфункцию, мы можем назвать ее так:
    image

Это напечатает в рамке, состоящей из 5 *Hey. Нам нужен какой-то способ различать, вызывается ли текущий модуль только из командной строки или на него ссылаются другие модули. В этом случае мы можем пройтиrequire.mainсудить:

image

Таким образом, мы можем реализовать описанный выше сценарий приложения с помощью этого условного выражения:

image

Если на текущий модуль не ссылаются другие модули в виде модуля, мы можемprocess.argvзвонитьprintInFrameфункция. В противном случае мы устанавливаемmodule.exportsПараметрыprintInFrameфункция.

All modules will be cached

Важно понимать кэширование модуля. Поясним это на простом примере, например, у нас есть файл js со следующим рисунком персонажа:

image

Мы надеемся каждый разrequireСимволы могут отображаться при документировании. Например, мы обращаемся к js отрисовки персонажа дважды, надеясь вывести отрисовку персонажа дважды:

image

Вторая ссылка не выводит глиф, потому что в этот момент модуль уже кэширован. После первой ссылки мы можем пройтиrequire.cacheдля просмотра кеша модуля.cacheОбъект — это простая пара ключ-значение, в которой кэшируется каждый модуль, на который ссылаются.cacheЗначение on соответствует каждому модулюmoduleобъект. мы можем начать сrequire.cacheудалить наmoduleобъект, чтобы сделать кеш недействительным. Если мы удалим из кешаmoduleObject, при повторном запросе узел все равно перезагрузит модуль и повторно кэширует модуль. Однако для этого случая вышеописанный способ модификации кеша не лучший способ. Самый простой способ - положитьascii-art.jsОберните его в функции и разоблачите ее, чтобы, когда мы ссылаемся наascii-art.js, вы получите функцию, которая будет выводить рисунок персонажа при каждом выполнении.

image