Programmering

Kod i JavaScript på den smarte, modulære måten

Noen mennesker fremdeles virker overrasket over at JavaScript blir sett på som et respektabelt, voksen programmeringsspråk for seriøse applikasjoner. Faktisk har JavaScript-utvikling modnet pent i årevis, med den beste fremgangsmåten for modulær utvikling som et godt eksempel.

Fordelene med å skrive modulkode er godt dokumentert: større vedlikeholdsevne, unngå monolitiske filer og frakopling av kode til enheter som kan testes riktig. For de av dere som vil bli fanget raskt, her er de vanlige metodene som brukes av moderne JavaScript-utviklere for å skrive modularisert kode.

Modulmønster

La oss starte med et grunnleggende designmønster kalt modulmønsteret. Som du kanskje mistenker, gjør dette oss i stand til å skrive kode på en modulær måte, slik at vi kan beskytte utførelseskonteksten til gitte moduler og bare eksponere det vi vil eksponere globalt. Mønsteret ser ut som:

(funksjon(){

// 'privat' variabel

var orderId = 123;

// eksponere metoder og variabler ved å legge ved dem

// til det globale objektet

window.orderModule = {

getOrderId: function () {

// brakt til deg ved nedleggelser

returordre

}

};

})()

En anonymfunksjon uttrykk, som fungerer som en fabrikk i dette tilfellet, skrives og kalles umiddelbart. For hastighet kan du eksplisitt overføre variabler tilfunksjon samtale og effektivt gjenbinde disse variablene til et lokalt omfang. Du vil også se dette noen ganger som en defensiv manøver i biblioteker som støtter gamle nettlesere der visse verdier (som f.eksudefinert) er skrivbare egenskaper.

(funksjon (global, udefinert) {

// kode her kan få tilgang til det globale objektet raskt,

// og 'udefinert' er helt sikkert 'udefinert'

// MERK: I moderne nettlesere kan ikke 'udefinert' skrives,

// men det er verdt å huske det

// når du skriver kode for gamle nettlesere.

})(dette)

Dette er bare et designmønster. Med denne teknikken trenger du ikke å inkludere ekstra biblioteker for å skrive modulært JavaScript. Mangelen på avhengighet er et stort pluss (eller oppdragskritisk) i noen innstillinger, spesielt hvis du skriver et bibliotek. Du vil se at de fleste av de populære bibliotekene vil bruke dette mønsteret til å kapsle interne funksjoner og variabler, og bare utsette det som er nødvendig.

Hvis du skriver en søknad, er det imidlertid noen ulemper ved denne tilnærmingen. La oss si at du lager en modul som setter noen få metoder påvindu. bestillinger. Hvis du vil bruke disse metodene i andre deler av applikasjonen, må du sørge for at modulen er inkludert før du ringer til dem. Deretter i koden der du ringerwindow.orders.getOrderId, du skriver koden og håper det andre skriptet har lastet inn.

Dette ser kanskje ikke ut som verdens ende, men det kan raskt gå ut av kontroll på kompliserte prosjekter - og administrering av skripts inkluderingsrekkefølge blir vondt. Dessuten må alle filene dine lastes synkront, ellers vil du invitere løpsforhold til å bryte koden din. Hvis det bare var en måte å eksplisitt erklære modulene du ønsket å bruke for en gitt bit kode ....

AMD (definisjon av asynkron modul)

AMD ble født ut av behovet for å spesifisere eksplisitte avhengigheter mens man unngikk synkron lasting av alle skript. Det er enkelt å bruke i en nettleser, men er ikke opprinnelig, så du må inkludere et bibliotek som laster skript, som RequireJS eller curl.js. Slik ser det ut å definere en modul ved hjelp av AMD:

// libs / order-module.js

