Всичко, което трябва да знаете за „модул“ и „изискване“ в Node.js

Модули

Node.js третира всеки JavaScript файл като отделен модул.

Например, ако имате файл, съдържащ някакъв код и този файл е именуван xyz.js, тогава този файл се третира като модул в Node и можете да кажете, че сте създали модул с име xyz.

Да вземем пример, за да разберем това по-добре.

Имате файл с име, circle.jsкойто се състои от логиката за изчисляване на площта и обиколката на кръг с даден радиус, както е дадено по-долу:

circle.js

Можете да извикате circle.jsфайл модул с име circle.

Може би се чудите защо е необходимо да имате множество модули? Можеше просто да напишете целия код в един модул. Е, много е важно да пишете модулен код. Под модулна, искам да кажа, че вашият код трябва да бъде независим и да бъде свободно свързан. Представете си, че има голямо приложение и целият ви код е написан само на едно място, само в един файл. Твърде разхвърляно, нали?

Как работи кодът, написан в модула?

Преди да изпълни кода, написан в модул, Node взема целия код и го затваря в обвивка на функция. Синтаксисът на тази обвивка на функция е:

Функционалната обвивка за circleмодула ще изглежда като тази, дадена по-долу:

Можете да видите, че има функция-обвивка на основно ниво, обхващаща целия код, написан вътре в circleмодула.

Целият код, написан в модула, е частен за модула, освен ако изрично не е посочено (експортирано) друго.

Това е най-същественото предимство от наличието на модули в Node.js. Дори и да се определи глобална променлива в един модул с помощта на var, letили constключови думи, променливите са с обхват на местно ниво към модула вместо да се тръгва по целия свят. Това се случва, защото всеки модул има собствена обвивка на функции и кодът, написан вътре в една функция, е локален за тази функция и не може да бъде достъпен извън тази функция.

Представете си, че има два модула - А и Б . Кодът писмено вътре в модул е затворен в обвивка функцията съответстваща на модул . Подобно нещо се случва с кода изписва и в модул B . Тъй като кодът, отнасящ се до двата модула, е затворен в различни функции, тези функции няма да имат достъп до кода един на друг. (Не забравяйте, че всяка функция в JavaScript има свой собствен локален обхват?) Това е причината, поради която модул A няма достъп до кода, написан вътре в модул B, и обратно.

Петте параметри - exports, require, module, __filename, __dirnameса на разположение във всеки модул в възел. Въпреки че тези параметри са глобални за кода в даден модул, но те са локални за модула (поради функцията за обвиване на функции, както е обяснено по-горе). Тези параметри предоставят ценна информация, свързана с модул.

Нека отново посетим circleмодула, който разгледахте по-рано. В този модул са дефинирани три конструкции - константна променлива PI, функция с име calculateAreaи друга функция с име calculateCircumference. Важен момент, който трябва да имате предвид, е, че всички тези конструкции са частни за circleмодула по подразбиране. Това означава, че не можете да използвате тези конструкции във всеки друг модул, освен ако не е изрично посочено.

И така, въпросът, който възниква сега, е как посочвате нещо в модул, който може да се използва от някой друг модул? Това е, когато module& requireпараметрите на обвивката на функции са полезни. Нека обсъдим тези два параметъра в тази статия.

module

На moduleпараметъра (а ключова дума в модул в Node) се отнася до обекта, представляващ текущия модул . exportsе ключ на moduleобекта, чиято съответна стойност е обект. Стойността по подразбиране на module.exportsобекта е {}(празен обект). Можете да проверите това, като регистрирате стойността на moduleключовата дума във всеки модул. Нека проверим каква е стойността на moduleпараметъра вътре в circleмодула.

circle.js

Забележете, че console.log(module);в края на кода във файла, даден по-горе , има изявление. Когато видите изхода, той ще регистрира moduleобекта, който има ключ с име exportsи стойността, съответстваща на този ключ, е {}(празен обект).

