Tweet
Modularitet Vikten av att frikoppla programmet
När vi säger att ett program är modulärt menar vi i allmänhet att det består av en uppsättning mycket frikopplade, distinkta delar av funktionalitet som lagras i moduler. Som du säkert vet underlättar lös koppling enklare underhåll av appar genom att ta bort beroenden där det är möjligt. När detta implementeras effektivt är det ganska lätt att se hur ändringar i en del av ett system kan påverka en annan del.
Till skillnad från vissa mer traditionella programmeringsspråk ger den nuvarande versionen av JavaScript (ECMA-262) dock inte utvecklare möjlighet att importera sådana moduler av kod på ett rent och organiserat sätt. Det är ett av problemen med specifikationer som inte har krävt någon större eftertanke förrän på senare år då behovet av mer organiserade JavaScript-applikationer blev uppenbart.
Istället får utvecklare för närvarande falla tillbaka på variationer av modul- eller objektlitteraturmönstren. I många av dessa är modulskripten sammanfogade i DOM med namnområden som beskrivs av ett enda globalt objekt där det fortfarande är möjligt att drabbas av namnkollisioner i din arkitektur. Det finns inte heller något rent sätt att hantera beroendehantering utan en del manuellt arbete eller verktyg från tredje part.
Medans inhemska lösningar på dessa problem kommer att anlända i ES Harmony, är den goda nyheten att det aldrig har varit enklare att skriva modulärt JavaScript och att du kan börja göra det redan idag.
I den här artikeln kommer vi att titta på tre format för att skriva modulärt JavaScript: AMD, CommonJS och förslag till nästa version av JavaScript, Harmony.
Prelude A Note On Script Loaders
Det är svårt att diskutera AMD- och CommonJS-moduler utan att tala om elefanten i rummet – script loaders. För närvarande är script loader ett medel för att nå ett mål, detta mål är modulärt JavaScript som kan användas i dagens tillämpningar – för detta är det tyvärr nödvändigt att använda en kompatibel script loader. För att få ut så mycket som möjligt av den här artikeln rekommenderar jag att man skaffar sig en grundläggande förståelse för hur de populära skriptladdningsverktygen fungerar så att förklaringarna av modulformaten blir meningsfulla i sitt sammanhang.
Det finns ett antal bra laddare för att hantera modulladdning i AMD- och CJS-formaten, men mina personliga preferenser är RequireJS och curl.js. Fullständiga handledningar om dessa verktyg ligger utanför ramen för den här artikeln, men jag kan rekommendera att du läser John Hanns inlägg om curl.js och James Burkes RequireJS API-dokumentation för mer information.
Från ett produktionsperspektiv rekommenderas användning av optimeringsverktyg (som RequireJS-optimeraren) för att sammanfoga skript för utplacering när du arbetar med sådana moduler. Intressant nog, med Almond AMD shim, behöver RequireJS inte rullas i den distribuerade webbplatsen och det som man kan betrakta som en skriptladdare kan lätt flyttas utanför utvecklingen.
Med det sagt skulle James Burke förmodligen säga att möjligheten att dynamiskt ladda skript efter sidladdning fortfarande har sina användningsområden och RequireJS kan hjälpa till med detta också. Med dessa anteckningar i åtanke kan vi börja.
AMD Ett format för att skriva modulärt JavaScript i webbläsaren
Det övergripande målet för AMD-formatet (Asynchronous Module Definition) är att tillhandahålla en lösning för modulärt JavaScript som utvecklare kan använda idag. Det föddes ur Dojos verkliga erfarenhet av att använda XHR+eval och förespråkarna för det här formatet ville undvika att framtida lösningar skulle drabbas av svagheterna hos de tidigare lösningarna.
Det egentliga AMD-modulformatet är ett förslag för att definiera moduler där både modulen och beroendena kan laddas asynkront. Det har ett antal tydliga fördelar, bl.a. att det är både asynkront och mycket flexibelt till sin natur, vilket tar bort den täta koppling som man vanligen kan finna mellan kod och modulidentitet. Många utvecklare tycker om att använda det och man kan betrakta det som en pålitlig språngbräda mot det modulsystem som föreslås för ES Harmony.
AMD började som ett utkast till specifikation för ett modulformat på CommonJS-listan, men eftersom det inte gick att nå full enighet flyttades vidareutvecklingen av formatet till amdjs-gruppen.
I dag omfamnas det av projekt som Dojo (1.7), MooTools (2.0), Firebug (1.8) och till och med jQuery (1.7). Även om termen CommonJS AMD-formatet har setts i naturen vid enstaka tillfällen är det bäst att referera till det som bara AMD eller Async Module support eftersom inte alla deltagare på CJS-listan ville driva det vidare.
Genom att komma igång med moduler
De två nyckelkoncept som du måste vara medveten om här är idén om en define
-metod för att underlätta moduldefinition och en require
-metod för att hantera laddning av beroenden. define används för att definiera namngivna eller icke namngivna moduler baserat på förslaget med hjälp av följande signatur:
Som du kan se av inline-kommentarerna är module_id
ett valfritt argument som typiskt sett bara krävs när concatenation-verktyg som inte är från AMD används (det kan finnas några andra kantfall där det också är användbart). När detta argument utelämnas kallar vi modulen för anonym.
När man arbetar med anonyma moduler är idén om en moduls identitet DRY, vilket gör det trivialt att undvika dubblering av filnamn och kod. Eftersom koden är mer portabel kan den lätt flyttas till andra platser (eller runt i filsystemet) utan att man behöver ändra själva koden eller ändra dess ID. module_id
motsvarar mappvägar i enkla paket och när de inte används i paket. Utvecklare kan också köra samma kod i flera miljöer bara genom att använda en AMD-optimerare som fungerar med en CommonJS-miljö som r.js.
Tillbaka till define-signaturen, argumentet dependencies representerar en matris av beroenden som krävs av den modul du definierar och det tredje argumentet (”definition function”) är en funktion som exekveras för att instantiera din modul. En barebone-modul kan definieras på följande sätt:
Understanding AMD: define()
require å andra sidan används vanligtvis för att läsa in kod i en JavaScript-fil på högsta nivå eller i en modul om du vill hämta beroenden dynamiskt. Ett exempel på dess användning är:
Understanding AMD: require()
Dynamiskt laddade beroenden
Understanding AMD: plugins
Följande är ett exempel på att definiera ett AMD-kompatibelt plugin:
Laddning av AMD-moduler med hjälp av require.js
Loading AMD Modules Using curl.js
Moduler med uppskjutna beroenden
Varför är AMD ett bättre val för att skriva modulärt JavaScript?
- Gör ett tydligt förslag på hur man ska gå tillväga för att definiera flexibla moduler.
- Väsentligt renare än de nuvarande lösningarna med global namespace och
<script>
-taggar som många av oss förlitar oss på. Det finns ett rent sätt att deklarera fristående moduler och de beroenden de kan ha. - Moduldefinitioner är inkapslade, vilket hjälper oss att undvika förorening av den globala namnrymden.
- Fungerar bättre än vissa alternativa lösningar (t.ex. CommonJS, som vi kommer att titta på inom kort). Har inga problem med domänöverskridande, lokal eller felsökning och är inte beroende av att verktyg på serversidan används. De flesta AMD-lastare stöder laddning av moduler i webbläsaren utan en byggprocess.
- Gör det möjligt att använda en ”transportmetod” för att inkludera flera moduler i en enda fil. Andra tillvägagångssätt som CommonJS har ännu inte kommit överens om ett transportformat.
- Det är möjligt att lazy load-skript om detta behövs.
AMD-moduler med Dojo
Det är ganska okomplicerat att definiera AMD-kompatibla moduler med Dojo. Som ovan definierar du eventuella modulberoenden i en array som första argument och tillhandahåller en callback (factory) som kommer att köra modulen när beroendena har laddats. e.g:
define(, function( Tooltip ){ //Our dijit tooltip is now available for local use new Tooltip(...);});
Notera den anonyma karaktären hos modulen som nu både kan konsumeras av en Dojo asynkron laddare, RequireJS eller standardmodulladdaren dojo.require() som du kanske är van vid att använda.
För dem som undrar över modulreferensering finns det några intressanta gotchas som är användbara att känna till här. Även om det av AMD förespråkade sättet att referera moduler deklarerar dem i beroendelistan med en uppsättning matchande argument, stöds detta inte av Dojo 1.6-byggsystemet – det fungerar egentligen bara för AMD-kompatibla laddare. e.g:
define(, function( cookie, Tooltip ){ var cookieValue = cookie("cookieName"); new Tree(...); });
Detta har många fördelar jämfört med nested namespacing eftersom moduler inte längre behöver referera direkt till hela namnområden varje gång – allt vi behöver är sökvägen ”dojo/cookie” i beroenden, som när den har aliaserats till ett argument kan refereras av den variabeln. Detta tar bort behovet av att upprepade gånger skriva ”dojo.” i dina program.
Det sista problemet att vara medveten om är att om du vill fortsätta att använda Dojos byggsystem eller om du vill migrera äldre moduler till den här nyare AMD-stilen, så möjliggör följande mer utförliga version en enklare migrering. Lägg märke till att dojo och dijit och refereras som beroenden också:
AMD Module Design Patterns (Dojo)
Om du har följt något av mina tidigare inlägg om fördelarna med designmönster vet du att de kan vara mycket effektiva för att förbättra hur vi närmar oss att strukturera lösningar på vanliga utvecklingsproblem. John Hann gav nyligen en utmärkt presentation om designmönster för AMD:s moduler som omfattar Singleton, Decorator, Mediator och andra. Jag rekommenderar starkt att du tar del av hans bilder om du får chansen.
Några exempel på dessa mönster finns nedan:
Decorator-mönstret:
Adaptermönster
AMD Modules With jQuery
The Basics
Till skillnad från Dojo kommer jQuery egentligen bara med en fil, men med tanke på bibliotekets insticksmodulbaserade karaktär kan vi demonstrera hur enkelt det är att definiera en AMD-modul som använder den nedan.
Det finns dock något som saknas i det här exemplet och det är begreppet registrering.
Registering jQuery As An Async-compatible Module
En av de viktigaste funktionerna som landade i jQuery 1.7 var stöd för att registrera jQuery som en asynkron modul. Det finns ett antal kompatibla skriptladdare (bland annat RequireJS och curl) som kan ladda moduler med hjälp av ett asynkront modulformat och detta innebär att färre hackningar krävs för att få saker och ting att fungera.
Som ett resultat av jQuerys popularitet måste AMD-laddare ta hänsyn till att flera versioner av biblioteket laddas in på samma sida, eftersom man helst inte vill att flera olika versioner ska laddas samtidigt. Laddare har möjlighet att antingen specifikt ta hänsyn till detta problem eller instruera sina användare om att det finns kända problem med tredjepartsskript och deras bibliotek.
Vad tillägget 1.7 tillför är att det hjälper till att undvika problem med annan tredjepartskod på en sida som oavsiktligt laddar upp en version av jQuery på sidan som ägaren inte hade väntat sig. Du vill inte att andra instanser ska klumpa ihop din egen och därför kan detta vara till nytta.
Sättet som detta fungerar på är att skriptläsaren som används anger att den har stöd för flera jQuery-versioner genom att specificera att en egenskap, define.amd.jQuery
är lika med true. För dem som är intresserade av mer specifika genomförandedetaljer registrerar vi jQuery som en namngiven modul eftersom det finns en risk att den kan sammankopplas med andra filer som kan använda AMD:s define()
-metod, men inte använda ett korrekt sammankopplingsskript som förstår anonyma AMD-moduldefinitioner.
Den namngivna AMD ger en säkerhetsmatta av att vara både robust och säker för de flesta användningsfall.
Smartare jQuery-plugins
Jag har nyligen diskuterat några idéer och exempel på hur jQuery-plugins skulle kunna skrivas med hjälp av Universal Module Definition (UMD)-mönster här. UMD:er definierar moduler som kan fungera på både klient och server samt med alla populära skriptladdare som finns tillgängliga för tillfället. Även om detta fortfarande är ett nytt område med många koncept som fortfarande håller på att färdigställas, är du välkommen att titta på kodproverna i avsnittsrubriken AMD && CommonJS nedan och meddela mig om du tycker att det finns något som vi skulle kunna göra bättre.
Vilka script loaders & ramverk har stöd för AMD?
In-browser:
Server-side:
- RequireJS http://requirejs.org
- PINF http://github.com/pinf/loader-js
AMD Slutsatser
Ovanstående är mycket triviala exempel på hur användbara AMD-moduler verkligen kan vara, men de ger förhoppningsvis en grund för att förstå hur de fungerar.
Du kanske är intresserad av att veta att många synliga stora tillämpningar och företag för närvarande använder AMD-moduler som en del av sin arkitektur. Bland annat IBM och BBC iPlayer, vilket visar hur allvarligt detta format tas i beaktande av utvecklare på företagsnivå.
För fler anledningar till varför många utvecklare väljer att använda AMD-moduler i sina applikationer kan du vara intresserad av det här inlägget av James Burke.
CommonJS Ett modulformat optimerat för servern
CommonJS är en frivillig arbetsgrupp som syftar till att utforma, skapa prototyper och standardisera JavaScript API:er. Hittills har de försökt ratificera standarder för både moduler och paket. CommonJS-modulförslaget specificerar ett enkelt API för att deklarera moduler på serversidan och till skillnad från AMD försöker det täcka en bredare uppsättning frågor som io, filsystem, löften med mera.
Genom att komma igång
Från ett strukturellt perspektiv är en CJS-modul en återanvändbar del av JavaScript som exporterar specifika objekt som görs tillgängliga för all beroende kod – det finns vanligtvis inga funktionsomslag runt sådana moduler (så du kommer inte att se define
användas här, till exempel).
På en hög nivå innehåller de i princip två primära delar: en fri variabel med namnet exports
som innehåller de objekt som en modul vill göra tillgängliga för andra moduler och en require
-funktion som moduler kan använda för att importera andra modulers export.
Förstå CJS: require() och exports
Grundläggande konsumtion av exports
AMD-ekvivalent till det första CJS-exemplet
Konsumtion av flera beroenden
app.js
bar.js
exports.name = 'bar';
foo.js
require('./bar');exports.helloWorld = function(){ return 'Hello World!!''}
Vilka laddare & som har stöd för CJS?
I webbläsare:
- curl.js http://github.com/unscriptable/curl
- SproutCore 1.1 http://sproutcore.com
- PINF http://github.com/pinf/loader-js
- (med mera)
Server-sida:
Är CJS lämplig för webbläsaren?
Det finns utvecklare som anser att CommonJS lämpar sig bättre för utveckling på serversidan, vilket är en av anledningarna till att det för närvarande råder oenighet om vilket format som bör och kommer att användas som de facto-standard i för-Harmony-åldern framöver. Några av argumenten mot CJS inkluderar en notering om att många CommonJS API:er behandlar serverorienterade funktioner som man helt enkelt inte skulle kunna implementera på webbläsarnivå i JavaScript – till exempel skulle io, system och js kunna betraktas som icke-implementerbara på grund av karaktären på deras funktionalitet.
Med detta sagt är det användbart att veta hur man strukturerar CJS-moduler oavsett så att vi bättre kan uppskatta hur de passar in när vi definierar moduler som kan användas överallt. Moduler som har tillämpningar på både klient och server är bland annat validerings-, konverterings- och templatingmotorer. Det sätt på vilket vissa utvecklare närmar sig valet av format är att välja CJS när en modul kan användas i en server-sidemiljö och använda AMD om så inte är fallet.
Då AMD-moduler kan använda plugins och kan definiera mer granulära saker som konstruktörer och funktioner är detta logiskt. CJS-moduler kan bara definiera objekt vilket kan vara tråkigt att arbeta med om man försöker få fram konstruktörer ur dem.
Och även om det ligger utanför den här artikelns räckvidd har du kanske också lagt märke till att det nämndes olika typer av ”require”-metoder när man diskuterade AMD och CJS.
Problemet med en liknande namnkonvention är naturligtvis förvirring och gemenskapen är för närvarande splittrad om fördelarna med en global require-funktion. John Hanns förslag här är att istället för att kalla den ”require”, vilket förmodligen inte skulle nå målet att informera användarna om skillnaden mellan en global och en inre require, kan det vara vettigare att döpa om den globala laddningsmetoden till något annat (t.ex. namnet på biblioteket). Det är av denna anledning som en loader som curl.js använder curl()
i stället för require
.
AMD && CommonJS Konkurrerande, men lika giltiga standarder
Samtidigt som den här artikeln har lagt större vikt vid att använda AMD framför CJS, är verkligheten att båda formaten är giltiga och har en användning.
AMD har ett webbläsarvänligt tillvägagångssätt för utveckling och väljer asynkront beteende och förenklad bakåtkompatibilitet, men det har inget koncept för File I/O. Den stöder objekt, funktioner, konstruktörer, strängar, JSON och många andra typer av moduler som körs nativt i webbläsaren. Den är otroligt flexibel.
CommonJS å andra sidan har en server-first-strategi och utgår från synkront beteende, inget globalt bagage som John Hann skulle kalla det, och den försöker ta hand om framtiden (på servern). Vad vi menar med detta är att eftersom CJS stöder oinpackade moduler kan det kännas lite mer nära ES.next/Harmony-specifikationerna, vilket befriar dig från define()
-omslaget som AMD tillämpar. CJS-moduler har dock endast stöd för objekt som moduler.
Och även om tanken på ännu ett modulformat kan vara skrämmande, kan du vara intresserad av några exempel på arbete med hybridmoduler AMD/CJS och Univeral AMD/CJS-moduler.
Basic AMD Hybrid Format (John Hann)
AMD/CommonJS Universal Module Definition (Variation 2, UMDjs)
Extensible UMD Plugins With (Variation by myself and 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; iapp.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 som har till uppgift att definiera syntaxen och semantiken för ECMAScript och dess framtida iterationer består av ett antal mycket intelligenta utvecklare. Några av dessa utvecklare (t.ex. Alex Russell) har hållit ett vakande öga på utvecklingen av JavaScript-användningen för storskalig utveckling under de senaste åren och är mycket medvetna om behovet av bättre språkfunktioner för att skriva mer modulär JS.
För närvarande finns det därför förslag på ett antal spännande tillägg till språket, bl.a. flexibla moduler som kan fungera både på klienten och på servern, en modulladdare med mera. I det här avsnittet kommer jag att visa dig några kodexempel på syntaxen för moduler i ES.next så att du kan få en försmak av vad som komma skall.
Observera: Även om Harmony fortfarande befinner sig i förslagsfasen kan du redan nu prova (partiella) funktioner i ES.next som tar upp inhemskt stöd för att skriva modulärt JavaScript tack vare Googles Traceur-kompilator. Om du vill komma igång med Traceur på mindre än en minut kan du läsa den här guiden för att komma igång. Det finns också en JSConf-presentation om det som är värd att titta på om du är intresserad av att lära dig mer om projektet.Moduler med import och export
Om du har läst igenom avsnitten om AMD- och CJS-moduler är du kanske bekant med begreppet modulberoenden (import) och modulexport (eller, det offentliga API/variabler som vi tillåter andra moduler att konsumera). I ES.next har dessa begrepp föreslagits på ett något mer kortfattat sätt där beroenden specificeras med hjälp av nyckelordet
import
.export
skiljer sig inte nämnvärt från vad vi kan förvänta oss och jag tror att många utvecklare kommer att titta på koden nedan och omedelbart "förstå" den.
- importdeklarationer binder en moduls export som lokala variabler och kan omdöpas för att undvika namnkollisioner/konflikter.
- exportdeklarationer deklarerar att en moduls lokala bindning är externt synlig, så att andra moduler kan läsa exporten men inte ändra den. Intressant nog kan moduler exportera underordnade moduler, men de kan inte exportera moduler som har definierats någon annanstans. Du kan också byta namn på exporter så att deras externa namn skiljer sig från deras lokala namn.
Moduler som laddas från fjärrkällor
Modulförslagen tar också hand om moduler som är fjärrbaserade (t.ex. en tredjeparts-API-wrapper), vilket gör det enkelt att ladda in moduler från externa platser. Här är ett exempel där vi hämtar in modulen som vi definierade ovan och använder den:
Modullastar-API
Modullastarförslaget beskriver ett dynamiskt API för laddning av moduler i mycket kontrollerade sammanhang. Signaturer som stöds av laddaren inkluderar load( url, moduleInstance, error)
för laddning av moduler, createModule( object, globalModuleReferences)
och andra. Här är ett annat exempel där vi dynamiskt laddar in den modul som vi ursprungligen definierade. Observera att till skillnad från det senaste exemplet där vi hämtade in en modul från en fjärrkälla är API:et för modulladdare bättre lämpat för dynamiska sammanhang.
Loader.load('http://addyosmani.com/factory/cakes.js', function(cakeFactory){ cakeFactory.oven.makeCupcake('chocolate'); });
CommonJS-liknande moduler för servern
För utvecklare som är serverorienterade är det modulsystem som föreslås för ES.next inte bara begränsat till att titta på moduler i webbläsaren. Nedan för exempel kan du se en CJS-liknande modul som föreslås för användning på servern:
// 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
Begreppet klass har alltid varit en omtvistad fråga bland purister och vi har hittills klarat oss med att antingen falla tillbaka på JavaScript:s prototypiska natur eller genom att använda ramar eller abstraktioner som ger möjlighet att använda klassdefinitioner i en form som motsvarar samma prototypiska beteende.
I Harmony kommer klasser som en del av språket tillsammans med konstruktörer och (äntligen) en viss känsla av sann integritet. I följande exempel har jag inkluderat några inlinekommentarer för att hjälpa dig att förstå hur klasser är uppbyggda, men du kanske också märker att ordet ”funktion” saknas här. Detta är inte ett stavfel: TC39 har gjort en medveten ansträngning för att minska vårt missbruk av nyckelordet function
för allting och förhoppningen är att detta ska bidra till att förenkla hur vi skriver kod.
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.