Documente Academic
Documente Profesional
Documente Cultură
Module #
16.1. Prezentare generală
16.4.3. Re-exportatori
16.5.1. Încărcătoare
Modulele ES6 sunt stocate în fișiere. Există exact un modul pe fișier și un fișier per modul.
Aveți două moduri de a exporta lucrurile dintr-un modul. Aceste două moduri pot fi
amestecate , dar de obicei este mai bine să le utilizați separat.
return x * x;
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5
console.log(lib.square(11)); // 121
console.log(lib.diag(4, 3)); // 5
myFunc();
Sau o clasă:
Rețineți că nu există nicio virgulă la sfârșit dacă exportați implicit o funcție sau o clasă (care sunt
declarații anonime).
Fiecare modul este o bucată de cod care se execută odată ce este încărcat.
În codul respectiv, pot exista declarații (declarații variabile, declarații funcționale etc.).
Puteți marca unele dintre ele ca exporturi, apoi alte module le pot importa.
Un modul poate importa lucrurile din alte module. Se referă la acele module prin intermediul
specificatorilor modulului , șiruri care sunt:
Căi relative ( '../model/user'): aceste căi sunt interpretate relativ în funcție de locația modulului
importator. Extensia de fișier .jspoate fi de obicei omisă.
Căi absolute ( '/lib/js/helpers'): indicați direct către fișierul modulului care trebuie importat.
Modulele sunt singletone. Chiar dacă un modul este importat de mai multe ori, există o singură
„instanță” a acestuia.
Această abordare a modulelor evită variabile globale, singurele lucruri care sunt globale sunt
specificatorii modulelor.
Sintaxa compactă
Sintaxa puțin mai complicată, care permite AMD să funcționeze fără evalu () (sau o etapă de
compilare)
Cele de mai sus nu sunt decât o explicație simplificată a modulelor ES5. Dacă doriți mai multe
materiale aprofundate, aruncați o privire la „ Scrierea JavaScript Modular cu AMD, CommonJS și
ES Harmony ” de Addy Osmani.
În mod similar CommonJS, acestea au o sintaxă compactă, o preferință pentru exporturi unice și
suport pentru dependențele ciclice.
În mod similar AMD, au suport direct pentru încărcarea asincronă și încărcarea modulului
configurabil.
Fiind încorporat în limbaj, permite modulelor ES6 să depășească CommonJS și AMD (detaliile
sunt explicate mai târziu):
Structura lor poate fi analizată static (pentru verificare statică, optimizare etc.).
Suportul lor pentru dependențele ciclice este mai bun decât cel al CommonJS.
API programatică de încărcare: pentru a configura modul în care sunt încărcate modulele și
pentru a încărca condițional modulele
return x * x;
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5
Există și alte modalități de a specifica exporturile numite (care sunt explicate mai târziu), dar mi
se pare unul destul de convenabil: pur și simplu scrieți-vă codul ca și cum nu ar exista lume
exterioară, apoi etichetați tot ce doriți să exportați cu un cuvânt cheie
Dacă doriți, puteți importa, de asemenea, întregul modul și consultați exporturile numite prin
notarea proprietății:
//------ main.js ------
console.log(lib.square(11)); // 121
console.log(lib.diag(4, 3)); // 5
Același cod în sintaxa CommonJS: Pentru un timp, am încercat mai multe strategii inteligente
pentru a fi mai puțin redundant cu exporturile modulului meu în Node.js. Acum prefer următorul
stil simplu, dar ușor verbos, care amintește de modelul modulului revelator :
function square(x) {
return x * x;
function diag(x, y) {
module.exports = {
sqrt: sqrt,
square: square,
diag: diag,
};
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5
Modulele care exportă doar valori unice sunt foarte populare în comunitatea Node.js. Dar sunt
de asemenea frecvente în dezvoltarea de frontenduri, unde aveți deseori clase pentru modele și
componente, cu o clasă pe modul. Un modul ES6 poate alege un export implicit , valoarea
principală exportată. Exporturile implicite sunt foarte ușor de importat.
myFunc();
Un modul ECMAScript 6 al cărui export implicit este o clasă arată după cum urmează:
Declarații de etichetare
De asemenea, puteți omite numele în acest caz. Aceasta face ca exporturile implicite să fie
singurul loc în care JavaScript are declarații de funcții anonime și declarații de clasă anonime:
export default 5 * 7;
Declarația din linia A este o clauză de export (care este explicată într- o secțiune ulterioară ).
Care dintre cele trei variabile foo, barși bazar fi exportul implicit?
Această restricție este aplicată sintactic, permițând doar importurile și exporturile la nivelul
superior al unui modul:
if (Math.random()) {
foo();
counter++;
console.log(counter); // 3
incCounter();
console.log(counter); // 4
Cum funcționează asta sub capotă este explicat într-o secțiune ulterioară .
Ele activează dependențe ciclice, chiar și pentru importuri necalificate (așa cum este explicat în
secțiunea următoare).
Puteți împărți codul în mai multe module și va continua să funcționeze (atât timp cât nu încercați
să modificați valorile importurilor).
var b = require('b');
function foo() {
b.bar();
exports.foo = foo;
function bar() {
if (Math.random()) {
a.foo(); // (ii)
exports.bar = bar;
Dacă modulul aeste importat mai întâi atunci, în linia i, modulul bprimește aobiectul exporturilor
înainte ca exporturile să fie adăugate la acesta. Prin urmare, bnu se poate accesa a.foola nivelul
său superior, dar acea proprietate există odată ce execuția lui aeste terminată. Dacă bar()este
apelat după aceea, funcționarea apelului din linia II funcționează.
De regulă, rețineți că, cu dependențe ciclice, nu puteți accesa importurile în corpul modulului.
Acest lucru este inerent fenomenului și nu se schimbă cu modulele ECMAScript 6.
Exporturile de o singură valoare în stil Node.js nu funcționează. Acolo, exportați valori unice în
loc de obiecte:
Dacă modul a afăcut acest lucru, atunci bvariabila modulului anu ar fi actualizată odată ce s-a
făcut atribuirea. Ar continua să se refere la obiectul inițial de export.
Nu puteți utiliza exporturile numite direct. Adică, modulul bnu poate importa fooastfel:
foopur și simplu ar fi undefined. Cu alte cuvinte, nu aveți de ales decât să faceți referire la foovia
a.foo.
Aceste limitări înseamnă că atât exportatorul cât și importatorii trebuie să fie conștienți de
dependențele ciclice și să le sprijine în mod explicit.
if (Math.random()) {
foo(); // (iv)
Acest cod funcționează, deoarece, așa cum este explicat în secțiunea precedentă, importurile
reprezintă opinii asupra exporturilor. Asta înseamnă că chiar și importurile necalificate (cum ar fi
barlinia ii și foolinia iv) sunt indicații care se referă la datele originale. Astfel, în fața
dependențelor ciclice, nu contează dacă accesați un export numit printr-un import necalificat
sau prin modulul său: Există o indirecție în ambele cazuri și funcționează întotdeauna.
Import implicit:
Importarea spațiului de nume: importă modulul ca obiect (cu o proprietate pe export numit).
Importuri denumite:
Import gol: încarcă doar modulul, nu importă nimic. Primul astfel de import într-un program
execută corpul modulului.
import 'src/my_lib';
Există doar două moduri de a combina aceste stiluri și ordinea în care apar este fixată; exportul
implicit vine întotdeauna pe primul loc.
···
···
···
Pe de altă parte, puteți enumera tot ceea ce doriți să exportați la sfârșitul modulului (care este
similar în stil cu modelul modulului revelator).
function myFunc() {
···
16.4.3 Reexportarea
Reexport înseamnă adăugarea exporturilor unui alt modul la cele ale modulului actual. Puteți
adăuga toate exporturile celuilalt modul:
Următoarea declarație face ca exportul numit myFuncal modulului să foofie exportul implicit al
modulului curent:
Re-exportatori:
Declarații variabile:
Declarații de funcție:
Declarații de clasă:
Export implicit:
export default 3 * 7;
Următorul model este surprinzător de comun în JavaScript: o bibliotecă este o singură funcție,
dar servicii suplimentare sunt furnizate prin proprietățile acelei funcții. Exemple includ jQuery și
Underscore.js. Următoarea este o schiță a Underscore ca modul CommonJS:
···
};
···
};
module.exports = _;
var _ = require('underscore');
···
Cu ochelari ES6, funcția _este exportul implicit, în timp ce eachși forEachsunt denumite
exporturi. După cum se dovedește, puteți avea, de fapt, exporturi numite și export implicit în
același timp. Ca exemplu, modulul CommonJS anterior, rescris ca modul ES6, arată astfel:
···
···
···
În general, recomand să păstrați separat cele două tipuri de export: pe modul, fie aveți doar un
export implicit sau au doar exporturi numite.
Totuși, aceasta nu este o recomandare foarte puternică; uneori poate avea sens să amesteci cele
două tipuri. Un exemplu este un modul care exportă implicit o entitate. Pentru testele unitare,
puteți pune la dispoziție, în plus, unele dintre cele interne prin exporturi numite.
function foo() {}
Asta înseamnă că defaultpoate apărea doar pe partea stângă a unui import de redenumire:
Acesta va fi specificat într-un document separat, „JavaScript Loader Standard”, care va fi evoluat
mai dinamic decât specificația de limbă. Depozitul pentru acel document prevede:
După cum puteți vedea în depozitul Standardului de încărcare JavaScript , API-ul de încărcare a
modulului este încă în curs de desfășurare. Tot ce ai citit despre asta în această carte este
tentativ. Pentru a avea impresia cum ar putea arăta API-ul, puteți arunca o privire la polifileta de
încărcare a modulelor ES6 de pe GitHub.
16.5.1 Încărcătoare
Încărcătoarele se ocupă cu specificațiile modulului de rezolvare (ID-urile șirului de la sfârșitul
import-from), modulele de încărcare, etc. Constructorul lor este Reflect.Loader. Fiecare
platformă păstrează o instanță implicită în variabila globală System( încărcătorul de sistem ), care
implementează stilul său specific de încărcare a modulelor.
System.import('some_module')
.then(some_module => {
// Use some_module
})
.catch(error => {
···
});
Promise.all(
System.module(source, options?)
evaluează codul JavaScript într- sourceun modul (care este livrat asincron prin intermediul unei
promisiuni).
System.set(name, module)
este pentru înregistrarea unui modul (de exemplu, unul pe care l-ați creat prin intermediul
System.module()).
Traduceți automat modulele la import (ar putea conține codul CoffeeScript sau TypeScript).
Încărcarea modulului configurabil este o zonă în care Node.js și CommonJS sunt limitate.
La fel ca în cazul încărcării modulelor, încă se lucrează la alte aspecte ale suportului pentru
module din browsere. Tot ce ai citit aici se poate schimba.
În browsere, există două tipuri diferite de entități: scripturi și module. Au sintaxa ușor diferită și
funcționează diferit.
Scripturi module
16.6.1.1 Scripturi
Scripturile sunt modul tradițional al browserului de a încorpora JavaScript și de a face referire la
fișiere JavaScript externe. Scripturile au un tip de suport de internet care este utilizat ca:
Tipul de conținut al fișierelor JavaScript livrate prin intermediul unui server web.
Valoarea atributului typede <script>elemente. Rețineți că, pentru HTML5, recomandarea este să
omiteți typeatributul în <script>elemente, dacă acestea conțin sau se referă la JavaScript.
Scripturile sunt în mod normal încărcate sau executate sincron. Firul JavaScript se oprește până
când codul a fost încărcat sau executat.
16.6.1.2 Module
Pentru a fi în concordanță cu obișnuita semantică de execuție-finalizare JavaScript, corpul unui
modul trebuie executat fără întrerupere. Aceasta lasă două opțiuni pentru importul modulelor:
Încărcați modulele în mod sincron, în timp ce corpul este executat. Asta face Node.js.
Încărcați toate modulele în mod asincron, înainte de executarea corpului. Așa se gestionează
modulele AMD. Este cea mai bună opțiune pentru browsere, deoarece modulele sunt încărcate
pe internet și execuția nu trebuie să se întrerupă în timp ce sunt. Ca beneficiu suplimentar,
această abordare permite încărcarea simultană a mai multor module.
ECMAScript 6 vă oferă cele mai bune din ambele lumi: Sintaxa sincronă a Node.js plus încărcarea
asincronă a AMD. Pentru a face posibilele două, modulele ES6 sunt sintactic mai puțin flexibile
decât modulele Node.js: Importurile și exporturile trebuie să se întâmple la nivelul superior. Asta
înseamnă că nici ele nu pot fi condiționate. Această restricție permite unui încărcător de module
ES6 să analizeze static ce module sunt importate de un modul și să le încarce înainte de a-și
executa corpul.
Natura sincronă a scripturilor îi împiedică să devină module. Scripturile nu pot importa modulele
în mod declarativ (trebuie să folosiți API-ul de încărcare a modulelor programatice dacă doriți să
faceți acest lucru).
Modulele pot fi utilizate din browsere printr-o nouă variantă a <script>elementului care este
complet asincron:
<script type="module">
import $ from 'lib/jquery';
var x = 123;
// `this` is undefined
</script>
După cum puteți vedea, elementul are propriul său domeniu de aplicare și variabilele „din
interior” sunt locale pentru acel domeniu. Rețineți că codul modulului este implicit în modul
strict. Aceasta este o veste grozavă - nu mai mult 'use strict'.
Avantajul <script>suportării modulelor în HTML printr-un tip personalizat este faptul că este ușor
de adus acel suport la motoarele mai vechi printr-o polifilare (o bibliotecă). În cele din urmă
poate exista sau nu un element dedicat pentru module (de exemplu <module>).
Semantica acestei bucăți de cod diferă în funcție de faptul dacă este interpretată ca un modul
sau ca un script:
Un exemplu mai realist este un modul care instalează ceva, de exemplu, o polifilare în variabile
globale sau un ascultător de evenimente globale. Un astfel de modul nu importă și nici nu
exportă nimic și este activat printr-un import gol:
import './my_module';
var counter = 3;
function incCounter() {
counter++;
module.exports = {
incCounter: incCounter,
};
console.log(counter); // 3
incCounter();
console.log(counter); // 3
counter++;
console.log(counter); // 4
Dacă accesați valoarea prin intermediul obiectului de export, aceasta este încă copiată o dată, la
export:
console.log(lib.counter); // 3
lib.incCounter();
console.log(lib.counter); // 3
lib.counter++;
console.log(lib.counter); // 4
Proprietățile unui obiect modul foo( import * as foo from 'foo') sunt ca proprietățile unui obiect
înghețat .
counter++;
console.log(counter); // 3
incCounter();
console.log(counter); // 4
counter++; // TypeError
Dacă importați obiectul modulului prin asterisc ( *), obțineți aceleași rezultate:
console.log(lib.counter); // 3
lib.incCounter();
console.log(lib.counter); // 4
Rețineți că, în timp ce nu puteți modifica valorile importurilor, puteți modifica obiectele la care
fac referire. De exemplu:
obj.prop = 123; // OK
Dependențe ciclice: avantajul principal este că acceptă dependențe ciclice chiar și pentru
importuri necalificate.
Puteți împărți codul în mai multe module și va continua să funcționeze (atât timp cât nu încercați
să modificați valorile importurilor).
Pe partea flip, plierea modulului , combinarea mai multor module într-un singur modul devine,
de asemenea, mai simplă.
În experiența mea, importurile ES6 funcționează doar, rar trebuie să te gândești la ce se întâmplă
sub capotă.
16.7.3 Implementarea punctelor de vedere
Cum funcționează importurile ca vederi ale exporturilor sub capotă? Exporturile sunt gestionate
prin înregistrarea de export a structurii de date . Toate intrările la export (cu excepția celor
pentru reexport) au următoarele două nume:
Nume local: este numele sub care exportul este stocat în modul.
Numele exportului: este numele pe care trebuie să-l folosească modulele de import pentru a
accesa exportul.
După ce ați importat o entitate, acea entitate este accesată întotdeauna printr-un indicator care
are modulul celor două componente și numele local . Cu alte cuvinte, acel pointer se referă la o
legare (spațiul de stocare a unei variabile) în interiorul unui modul.
Să examinăm numele exporturilor și numele locale create de diverse tipuri de export. Următorul
tabel ( adaptat din spec. ES6 ) oferă o imagine de ansamblu, secțiunile ulterioare au mai multe
detalii.
export { foo };
function foo() {}
function foo() {}
export { foo };
Numele local a fost ales astfel încât să nu se ciocnească cu niciun alt nume local.
Rețineți că un export implicit duce la crearea unei legături. Dar, din cauza faptului că *default*nu
este un identificator legal, nu puteți accesa această legătură din interiorul modulului.
function foo() {}
Numele sunt:
Nume local: foo
Asta înseamnă că puteți modifica valoarea exportului implicit din interiorul modulului, alocând o
altă valoare foo.
(Numai) pentru exporturile implicite, puteți omite și numele unei declarații de funcție:
Numele sunt:
Gestionarea importurilor:
Numele de export și numele locale create de diferitele tipuri de export sunt prezentate în tabelul
42 din secțiunea „ Înregistrări ale modulului text sursă ”. Secțiunea „ Static Semantics:
ExportEntries ” are mai multe detalii. Puteți vedea că intrările de export sunt configurate static
(înainte de a evalua modulul), evaluarea declarațiilor de export este descrisă în secțiunea
„ Runtime Semantics: Evaluare ”.
ECMAScript 6 favorizează stilul de export unic / implicit și oferă cea mai dulce sintaxă la importul
implicit. Importul exporturilor numite poate și chiar ar trebui să fie puțin mai puțin concis.
Următoarele sunt două exemple de module CommonJS care nu au o structură statică. În primul
exemplu, trebuie să rulați codul pentru a afla ce importă:
var my_lib;
if (Math.random()) {
my_lib = require('foo');
} else {
my_lib = require('bar');
if (Math.random()) {
exports.baz = ···;
Modulele ECMAScript 6 sunt mai puțin flexibile și te obligă să fii static. Drept urmare, obțineți
mai multe beneficii, care sunt descrise în continuare.
Pentru implementare, aceste module sunt incluse în câteva fișiere, relativ mari.
Comprimarea fișierului pachet este puțin mai eficientă decât comprimarea fișierelor separate.
În timpul grupării, exporturile neutilizate pot fi eliminate, ceea ce poate duce la economii de
spațiu semnificative.
Motivul nr. 1 este important pentru HTTP / 1, unde costul pentru solicitarea unui fișier este
relativ mare. Acest lucru se va schimba cu HTTP / 2, motiv pentru care acest motiv nu contează
acolo.
Motivul nr. 3 va rămâne convingător. Acesta poate fi realizat numai cu un format de modul care
are o structură statică.
Structura lor statică înseamnă că formatul pachetului nu trebuie să țină cont de module
încărcate condiționat (o tehnică obișnuită pentru a face acest lucru este introducerea codului
modulului în funcții).
Importurile fiind afișate numai în citire despre exporturi înseamnă că nu trebuie să copiați
exporturile, puteți să le faceți referire direct la acestea.
// main.js
console.log(foo());
Rollup-ul poate grupa aceste două module ES6 în următorul modul ES6 unic (rețineți exportul
neutilizat neutilizat bar):
function foo() {}
console.log(foo());
Un alt avantaj al abordării Rollup este că pachetul nu are un format personalizat, ci este doar un
modul ES6.
În schimb, dacă importați o bibliotecă în ES6, cunoașteți static conținutul acesteia și puteți
optimiza accesele:
import * as lib from 'lib';
Variabile globale: din ce în ce, singurele variabile complet globale vor proveni din limbajul
propriu-zis. Toate celelalte vor proveni din module (inclusiv funcționalitate din biblioteca
standard și browser). Adică cunoașteți static toate variabilele globale.
Acest lucru ajută enorm la verificarea dacă un anumit identificator a fost scris corect. Acest tip de
verificare este o caracteristică populară a garniturilor precum JSLint și JSHint; în ECMAScript 6,
cea mai mare parte a acestuia poate fi efectuată de motoare JavaScript.
În plus, orice acces al importurilor numite (cum ar fi lib.foo) poate fi, de asemenea, verificat
static.
macro class {
rule {
$className {
} => {
$($className.prototype.$mname
class Person {
constructor(name) {
this.name = name;
say(msg) {
Tipurile sunt atrăgătoare, deoarece permit dialectele rapide de tip JavaScript, în care poate fi
scris codul critic pentru performanță. Un astfel de dialect este JavaScript de nivel scăzut (LLJS).
Dependențele ciclice nu sunt în mod inerent rele. Mai ales pentru obiecte, uneori chiar îți dorești
acest tip de dependență. De exemplu, în unii arbori (cum ar fi documentele DOM), părinții se
referă la copii și copiii se referă înapoi la părinți. În biblioteci, puteți evita de obicei dependențele
ciclice printr-un design atent. Cu toate acestea, într-un sistem mare, acestea se pot întâmpla, mai
ales în timpul refactorizării. Atunci este foarte util dacă un sistem de module le acceptă,
deoarece sistemul nu se sparge în timp ce refactorizați.
Documentația Node.js recunoaște importanța dependențelor ciclice și Rob Sayre oferă dovezi
suplimentare :
Punct de date: Am implementat o dată un sistem precum [module ECMAScript 6] pentru Firefox.
M - am cerut sprijin de dependență ciclică de 3 săptămâni după transport maritim.
Sistemul pe care l-am inventat Alex Fritze și pe care am lucrat nu este perfect, iar sintaxa nu este
foarte drăguță. Dar încă se obișnuiește 7 ani mai târziu, așa că trebuie să fi obținut ceva corect.
System.import(moduleSpecifier)
.then(the_module => {
// Use the_module
})
if (Math.random()) {
System.import('some_module')
.then(some_module => {
// Use some_module
})
// Illegal syntax:
// Illegal syntax:
Structura modulului static (care ajută la eliminarea codului mort, optimizări, verificare statică și
multe altele)
De asemenea, modulele ES6 vor pune capăt fragmentării dintre standardele actuale dominante
CommonJS și AMD. A avea un singur standard nativ pentru module înseamnă:
Nu mai există UMD ( definiția modulului universal ): UMD este un nume pentru tiparele care
permit același fișier să fie utilizat de mai multe sisteme de module (de exemplu, CommonJS și
AMD). Odată ce ES6 este singurul modul standard, UMD devine învechită.
Noile API-uri ale browserului devin module în loc de variabile sau proprietăți globale ale
navigator.