Сега, какво прави module.exportsобектът? Е, той се използва за определяне на неща, които могат да бъдат експортирани от модул. Каквото и да се експортира от модул, от своя страна може да бъде предоставено на други модули. Експортирането на нещо е доста лесно. Трябва само да го добавите към module.exportsобекта. Има три начина да добавите нещо към module.exportsобекта, който ще бъде експортиран. Нека обсъдим тези методи един по един.

Метод 1:

(Дефиниране на конструкции и след това използване на множество module.exportsизрази за добавяне на свойства)

В първия метод първо дефинирате конструкциите и след това използвате множество модули.exports инструкции, където всеки оператор се използва за експортиране на нещо от модул. Нека да разгледаме този метод в действие и да видим как можете да експортирате двете функции, дефинирани в circleмодула.

circle.js

Както ви казах по-рано, moduleе обект с име на ключ exportsи този ключ ( module.exports) от своя страна се състои от друг обект. Сега, ако забележите кода, даден по-горе, всичко, което правите, е да добавите нови свойства (двойки ключ-стойност) към module.exportsобекта.

Първото свойство има ключа calculateArea(определено на ред 19)а стойността, записана от дясната страна на оператора за присвояване, е функцията, дефинирана с името calculateArea(на ред 9).

Второто свойство (дефинирано на ред 20) има ключа calculateCircumferenceа стойността е функцията, дефинирана с името calculateCircumference(на ред 16).

По този начин сте присвоили две свойства (двойки ключ-стойност) на module.exportsобекта.

Също така, нека не забравяме, че тук сте използвали точковото обозначение. Можете алтернативно да използвате скобата нотация за присвояване на свойствата на module.exportsобекта и да добавите функциите - calculateAreaи calculateCircumferenceкато посочите клавишите, следващи нотацията на скобата. По този начин можете да напишете следните два реда, за да добавите свойства към module.exportsобекта, като използвате скоба нотация, докато замествате последните два реда (използвайки точкова нотация) в кода, даден по-горе:

// exporting stuff by adding to module.exports object using the bracket notation
module.exports['calculateArea'] = calculateArea;module.exports['calculateCircumference'] = calculateCircumference; 

Нека сега се опитаме да регистрираме стойността на module.exportsобекта след добавяне на свойствата. Забележете, че следното изявление е добавено в края на кода във файла, даден по-долу:

// logging the contents of module.exports object after adding properties to it
console.log(module.exports);

circle.js

Нека проверим изхода на този код и да видим дали всичко работи добре. За да направите това, запазете кода си и изпълнете следната команда във вашия терминал :

node circle

Изход:

{ calculateArea: [Function: calculateArea], calculateCircumference: [Function: calculateCircumference] }

Конструкциите - calculateAreaи calculateCircumference, добавени към module.exportsобекта, се регистрират. По този начин вие успешно добавихте двете свойства в module.exportsобекта, така че функциите - calculateAreaи calculateCircumferenceда могат да бъдат експортирани от circleмодула в някой друг модул.

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

Метод 2:

(Дефиниране на конструкции и след това използване на един module.exportsизраз за добавяне на свойства)

Друг начин е първо да дефинирате всички конструкции (както направихте в по-ранния метод), но да използвате един module.exportsоператор, за да ги експортирате всички. Този метод е подобен на синтаксиса на нотация на литерален обект, където добавяте всички свойства към обект наведнъж.

Тук използвате обектната литерална нотация и добавихте двете функции - calculateArea и calculateCircumference(всички наведнъж) към module.exportsобекта, като напишете един оператор module.exports .

Ако проверите изхода на този код, ще получите същия резултат, който сте получили по-рано при използване на метод 1.

Метод 3:

(Добавяне на свойства към module.exportsобекта при дефиниране на конструкции)

В този метод можете да добавяте конструкциите към module.exportsобекта, докато ги дефинирате. Нека да видим как този метод може да бъде приет в нашия circleмодул.

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

Имайте предвид, че функцията вече няма никакво име и е анонимна функция, която просто се третира като стойност за ключ на обект. По този начин на тази функция не може да се прави препратка в circleмодула и не можете да извикате тази функция вътре в този модул, като напишете следния израз:

calculateArea(8);