definere (funksjon () {

// 'privat' variabel

var orderId = 123;

// avsløre metoder og variabler ved å returnere dem

komme tilbake {

getOrderId: function () {

returordreId;

}

});

Det ser ut som det vi hadde å gjøre med før, bortsett fra at i stedet for å umiddelbart ringe fabrikkfunksjonen vår direkte, går vi som et argument tildefinere. Den virkelige magien begynner å skje når du vil bruke modulen senere:

define (['libs / order-module'], function (orderModule) {

orderModule.getOrderId (); // evaluerer til 123

});

Det første argumentet avdefinere er nå en rekke avhengigheter, som kan være vilkårlig lange, og fabrikkfunksjonen lister opp formelle parametere for de avhengighetene som skal knyttes til den. Nå kan noen avhengigheter du trenger ha egne avhengigheter, men med AMD trenger du ikke å vite det:

// src / utils.js

definere (['libs / understrek'), funksjon (_) {

komme tilbake {

moduleId: 'foo',

_ : _

});

// src / myapp.js

definere ([

'libs / jquery',

'libs / styr',

'src / utils'

], funksjon ($, styr, verktøy) {

// Bruk hver av de oppgitte avhengighetene uten

// bekymringsfull om de er der eller ikke.

$ ('div'). addClass ('bar');

// Underavhengigheter er også ivaretatt

Verktøy ._. Nøkler (vindu);

});

Dette er en fin måte å utvikle modulært JavaScript når du arbeider med mange bevegelige deler og avhengigheter. Ansvaret for å bestille og inkludere skript ligger nå på script-loaderens skuldre, slik at du bare kan oppgi hva du trenger og begynne å bruke det.

På den annen side er det noen potensielle problemer. Først har du et ekstra bibliotek å inkludere og lære å bruke. Jeg har ingen erfaring med curl.js, men RequireJS innebærer å lære å sette opp konfigurasjon for prosjektet ditt. Dette vil ta noen timer å bli kjent med innstillingene, og deretter tar det bare minutter å skrive den opprinnelige konfigurasjonen. Også moduldefinisjoner kan bli lange hvis de fører til en haug med avhengigheter. Her er et eksempel hentet fra forklaringen på dette problemet i RequireJS-dokumentasjonen:

// Fra RequireJS-dokumentasjon:

// //requirejs.org/docs/whyamd.html#sukker

definere (["krever", "jquery", "blad / objekt", "blad / fn", "rdapi",

"oauth", "blade / jig", "blade / url", "dispatch", "accounts",

"lagring", "tjenester", "widgets / AccountPanel", "widgets / TabButton",

"widgets / AddAccount", "less", "osTheme", "jquery-ui-1.8.7.min",

"jquery.textOverflow"],

funksjon (krever, $, objekt, fn, rdapi,

oauth, jig, url, forsendelse, kontoer,

lagring, tjenester, AccountPanel, TabButton,

AddAccount, less, osTheme) {

});

Au! RequireJS gir litt syntaktisk sukker for å takle dette, som ser ganske ut som et annet populært API for modulær utvikling, CommonJS.

CJS (CommonJS)

Hvis du noen gang har skrevet JavaScript på serversiden ved hjelp av Node.js, har du brukt CommonJS-moduler. Hver fil du skriver er ikke pakket inn i noe fancy, men har tilgang til en variabel som hetereksport som du kan tildele alt du vil bli utsatt for av modulen. Slik ser det ut:

// en 'privat' variabel

var orderId = 123;

exports.getOrderId = funksjon () {

returordre

};

Så når du vil bruke modulen, erklærer du den innebygd:

// orderModule får verdien av 'eksport'

var orderModule = krever ('./ order-module');

orderModule.getOrderId (); // evaluerer til 123

Syntaktisk har dette alltid sett bedre ut for meg, hovedsakelig fordi det ikke involverer den bortkastede fordypningen som er tilstede i de andre alternativene vi har diskutert. På den annen side skiller den seg sterkt fra de andre ved at den er designet for synkron lasting av avhengigheter. Dette er mer fornuftig på serveren, men vil ikke gjøre i frontenden. Synkron avhengighetslasting betyr lengre sidetid, noe som er uakseptabelt for Internett. Mens CJS er uten tvil min favoritt-utseende modul-syntaks, får jeg bare bruke den på serveren (og mens jeg skriver mobilapper med Appcelerators Titanium Studio).

En som styrer dem alle

Det nåværende utkastet til den sjette utgaven av ECMAScript (ES6), spesifikasjonen som JavaScript er implementert fra, legger til innfødt støtte for moduler. Spesifikasjonen er fremdeles i utkastform, men det er verdt å ta en titt på hvordan fremtiden kan se ut for modulær utvikling. Ganske mange nye nøkkelord brukes i ES6-spesifikasjonen (også kjent som Harmony), hvorav flere brukes med moduler:

// libs / order-module.js

var orderId = 123;

eksporter var getOrderId = funksjon () {

returordre

};

Du kan ringe det senere på flere måter:

importer {getOrderId} fra "libs / order-module";

getOrderId ();

Ovenfor velger du hvilken eksport i en modul du vil binde til lokale variabler. Alternativt kan du importere hele modulen som om det var et objekt (ligner påeksport objekt i CJS-moduler):

importer "libs / order-module" som orderModule;

orderModule.getOrderId ();

Det er mye mer med ES6-moduler (sjekk ut Dr. Axel Rauschmayers 2ality-blogg for mer), men eksemplet skal vise deg noen få ting. For det første har vi en syntaks som ligner på CJS-moduler, noe som betyr at ekstra innrykk ikke er noe å finne, selv om det ikke alltid er tilfelle, som du finner i 2ality-lenken ovenfor. Det som ikke er åpenbart ved å se på eksemplet, spesielt fordi det ser så mye ut som CJS, er at modulene som er oppført i importuttalelsen, lastes asynkront.

Sluttresultatet er den lettleste syntaksen til CJS blandet med den asynkrone naturen til AMD. Dessverre vil det ta en stund før disse støttes fullt ut i alle ofte målrettede nettlesere. Når det er sagt, har "en stund" blitt kortere og kortere ettersom nettleserleverandører strammer utgivelsessyklusene.

I dag er en kombinasjon av disse verktøyene veien å gå når det gjelder modulær JavaScript-utvikling. Alt avhenger av hva du gjør. Hvis du skriver et bibliotek, bruk moduldesignmønsteret. Hvis du bygger applikasjoner for nettleseren, kan du bruke AMD-moduler med en skriptlaster. Hvis du er på serveren, kan du dra nytte av CJS-moduler. Til slutt vil selvfølgelig ES6 støttes over hele linja - på hvilket tidspunkt du kan gjøre ting på ES6-måten og skrote resten!

Spørsmål eller tanker? Legg gjerne igjen en melding nedenfor i kommentarfeltet eller ta kontakt med meg på Twitter, @ freethejazz.

$config[zx-auto] not found$config[zx-overlay] not found