Tweet
Modularitet Vigtigheden af at afkoble din applikation
Når vi siger, at en applikation er modulær, mener vi generelt, at den består af et sæt meget afkoblede, forskellige dele af funktionalitet, der er gemt i moduler. Som du sikkert ved, gør løs kobling det nemmere at vedligeholde programmer ved at fjerne afhængigheder, hvor det er muligt. Når dette implementeres effektivt, er det ret nemt at se, hvordan ændringer i en del af et system kan påvirke en anden del af et system.
I modsætning til nogle mere traditionelle programmeringssprog giver den nuværende iteration af JavaScript (ECMA-262) imidlertid ikke udviklerne mulighed for at importere sådanne moduler af kode på en ren og organiseret måde. Det er et af de problemer med specifikationer, der ikke har krævet store overvejelser før de senere år, hvor behovet for mere organiserede JavaScript-applikationer blev tydeligt.
I stedet må udviklerne på nuværende tidspunkt falde tilbage på variationer af modul- eller objektlitterære mønstre. Med mange af disse er modulskripter sat sammen i DOM med navnerum, der beskrives af et enkelt globalt objekt, hvor det stadig er muligt at få navngivningskollisioner i din arkitektur. Der er heller ikke nogen ren måde at håndtere afhængighedsstyring på uden en vis manuel indsats eller tredjepartsværktøjer.
Mens native løsninger på disse problemer vil ankomme i ES Harmony, er den gode nyhed, at det aldrig har været nemmere at skrive modulært JavaScript, og du kan begynde at gøre det i dag.
I denne artikel vil vi se på tre formater til at skrive modulært JavaScript: AMD, CommonJS og forslag til den næste version af JavaScript, Harmony.
Prelude En bemærkning om scriptloadere
Det er svært at diskutere AMD- og CommonJS-moduler uden at tale om elefanten i rummet – scriptloadere. På nuværende tidspunkt er script loading et middel til at nå et mål, og dette mål er modulopbygget JavaScript, der kan bruges i applikationer i dag – for at opnå dette er det desværre nødvendigt at bruge en kompatibel script loader. For at få mest muligt ud af denne artikel anbefaler jeg, at du får en grundlæggende forståelse af, hvordan populære script loading-værktøjer fungerer, så forklaringerne om modulformater giver mening i en sammenhæng.
Der findes en række gode loadere til håndtering af modulindlæsning i AMD- og CJS-formater, men mine personlige præferencer er RequireJS og curl.js. Komplette vejledninger om disse værktøjer ligger uden for rammerne af denne artikel, men jeg kan anbefale at læse John Hann’s indlæg om curl.js og James Burke’s RequireJS API-dokumentation for at få mere at vide.
Fra et produktionsperspektiv anbefales det at bruge optimeringsværktøjer (som RequireJS optimizer) til at sammenkæde scripts til udrulning, når der arbejdes med sådanne moduler. Interessant nok behøver RequireJS med Almond AMD shim ikke at blive rullet i det implementerede websted, og det, som du måske betragter som en script loader, kan nemt flyttes uden for udvikling.
Det sagt ville James Burke nok sige, at det at kunne indlæse scripts dynamisk efter sideindlæsning stadig har sine anvendelsesmuligheder, og RequireJS kan også hjælpe med dette. Med disse bemærkninger i baghovedet kan vi komme i gang.
AMD Et format til at skrive modulært JavaScript i browseren
Det overordnede mål for AMD-formatet (Asynchronous Module Definition) er at give en løsning til modulært JavaScript, som udviklere kan bruge i dag. Det blev født ud af Dojos erfaringer fra den virkelige verden med XHR+eval, og fortalerne for dette format ønskede at undgå, at fremtidige løsninger lider under svaghederne ved de tidligere løsninger.
Det egentlige AMD-modulformat er et forslag til definition af moduler, hvor både modulet og afhængigheder kan indlæses asynkront. Det har en række klare fordele, herunder at det både er asynkront og meget fleksibelt af natur, hvilket fjerner den tætte kobling, som man normalt finder mellem kode og modulidentitet. Mange udviklere nyder at bruge det, og man kan betragte det som et pålideligt springbræt til det modulsystem, der er foreslået for ES Harmony.
AMD begyndte som et udkast til specifikation for et modulformat på CommonJS-listen, men da det ikke lykkedes at opnå fuld enighed, flyttede den videre udvikling af formatet til amdjs-gruppen.
I dag er det omfavnet af projekter som Dojo (1.7), MooTools (2.0), Firebug (1.8) og endda jQuery (1.7). Selvom udtrykket CommonJS AMD-formatet er blevet set i naturen ved lejlighed, er det bedst at henvise til det som bare AMD eller Async Module support, da ikke alle deltagere på CJS-listen ønskede at forfølge det.
Gå i gang med moduler
De to nøglebegreber, som du skal være opmærksom på her, er ideen om en define
metode til at lette moduldefinitionen og en require
metode til håndtering af indlæsning af afhængigheder. define bruges til at definere navngivne eller unavngivne moduler baseret på forslaget ved hjælp af følgende signatur:
Som du kan se af inline-kommentarerne, er module_id
et valgfrit argument, som typisk kun er påkrævet, når der anvendes ikke-AMD-konkateneringsværktøjer (der kan være nogle andre kanttilfælde, hvor det også er nyttigt). Når dette argument udelades, kalder vi modulet anonymt.
Når vi arbejder med anonyme moduler, er ideen om et moduls identitet DRY, hvilket gør det trivielt at undgå duplikering af filnavne og kode. Da koden er mere bærbar, kan den nemt flyttes til andre steder (eller rundt i filsystemet) uden at skulle ændre selve koden eller ændre dens ID. module_id
svarer til mappestier i simple pakker og når de ikke anvendes i pakker. Udviklere kan også køre den samme kode på flere miljøer blot ved at bruge en AMD-optimering, der fungerer med et CommonJS-miljø som f.eks. r.js.
Tilbage til define-signaturen repræsenterer dependencies-argumentet et array af afhængigheder, som er nødvendige for det modul, du definerer, og det tredje argument (“definitionsfunktion”) er en funktion, der udføres for at instantiere dit modul. Et barebone-modul kunne defineres på følgende måde:
Forståelse af AMD: define()
require på den anden side bruges typisk til at indlæse kode i en JavaScript-fil på øverste niveau eller i et modul, hvis du ønsker at hente afhængigheder dynamisk. Et eksempel på brugen af den er:
Forståelse af AMD: require()
Dynamisk indlæste afhængigheder
Forståelse af AMD: plugins
Det følgende er et eksempel på definition af et AMD-kompatibelt plugin:
Indlæsning af AMD-moduler ved hjælp af require.js
Ladning af AMD-moduler ved hjælp af curl.js
Moduler med udskudte afhængigheder
Hvorfor er AMD et bedre valg til at skrive modulært JavaScript?
- Giver et klart forslag til, hvordan man skal gribe definitionen af fleksible moduler an.
- Væsentligt renere end de nuværende globale namespace- og
<script>
-tag-løsninger, som mange af os er afhængige af. Der er en ren måde at deklarere selvstændige moduler og de afhængigheder, de måtte have. - Moduldefinitioner er indkapslet, hvilket hjælper os med at undgå forurening af det globale namespace.
- Fungerer bedre end nogle alternative løsninger (f.eks. CommonJS, som vi vil se på om kort tid). Har ikke problemer med cross-domain, lokal eller debugging og er ikke afhængig af, at der skal bruges server-side værktøjer. De fleste AMD-loader understøtter indlæsning af moduler i browseren uden en byggeproces.
- Giver en “transport”-tilgang til at inkludere flere moduler i en enkelt fil. Andre tilgange som CommonJS er endnu ikke blevet enige om et transportformat.
- Det er muligt at lazy-loade scripts, hvis dette er nødvendigt.
AMD-moduler med Dojo
Det er ret ligetil at definere AMD-kompatible moduler ved hjælp af Dojo. Som ovenfor skal du definere eventuelle modulafhængigheder i et array som det første argument og angive en callback (factory), som vil udføre modulet, når afhængighederne er blevet indlæst. e.g:
define(, function( Tooltip ){ //Our dijit tooltip is now available for local use new Tooltip(...);});
Bemærk modulets anonyme karakter, som nu både kan forbruges af en Dojo asynkron loader, RequireJS eller standardmodulloaderen dojo.require(), som du måske er vant til at bruge.
For dem, der undrer sig over modulhenvisninger, er der nogle interessante gotchas, som det er nyttigt at kende her. Selv om den af AMD anbefalede måde at henvise til moduler på deklarerer dem i afhængighedslisten med et sæt matchende argumenter, understøttes dette ikke af Dojo 1.6 build-systemet – det fungerer egentlig kun for AMD-kompatible loadere. e.g:
define(, function( cookie, Tooltip ){ var cookieValue = cookie("cookieName"); new Tree(...); });
Dette har mange fremskridt i forhold til nested namespacing, da moduler ikke længere behøver at referere direkte til komplette namespaces hver gang – alt hvad vi kræver er stien ‘dojo/cookie’ i afhængigheder, som, når den er aliaset til et argument, kan refereres af denne variabel. Dette fjerner behovet for gentagne gange at skrive ‘dojo.’ i dine programmer.
Den sidste gotcha, som du skal være opmærksom på, er, at hvis du fortsat ønsker at bruge Dojo build-systemet eller ønsker at migrere ældre moduler til denne nyere AMD-stil, muliggør den følgende mere mundrette version en lettere migration. Bemærk, at dojo og dijit og også refereres som afhængigheder:
AMD Module Design Patterns (Dojo)
Hvis du har fulgt nogle af mine tidligere indlæg om fordelene ved designmønstre, vil du vide, at de kan være meget effektive til at forbedre den måde, vi griber strukturering af løsninger på almindelige udviklingsproblemer an på. John Hann gav for nylig en fremragende præsentation om AMD-modul-designmønstre, der dækker Singleton, Decorator, Mediator og andre. Jeg kan varmt anbefale at tjekke hans slides, hvis du får mulighed for det.
Nogle eksempler på disse mønstre kan findes nedenfor:
Decorator-mønster:
Adaptermønster
AMD Modules With jQuery
The Basics
I modsætning til Dojo leveres jQuery egentlig kun med én fil, men i betragtning af bibliotekets plugin-baserede karakter kan vi demonstrere, hvor ligetil det er at definere et AMD-modul, der bruger det nedenfor.
Der mangler dog noget i dette eksempel, og det er begrebet registrering.
Registrering af jQuery som et asynkront modul
En af de vigtigste funktioner, der landede i jQuery 1.7, var støtte til registrering af jQuery som et asynkront modul. Der findes en række kompatible script loaders (herunder RequireJS og curl), som er i stand til at indlæse moduler ved hjælp af et asynkront modulformat, og det betyder, at der kræves færre hacks for at få tingene til at fungere.
Som følge af jQuerys popularitet skal AMD loaders tage hensyn til flere versioner af biblioteket, der indlæses i den samme side, da man ideelt set ikke ønsker, at flere forskellige versioner indlæses på samme tid. Læssere har mulighed for enten specifikt at tage hensyn til dette problem eller at instruere deres brugere om, at der er kendte problemer med tredjepartsskripter og deres biblioteker.
Hvad tilføjelsen 1.7 bringer til bordet er, at den hjælper med at undgå problemer med anden tredjepartskode på en side, der ved et uheld indlæser en version af jQuery på siden, som ejeren ikke havde forventet. Man ønsker ikke, at andre instanser skal ødelægge ens egen, og derfor kan dette være en fordel.
Den måde, det fungerer på, er, at den anvendte script loader angiver, at den understøtter flere jQuery-versioner ved at angive, at en egenskab, define.amd.jQuery
er lig med true. For dem, der er interesseret i mere specifikke implementeringsdetaljer, registrerer vi jQuery som et navngivet modul, da der er en risiko for, at det kan sammenkædes med andre filer, som måske bruger AMD’s define()
-metode, men ikke bruger et korrekt sammenkædningsscript, der forstår anonyme AMD-moduldefinitioner.
Den navngivne AMD giver en sikkerhedstæppe af at være både robust og sikker for de fleste brugssager.
Smartere jQuery-plugins
Jeg har for nylig diskuteret nogle ideer og eksempler på, hvordan jQuery-plugins kunne skrives ved hjælp af Universal Module Definition (UMD)-mønstre her. UMD’er definerer moduler, der kan fungere på både klient og server samt med alle populære script loaders, der er tilgængelige i øjeblikket. Selv om dette stadig er et nyt område med mange koncepter, der stadig er ved at blive færdiggjort, er du velkommen til at kigge på kodeeksemplerne i afsnittets titel AMD && CommonJS nedenfor, og lad mig vide, hvis du mener, at der er noget, vi kunne gøre bedre.
Hvilke Script Loaders & Frameworks understøtter AMD?
In-browser:
Server-side:
- RequireJS http://requirejs.org
- PINF http://github.com/pinf/loader-js
AMD Konklusioner
Ovenstående er meget trivielle eksempler på, hvor nyttige AMD-moduler virkelig kan være, men de giver forhåbentlig et grundlag for at forstå, hvordan de fungerer.
Du vil måske være interesseret i at vide, at mange synlige store applikationer og virksomheder i øjeblikket bruger AMD-moduler som en del af deres arkitektur. Disse omfatter IBM og BBC iPlayer, hvilket understreger, hvor alvorligt dette format overvejes af udviklere på virksomhedsniveau.
For flere grunde til, hvorfor mange udviklere vælger at bruge AMD-moduler i deres applikationer, vil du måske være interesseret i dette indlæg af James Burke.
CommonJS Et modulformat optimeret til serveren
CommonJS er en frivillig arbejdsgruppe, der har til formål at designe, prototype og standardisere JavaScript-API’er. Til dato har de forsøgt at ratificere standarder for både moduler og pakker. CommonJS-modulforslaget specificerer et simpelt API til at deklarere moduler server-side og forsøger i modsætning til AMD at dække et bredere sæt af bekymringer såsom io, filsystem, promises og meget mere.
Gå i gang
Fra et strukturelt perspektiv er et CJS-modul et genanvendeligt stykke JavaScript, som eksporterer specifikke objekter, der stilles til rådighed for enhver afhængig kode – der er typisk ingen funktionsindpakninger omkring sådanne moduler (så du vil f.eks. ikke se define
brugt her).
På et højt niveau indeholder de grundlæggende to primære dele: en fri variabel ved navn exports
, som indeholder de objekter, som et modul ønsker at gøre tilgængelige for andre moduler, og en require
-funktion, som moduler kan bruge til at importere andre moduls eksport af andre moduler.
Forståelse af CJS: require() og exports
Grundlæggende forbrug af exports
AMD-ækvivalent af det første CJS-eksempel
Forbrug af flere afhængigheder
app.js
bar.js
exports.name = 'bar';
foo.js
require('./bar');exports.helloWorld = function(){ return 'Hello World!!''}
Hvilke loaders & Frameworks understøtter CJS?
In-browser:
- curl.js http://github.com/unscriptable/curl
- SproutCore 1.1 http://sproutcore.com
- PINF http://github.com/pinf/loader-js
- (og flere)
Server-side:
Er CJS velegnet til browseren?
Der er udviklere, der mener, at CommonJS er bedre egnet til udvikling på serversiden, hvilket er en af grundene til, at der i øjeblikket er en vis uenighed om, hvilket format der skal og vil blive brugt som de facto-standard i præ-Harmony-alderen fremadrettet. Nogle af argumenterne mod CJS omfatter en bemærkning om, at mange CommonJS API’er adresserer serverorienterede funktioner, som man simpelthen ikke ville være i stand til at implementere på browserniveau i JavaScript – f.eks. kunne io, system og js betragtes som uimplementerbare på grund af karakteren af deres funktionalitet.
Dette sagt er det nyttigt at vide, hvordan CJS-moduler skal struktureres uanset, så vi bedre kan vurdere, hvordan de passer ind, når vi definerer moduler, der kan bruges overalt. Moduler, som har anvendelse på både klient og server, omfatter validering, konvertering og templating engines. Den måde, som nogle udviklere griber valget af format an på, er ved at vælge CJS, når et modul kan bruges i et server-side miljø, og ved at bruge AMD, hvis dette ikke er tilfældet.
Da AMD-moduler er i stand til at bruge plugins og kan definere mere granulære ting som konstruktører og funktioner, giver det mening. CJS-moduler er kun i stand til at definere objekter, som kan være kedelige at arbejde med, hvis du forsøger at få konstruktører ud af dem.
Men selv om det ligger uden for rammerne af denne artikel, har du måske også bemærket, at der blev nævnt forskellige typer af “require”-metoder, da vi diskuterede AMD og CJS.
Problemet med en lignende navnekonvention er naturligvis forvirring, og fællesskabet er i øjeblikket splittet om fordelene ved en global require-funktion. John Hann’s forslag her er, at i stedet for at kalde den ‘require’, hvilket sandsynligvis ikke ville opfylde målet om at informere brugerne om forskellen mellem en global og en indre require, er det måske mere fornuftigt at omdøbe den globale loader-metode til noget andet (f.eks. navnet på biblioteket). Det er af denne grund, at en loader som curl.js bruger curl()
i stedet for require
.
AMD && CommonJS Konkurrerende, men lige gyldige standarder
Mens denne artikel har lagt mere vægt på at bruge AMD frem for CJS, er virkeligheden den, at begge formater er gyldige og har en anvendelse.
AMD antager en browser-første tilgang til udvikling og vælger asynkron adfærd og forenklet bagudkompatibilitet, men det har ikke noget koncept for File I/O. Den understøtter objekter, funktioner, konstruktører, strings, JSON og mange andre typer moduler, der kører nativt i browseren. Det er utroligt fleksibelt.
CommonJS har på den anden side en server-først-tilgang, der forudsætter synkron adfærd, ingen global bagage, som John Hann ville kalde det, og det forsøger at tage højde for fremtiden (på serveren). Det vi mener med dette er, at fordi CJS understøtter uindpakkede moduler, kan det føles lidt mere tæt på ES.next/Harmony-specifikationerne og frigøre dig fra define()
-omslaget, som AMD påtvinger. CJS-moduler understøtter dog kun objekter som moduler.
Men selv om tanken om endnu et modulformat kan virke skræmmende, kan du måske være interesseret i nogle eksempler på arbejde med hybride AMD/CJS-moduler og Univeral AMD/CJS-moduler.
Basis AMD Hybrid Format (John Hann)
AMD/CommonJS Universal Module Definition (Variation 2, UMDjs)
Extensible UMD Plugins With (Variation af mig selv og Thomas Davis).
core.js
myExtension.js
;(function ( name, definition ) { var theModule = definition(), hasDefine = typeof define === 'function', hasExports = typeof module !== 'undefined' && module.exports; if ( hasDefine ) { // AMD Module define(theModule); } else if ( hasExports ) { // Node.js Module module.exports = theModule; } else { // Assign to common namespaces or simply the global object (window) // account for for flat-file/global module extensions var obj = null; var namespaces = name.split("."); var scope = (this.jQuery || this.ender || this.$ || this); for (var i = 0; i app.js
$(function(){ // the plugin 'core' is exposed under a core namespace in // this example which we first cache var core = $.core; // use then use some of the built-in core functionality to // highlight all divs in the page yellow core.highlightAll(); // access the plugins (extensions) loaded into the 'plugin' // namespace of our core module: // Set the first div in the page to have a green background. core.plugin.setGreen("div:first"); // Here we're making use of the core's 'highlight' method // under the hood from a plugin loaded in after it // Set the last div to the 'errorColor' property defined in // our core module/plugin. If you review the code further down // you'll see how easy it is to consume properties and methods // between the core and other plugins core.plugin.setRed('div:last');});
ES Harmony Modules Of The Future
TC39, det standardiseringsorgan, der har til opgave at definere syntaksen og semantikken for ECMAScript og dets fremtidige iterationer, består af en række meget intelligente udviklere. Nogle af disse udviklere (f.eks. Alex Russell) har holdt nøje øje med udviklingen i brugen af JavaScript til udvikling i stor skala i de seneste år og er meget opmærksomme på behovet for bedre sprogfunktioner til at skrive mere modulær JS.
Der er derfor i øjeblikket forslag til en række spændende tilføjelser til sproget, herunder fleksible moduler, der kan fungere både på klienten og serveren, en modullæsser og meget mere. I dette afsnit vil jeg vise dig nogle kodeeksempler på syntaksen for moduler i ES.next, så du kan få en forsmag på, hvad der er på vej.
Bemærk: Selv om Harmony stadig er i forslagsfasen, kan du allerede nu afprøve (delvise) funktioner i ES.next, der omhandler native understøttelse af at skrive modulær JavaScript takket være Googles Traceur-kompiler. Du kan komme i gang med Traceur på under et minut ved at læse denne vejledning til at komme i gang. Der er også en JSConf-præsentation om det, som er værd at se, hvis du er interesseret i at lære mere om projektet.
Moduler med import og eksport
Hvis du har læst afsnittene om AMD- og CJS-moduler, er du måske bekendt med begrebet modulafhængigheder (import) og moduleksport (eller de offentlige API/variabler, som vi tillader andre moduler at forbruge). I ES.next er disse begreber blevet foreslået på en lidt mere kortfattet måde, idet afhængigheder angives ved hjælp af et import
nøgleord. export
er ikke meget anderledes end det, vi kunne forvente, og jeg tror, at mange udviklere vil se på nedenstående kode og straks "forstå" det.
- importdeklarationer binder et moduls eksport som lokale variabler og kan omdøbes for at undgå navnekollisioner/konflikter.
- eksportdeklarationer erklærer, at en lokal binding af et modul er eksternt synlig, således at andre moduler kan læse eksporten, men ikke kan ændre den. Interessant nok kan moduler eksportere underordnede moduler, men de kan ikke eksportere moduler, der er defineret andetsteds. Du kan også omdøbe eksporten, så deres eksterne navn adskiller sig fra deres lokale navn.
Moduler indlæst fra eksterne kilder
app.js
$(function(){ // the plugin 'core' is exposed under a core namespace in // this example which we first cache var core = $.core; // use then use some of the built-in core functionality to // highlight all divs in the page yellow core.highlightAll(); // access the plugins (extensions) loaded into the 'plugin' // namespace of our core module: // Set the first div in the page to have a green background. core.plugin.setGreen("div:first"); // Here we're making use of the core's 'highlight' method // under the hood from a plugin loaded in after it // Set the last div to the 'errorColor' property defined in // our core module/plugin. If you review the code further down // you'll see how easy it is to consume properties and methods // between the core and other plugins core.plugin.setRed('div:last');});
ES Harmony Modules Of The Future
TC39, det standardiseringsorgan, der har til opgave at definere syntaksen og semantikken for ECMAScript og dets fremtidige iterationer, består af en række meget intelligente udviklere. Nogle af disse udviklere (f.eks. Alex Russell) har holdt nøje øje med udviklingen i brugen af JavaScript til udvikling i stor skala i de seneste år og er meget opmærksomme på behovet for bedre sprogfunktioner til at skrive mere modulær JS.
Der er derfor i øjeblikket forslag til en række spændende tilføjelser til sproget, herunder fleksible moduler, der kan fungere både på klienten og serveren, en modullæsser og meget mere. I dette afsnit vil jeg vise dig nogle kodeeksempler på syntaksen for moduler i ES.next, så du kan få en forsmag på, hvad der er på vej.
Moduler med import og eksport
Hvis du har læst afsnittene om AMD- og CJS-moduler, er du måske bekendt med begrebet modulafhængigheder (import) og moduleksport (eller de offentlige API/variabler, som vi tillader andre moduler at forbruge). I ES.next er disse begreber blevet foreslået på en lidt mere kortfattet måde, idet afhængigheder angives ved hjælp af et import
nøgleord. export
er ikke meget anderledes end det, vi kunne forvente, og jeg tror, at mange udviklere vil se på nedenstående kode og straks "forstå" det.
Modulforslagene tager også højde for moduler, der er eksternt baserede (f.eks. en API-wrapper fra en tredjepart), hvilket gør det forenklet at indlæse moduler fra eksterne steder. Her er et eksempel, hvor vi trækker det modul, vi definerede ovenfor, ind og bruger det:
Module Loader API
Den foreslåede module loader beskriver et dynamisk API til indlæsning af moduler i stærkt kontrollerede sammenhænge. Signaturer, der understøttes på loader, omfatter load( url, moduleInstance, error)
til indlæsning af moduler, createModule( object, globalModuleReferences)
og andre. Her er et andet eksempel, hvor vi dynamisk indlæser det modul, som vi oprindeligt definerede. Bemærk, at i modsætning til det sidste eksempel, hvor vi trak et modul ind fra en fjernkilde, er modullader-API’et bedre egnet til dynamiske kontekster.
Loader.load('http://addyosmani.com/factory/cakes.js', function(cakeFactory){ cakeFactory.oven.makeCupcake('chocolate'); });
CommonJS-lignende moduler til serveren
For udviklere, der er serverorienterede, er det modulsystem, der foreslås for ES.next, ikke kun begrænset til at se på moduler i browseren. Nedenfor kan du som eksempler se et CJS-lignende modul, der er foreslået til brug på serveren:
// io/File.jsexport function open(path) { ... };export function close(hnd) { ... };
module lexer from 'compiler/LexicalHandler';module stdlib from '@std'; //... scan(cmdline) ...
Klasser med konstruktører, getters & setters
Begrebet klasse har altid været et omstridt emne blandt purister, og vi har hidtil klaret os med enten at falde tilbage på JavaScript’s prototypiske natur eller ved at bruge rammer eller abstraktioner, der giver mulighed for at bruge klassedefinitioner i en form, der desukker til den samme prototypiske adfærd.
I Harmony kommer klasser som en del af sproget sammen med konstruktører og (endelig) en vis følelse af ægte privatlivets fred. I de følgende eksempler har jeg inkluderet nogle inline-kommentarer for at hjælpe dig med at forstå, hvordan klasser er opbygget, men du vil måske også bemærke manglen på ordet “funktion” herinde. Dette er ikke en slåfejl: TC39 har gjort en bevidst indsats for at mindske vores misbrug af nøgleordet function
til alting, og håbet er, at dette vil bidrage til at forenkle den måde, vi skriver kode på.
class Cake{ // We can define the body of a class' constructor // function by using the keyword 'constructor' followed // by an argument list of public and private declarations. constructor( name, toppings, price, cakeSize ){ public name = name; public cakeSize = cakeSize; public toppings = toppings; private price = price; } // As a part of ES.next's efforts to decrease the unnecessary // use of 'function' for everything, you'll notice that it's // dropped for cases such as the following. Here an identifier // followed by an argument list and a body defines a new method addTopping( topping ){ public(this).toppings.push(topping); } // Getters can be defined by declaring get before // an identifier/method name and a curly body. get allToppings(){ return public(this).toppings; } get qualifiesForDiscount(){ return private(this).price > 5; } // Similar to getters, setters can be defined by using // the 'set' keyword before an identifier set cakeSize( cSize ){ if( cSizeES Harmony Conclusions
As you can see, ES.next is coming with some exciting new additions. Although Traceur can be used to an extent to try our such features in the present, remember that it may not be the best idea to plan out your system to use Harmony (just yet). There are risks here such as specifications changing and a potential failure at the cross-browser level (IE9 for example will take a while to die) so your best bets until we have both spec finalization and coverage are AMD (for in-browser modules) and CJS (for those on the server).
Conclusions And Further Reading A Review
In this article we've reviewed several of the options available for writing modular JavaScript using modern module formats. These formats have a number of advantages over using the (classical) module pattern alone including: avoiding a need for developers to create global variables for each module they create, better support for static and dynamic dependency management, improved compatibility with script loaders, better (optional) compatibility for modules on the server and more.
In short, I recommend trying out what's been suggested today as these formats offer a lot of power and flexibility that can help when building applications based on many reusable blocks of functionality.
And that's it for now. If you have further questions about any of the topics covered today, feel free to hit me up on twitter and I'll do my best to help!
Copyright Addy Osmani, 2012. All Rights Reserved.