Ако се опитате да изпълни горното твърдение, ще получите ReferenceErrorкато посочва calculateArea is not defined.

След като научихте как можете да посочите какво трябва да се експортира от модул, как мислите, че другият модул ще може да използва експортираните неща? Трябва да импортирате модула в някой друг модул, за да можете да използвате експортираните неща от първия във втория. Това е, когато трябва да обсъдим друг параметър с име require.

изискват

requireключовата дума се отнася до функция, която се използва за импортиране на всички конструкции, експортирани с помощта на module.exportsобекта от друг модул. Ако имате модул x, в който експортирате някои конструкции с помощта на module.exportsобекта и искате да импортирате тези експортирани конструкции в модул y , тогава трябва да изисквате модула x в модула y, използвайки requireфункцията. Стойността, върната от requireфункцията в модул y, е равна на module.exportsобекта в модула x .

Нека разберем това, като използваме примера, който обсъдихме по-рано. Вече имате circleмодула, от който експортирате функциите calculateAreaи calculateCircumference. Сега нека видим как можете да използвате requireфункцията за импортиране на експортираните неща в друг модул.

Нека първо създадем нов файл, в който ще използвате експортирания код от circleмодула. Нека да дадем име на този файл app.jsи можете да го наречете appмодул.

Целта е да импортирате в appмодула целия код, експортиран от circleмодула. И така, как можете да включите кода си, написан в един модул, в друг модул?

Помислете за синтаксиса на requireфункцията, дадена по-долу:

const variableToHoldExportedStuff = require('idOrPathOfModule');

На requireфункцията се в довод, който може да бъде ID или път. Идентификаторът се отнася до идентификатора (или името) на необходимия модул. Трябва да предоставите ID като аргумент, когато използвате модули на трети страни или основни модули, предоставени от Node Package Manager. От друга страна, когато имате дефинирани от вас персонализирани модули, трябва да предоставите пътя на модула като аргумент. Можете да прочетете повече за функцията за изискване от тази връзка.

Тъй като вече сте дефинирали персонализиран модул с име circle, ще предоставите пътя като аргумент на requireфункцията.

app.js

Ако забележите ясно, точката в началото на пътя означава, че това е относителен път и че модулите appи circleсе съхраняват в същия път.

Нека влезем в конзолата на circleпроменливата, която съдържа резултата, върнат от requireфункцията. Нека да видим какво се съдържа вътре в тази променлива.

app.js

Проверете изхода, като запазите целия си код и изпълните следната команда във вашия терминал (последната не е необходима, ако имате nodemonинсталиран пакет):

node app

Изход:

{ calculateArea: [Function: calculateArea],calculateCircumference: [Function: calculateCircumference] }

Както можете да видите, requireфункцията връща обект, чиито ключове са имената на променливите / функциите, които са експортирани от необходимия модул ( circle). Накратко, requireфункцията връща module.exportsобекта.

Нека сега да осъществим достъп до функциите, внесени от circleмодула.

app.js

Изход:

Area = 200.96, Circumference = 50.24

Какво мислите, че ще се случи, ако се опитам да вляза в променливата, PIопределена в circleмодула вътре в appмодула?

app.js

Изход:

Area = 200.96, Circumference = 50.24pi = undefined

Може ли да разбера защо piе undefined? Е, това е така, защото променливата PIне се експортира от circleмодула. Не забравяйте точката, в която ви казах, че нямате достъп до кода, написан вътре в модул в друг модул, тъй като целият код, написан в модула, е частен за него, освен ако не бъде експортиран? Тук се опитвате да получите достъп до нещо, което не е експортирано от circleмодула и е частно за него.

Така че, може би се чудите защо не сте получили ReferenceError. Това е така, защото се опитвате да получите достъп до ключ, наречен PIвътре в module.exportsобекта, върнат от requireфункцията. Знаете също така, че посоченият ключ PIне съществува в module.exportsобекта.

Имайте предвид, че когато се опитате да осъществите достъп до несъществуващ ключ в обект, получавате резултата като undefined. Това е причината, поради която получавате PI, undefinedвместо да получавате ReferenceError.

Сега нека да експортираме променливата PIот circleмодула и да видим дали отговорът се променя.

circle.js

Забележете, че тук не използвате името на променливата PIкато ключ на свойството, добавено към module.exportsобекта. Вместо това използвате друго име, което е lifeOfPi.

Това е интересно нещо, което трябва да се отбележи. Когато експортирате някаква конструкция за кодиране, можете да дадете произволно име на ключа, когато добавяте свойство, добавено към module.exportsобекта. Не е задължително да използвате същото име като името, което сте използвали, докато дефинирате конструкцията. Това е така, защото можете да използвате всеки валиден идентификатор като ключ в обект на JavaScript. По този начин от лявата страна на оператора за присвояване можете да използвате всеки валиден идентификатор, но от дясната страна на оператора за присвояване трябва да предоставите стойност, която е определена като конструкция в обхвата на текущия модул (както вие дефинирах променливите и функциите в модула "кръг").

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

app.js

Тъй като сте използвали ключа lifeOfPi, трябва да използвате същия ключ за достъп до променливата, PIдефинирана в circleмодула, както е направено в кода, даден по-горе.

Изход:

Area = 200.96, Circumference = 50.24pi = 3.14

Какво мислите, че ще се случи, ако използвате името на променливата, вместо да използвате ключа, който е бил използван по време на експортирането? Накратко, нека се опитаме да осъществим достъп PI(име на променливата) вместо lifeOfPi(ключ, използван при експортиране PI).

app.js

Изход:

Area = 200.96, Circumference = 50.24pi = undefined

Това се случва, защото module.exportsобектът вече не познава променливата PI. Той просто знае за ключовете, добавени към него. Тъй като ключът, използван за експортиране на променливата, PIе lifeOfPi, последният може да се използва само за достъп до първия.

TL; DR

  • Всеки файл в Node.js се нарича модул .
  • Преди да изпълни кода, написан в модул, Node.js взема целия код, написан вътре в модула, и го преобразува във функционална обвивка, която има следния синтаксис:
(function(exports, require, module, __filename, __dirname) { // entire module code lives in here});
  • Функцията обвивка гарантира, че целият код, написан в модула, е частен за него, освен ако изрично не е посочено друго (експортирано). Параметрите exports, require, module, __filename, и __dirnameдействат като променливите глобални към целия код в един модул. Тъй като всеки модул има собствена обвивка на функции, кодът, написан вътре в една обвивка на функция, става локален за тази обвивка на функцията (модул за четене) и не е достъпен в друга обвивка за функции (модул за четене).
  • moduleключова дума се отнася до обекта, представляващ текущия модул. В moduleобекта има ключ на име exports. module.exportsе друг обект, който се използва за определяне на това, което може да бъде експортирано от модул и може да бъде предоставено на други модули. Накратко, ако даден модул иска да експортира нещо, той трябва да бъде добавен към module.exportsобекта.
  • Стойността по подразбиране на module.exportsобекта е {}.
  • Има три метода, при които можете да експортирате нещо от модул или да добавите нещо към module.exportsобекта:

    1. Дефинирайте първо всички конструкции и след това използвайте множество module.exportsизрази, където всеки израз се използва за експортиране на конструкция.

    2. Дефинирайте първо всички конструкции и след това използвайте един module.exportsоператор, за да експортирате всички конструкции наведнъж, следвайки обектната литерална нотация.

    3. Добавете конструкции към module.exportsобекта, докато ги дефинирате.

  • requireключовата дума се отнася до функция, която се използва за импортиране на всички променливи и функции, експортирани с помощта на module.exportsобекта от друг модул. Накратко, ако файл иска да импортира нещо, той трябва да го декларира, като използва следния синтаксис:
require('idOrPathOfModule');
  • Докато експортирате нещо от модул, можете да използвате всеки валиден идентификатор. Не е задължително да трябва да давате точното име на променливата / функцията като ключ на свойството, добавено към module.exportsобекта. Просто се уверете, че използвате същия ключ за достъп до нещо, което сте използвали, докато сте го експортирали.