javascript unlocked - pepa.holla.cz - co se jinam...
TRANSCRIPT
TableofContents
JavaScriptUnlocked
Credits
AbouttheAuthor
AbouttheReviewer
www.PacktPub.com
Supportfiles,eBooks,discountoffers,andmore
Whysubscribe?
FreeaccessforPacktaccountholders
Preface
Whatthisbookcovers
Whatyouneedforthisbook
Whothisbookisfor
Conventions
Readerfeedback
Customersupport
Downloadingtheexamplecode
Errata
Piracy
Questions
1.DivingintotheJavaScriptCore
Makeyourcodereadableandexpressive
Functionargumentdefaultvalue
Conditionalinvocation
Arrowfunctions
Methoddefinitions
Therestoperator
Thespreadoperator
MasteringmultilinestringsinJavaScript
Concatenationversusarrayjoin
Templateliteral
Multi-linestringsviatranspilers
ManipulatingarraysintheES5way
ArraymethodsinES5
ArraymethodsinES6
Traversinganobjectinanelegant,reliable,safe,andfastway
Iteratingthekey-valueobjectsafelyandfast
Enumeratinganarray-likeobject
ThecollectionsofES6
Themosteffectivewayofdeclaringobjects
Classicalapproach
Approachwiththeprivatestate
Inheritancewiththeprototypechain
InheritingfromprototypewithObject.create
InheritingfromprototypewithObject.assign
ApproachwithExtendClass
ClassesinES6
Howto–magicmethodsinJavaScript
AccessorsinES6classes
Controllingaccesstoarbitraryproperties
Summary
2.ModularProgrammingwithJavaScript
HowtogetoutofamessusingmodularJavaScript
Modules
Cleanerglobalscope
Packagingcodeintofiles
Reuse
Modulepatterns
Augmentation
Modulestandards
Howtouseasynchronousmodulesinthebrowser
Prosandcons
Howto–usesynchronousmodulesontheserver
Prosandcons
UMD
JavaScript’sbuilt-inmodulesystem
Namedexports
Defaultexport
ThemoduleloaderAPI
Conclusion
TranspilingCommonJSforin-browseruse
BundlingES6modulesforsynchronousloading
Summary
3.DOMScriptingandAJAX
High-speedDOMoperations
TraversingtheDOM
ChangingtheDOM
StylingtheDOM
Makinguseofattributesandproperties
HandlingDOMevents
Communicatingwiththeserver
XHR
FetchAPI
Summary
4.HTML5APIs
Storingdatainweb-browser
WebStorageAPI
IndexedDB
FileSystemAPI
BoostingperformancewithJavaScriptworkers
Creatingthefirstwebcomponent
Learningtouseserver-to-browsercommunicationchannels
Server-SentEvents
WebSockets
Summary
5.AsynchronousJavaScript
NonblockingJavaScript
Error-firstCallback
Continuation-passingstyle
HandlingasynchronousfunctionsintheES7way
ParalleltasksandtaskserieswiththeAsync.jslibrary
Eventhandlingoptimization
Debouncing
Throttling
Writingcallbacksthatdon’timpactlatency-criticalevents
Summary
6.ALarge-ScaleJavaScriptApplicationArchitecture
DesignpatternsinJavaScript
UnderstandingconcernseparationinJavaScript
MVVM
UsingJavaScriptMV*frameworks
Backbone
Angular
React
Summary
7.JavaScriptBeyondtheBrowser
Levellingupthecodingofacommand-lineprograminJavaScript
BuildingawebserverwithJavaScript
WritingadesktopHTML5application
Settinguptheproject
AddingtheHTML5application
Debugging
Packaging
UsingPhoneGaptomakeamobilenativeapp
Settinguptheproject
Buildingtheproject
Addingplugins
Debugging
Summary
8.DebuggingandProfiling
Huntingbugs
GettingthebestfromaconsoleAPI
Tuningperformance
Summary
Index
JavaScriptUnlockedCopyright©2015PacktPublishing
Allrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.
Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthor,norPacktPublishing,anditsdealersanddistributorswillbeheldliableforanydamagescausedorallegedtobecauseddirectlyorindirectlybythisbook.
PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthisbookbytheappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.
Firstpublished:December2015
Productionreference:1011215
PublishedbyPacktPublishingLtd.
LiveryPlace
35LiveryStreet
BirminghamB32PB,UK.
ISBN978-1-78588-157-2
www.packtpub.com
CreditsAuthor
DmitrySheiko
Reviewer
DurgeshPriyaranjan
CommissioningEditor
WilsonDsouza
AcquisitionEditor
MeetaRajani
ContentDevelopmentEditor
PriyankaMehta
TechnicalEditor
MohitaVyas
CopyEditor
KausambhiMajumdar
ProjectCoordinator
IzzatContractor
Proofreader
SafisEditing
Indexer
TejalSoni
Graphics
AbhinashSahu
ProductionCoordinator
AparnaBhagat
CoverWork
AparnaBhagat
AbouttheAuthorDmitrySheikoisapassionatebloggerandtheauthorofInstantTestingwithQUnit.
Dmitrygothookedtocomputerprogramminginthelate’80s.Forthelast18years,hehasbeeninwebdevelopment.HisveryfirstopensourcecontributionwasanXSLT-basedCMSin2004.Sincethen,hehasbeencontributingquitealottoFOSS.YoucanfindDmitry’slatestworksathttps://github.com/dsheiko.Currently,heisworkingasawebdeveloperinthelovelycityofFrankfurtamMainatCrytekGmbH.
First,Iwouldliketothankmyfamilyforthecontinuoussupportandlettingmetorealizemypotential.Aspecialthankyoutomyfather,whotookmetoanindustrialcomputercenterwhenIwas3yearsold.InIdecadeafterthis,withtheadvanceinPCs,Irealizedthatcomputersmeangames,andafterawhile,Ibecamecuriousenoughtounderstandhowthegamesarebuilt.ThisishowIstartedlearningprogramming.
ThankyoutomyteamatCrytek,whocompliantlyfollowallthepracticesdescribedinthebookandadapttotheconstantlyevolvingtechnologiestokeepupwiththepaceIset.
AbouttheReviewerDurgeshPriyaranjanisaseniorsoftwaredeveloperwhohasbeenworkingonvarioustechnologies.However,helovesJavaScriptprogrammingandinteractiondesignthemost.HeiscurrentlybasedinBengaluru(India)andisworkingasaUIengineerforoneoftheIndiane-commercegiants,Flipkart.
Helovestryingoutdifferenttechnologieswithoutanybias.Oflate,hecanbefoundtinkeringaroundwithRaspberryPi.
I’dliketothankmylovingwifeforhercontinuoussupportofmyworkandwork-relatedhobbies.
Supportfiles,eBooks,discountoffers,andmoreForsupportfilesanddownloadsrelatedtoyourbook,pleasevisitwww.PacktPub.com.
DidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.PacktPub.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusat<[email protected]>formoredetails.
Atwww.PacktPub.com,youcanalsoreadacollectionoffreetechnicalarticles,signupforarangeoffreenewslettersandreceiveexclusivediscountsandoffersonPacktbooksandeBooks.
https://www2.packtpub.com/books/subscription/packtlib
DoyouneedinstantsolutionstoyourITquestions?PacktLibisPackt’sonlinedigitalbooklibrary.Here,youcansearch,access,andreadPackt’sentirelibraryofbooks.
Whysubscribe?FullysearchableacrosseverybookpublishedbyPacktCopyandpaste,print,andbookmarkcontentOndemandandaccessibleviaawebbrowser
FreeaccessforPacktaccountholdersIfyouhaveanaccountwithPacktatwww.PacktPub.com,youcanusethistoaccessPacktLibtodayandview9entirelyfreebooks.Simplyuseyourlogincredentialsforimmediateaccess.
PrefaceJavaScriptwasbornasascriptinglanguageatthemostinappropriatetime—thetimeofbrowserwars.Itwasneglectedandmisunderstoodforadecadeandenduredsixeditions.Andlookatitnow!JavaScripthasbecomeamainstreamprogramminglanguage.Ithasadvancedliterallyeverywhere:inlarge-scaleclient-sidedevelopment,serverscripting,desktopapplications,nativemobileprogramming,gamedevelopment,DBquerying,hardwarecontrol,andOSautomating.JavaScriptacquiredanumberofsubsetssuchasObjective-J,CoffeeScript,TypeScript,andothers.JavaScriptismarvelouslyconciseandanexpressivelanguage.Itfeaturesprototype-basedOOP,objectcompositionandinheritance,variadicfunctions,event-drivenprogramming,andnon-blockingI/O.However,toexploitthetruepowerofJavaScript,weneedtohaveadeepunderstandingoflanguagequirks.Moreover,whiledevelopinginJavaScript,wewillbecomeawareofitsnumerouspitfalls,andwewillneedafewtrickstoavoidthem.TheprojectformerlyknownasEcmaScriptHarmony,wasjustrecentlyfinalizedinthespecificationnamedEcmaScript2015,whichismoreoftenreferredtoasES6.Thisnotonlybroughtthelanguagetothenextlevel,butalsointroducedanumberofnewtechnologiesthatrequireattention.
ThisbookaimstoguidethereaderinunderstandingtheupcomingandexistingfeaturesofJavaScript.Itisfullypackedwithcoderecipesthataddresscommonprogrammingtasks.ThetasksaresuppliedwithsolutionsforclassicalJavaScript(ES5)aswellasforthenextgenerationlanguage(ES6-7).Thebookdoesn’tfocusonlyonin-browserlanguage,butalsoprovidestheessentialsonwritingefficientJavaScriptfordesktopapplications,server-sidesoftware,andnativemoduleapps.Theultimategoaloftheauthorisnotjusttodescribethelanguage,butalsotohelpthereadertoimprovetheircodeforbettermaintainability,readability,andperformance.
WhatthisbookcoversChapter1,DivingintotheJavaScriptCore,discussesthetechniquestoimprovetheexpressivenessofthecode,tomastermulti-linestringsandtemplating,andtomanipulatearraysandarray-likeobjects.ThechapterexplainshowtotakeadvantageofJavaScriptprototypewithoutharmingthereadabilityyourcode.Further,thechapterintroducesthe“magicmethods”ofJavaScriptandgivesapracticalexampleoftheiruse.
Chapter2,ModularProgrammingwithJavaScript,describesthemodularityinJavaScript:whatmodulesare,whytheyareimportant,thestandardapproachesforasynchronouslyandsynchronouslyloadedmodules,andwhatES6modulesare.ThechaptershowshowCommonJSmodulesareusedinserver-sideJavaScriptandhowtopre-compilethemforin-browseruse.Itelaborateshowasynchronousandsynchronousapproachescanbecombinedtoachieveabetterapplicationperformance.ItalsoexplainshowonecanpolyfillES6modulesforproductionbythemeansofBabel.js.
Chapter3,DOMScriptingandAJAX,introducesDocumentObjectModel(DOM),showsthebestpracticestominimizebrowserreflow,andenhanceapplicationperformancewhileoperatingwiththeDOM.Thechapteralsocomparestwoclient-servercommunicationmodels:XHRandFetchAPI.
Chapter4,HTML5APIs,considersthepersistenceAPIsofthebrowsersuchasWebStorage,IndexDB,andFileSystem.ItintroducesWebComponentsandgivesawalk-throughofthecreationofacustomcomponent.Thechapterdescribesserver-to-browsercommunicationAPIssuchasSSEandWebSockets.
Chapter5,AsynchronousJavaScript,explainsthenonblockingnatureofJavaScript,elaboratestheeventloopandthecallstack.Thechapterconsidersthepopularstylesofchainingasynchronouscallsandhandlingerrors.Itpresentstheasync/awaittechniqueofES7andalsogivesexamplesofrunningtasksinparallelandinsequenceusingthePromiseAPIandtheAsync.jslibrary.Itdescribesthrottlinganddebouncingconcepts.
Chapter6,ALarge-ScaleJavaScriptApplicationArchitecture,focusesoncodemaintainabilityandarchitecture.ThechapterintroducestheMVCparadigmanditsderivatives,MVPandMVVM.Italsoshows,withexamples,howconcernseparationisimplementedinpopularframeworkssuchasBackbone.js,AngularJS,andReactJS.
Chapter7,JavaScriptBeyondtheBrowser,explainshowtowritecommand-lineprogramsinJavaScriptandhowtobuildawebserverwithNode.js.ItalsocoversthecreationofdesktopHTML5applicationswithNW.jsandguidesthedevelopmentofnativemobileapplicationswithPhongap.
Chapter8,DebuggingandProfiling,divesintobugdetectionandisolation.ItexaminesthecapacitiesofDevToolsandthelesser-knownfeaturesoftheJavaScriptconsoleAPI.
WhatyouneedforthisbookIt’senoughifyouhaveamodernbrowserandatexteditortoruntheexamplesfromthebook.Itmaybehelpful,however,touseabrowsertoolsimilartoFirefoxScratchpad(https://developer.mozilla.org/en-US/docs/Tools/Scratchpad)toeditthesamplecodedirectlyinthebrowser.ThebooksalsocontainsES6/ES7codeexamplesthatrelyonfeaturesnotyetavailableinbrowsers.YoucanruntheseexamplesinBabel.js’sonlinesandboxavailableathttps://babeljs.io/repl/.
YouwillfinddetailedinstructionsofhowtosetupyourdevelopmentenvironmentandinstalltherequiredtoolsanddependenciesinthechapterswherewerefertoNode.js,NW.js,PhoneGap,JavaScriptframeworks,andNPMpackages.
WhothisbookisforThisbookisforthedeveloperswhoarealreadyfamiliarwithJavaScriptandwanttoleveluptheirskillstogetthemostoutofthelanguage.Thebookispractice-orientedandwouldbehelpfulforthosewhoareusedtothe“learnbydoing”approach,asthetopicsarethoroughlycoveredwithreal-lifeexamplesandtutorials.
ConventionsInthisbook,youwillfindanumberoftextstylesthatdistinguishbetweendifferentkindsofinformation.Herearesomeexamplesofthesestylesandanexplanationoftheirmeaning.
Codewordsintext,databasetablenames,foldernames,filenames,fileextensions,pathnames,dummyURLs,userinput,andTwitterhandlesareshownasfollows:“Wecanincludeothercontextsthroughtheuseoftheincludedirective.”
Ablockofcodeissetasfollows:
varres=[1,2,3,4].filter(function(v){
returnv>2;
})
console.log(res);//[3,4]
Whenwewishtodrawyourattentiontoaparticularpartofacodeblock,therelevantlinesoritemsaresetinbold:
/**
*@param{Function}[cb]-callback
*/
functionfn(cb){
cb&&cb();
};
Anycommand-lineinputoroutputiswrittenasfollows:
npminstallfs-walkcli-color
Newtermsandimportantwordsareshowninbold.Wordsthatyouseeonthescreen,forexample,inmenusordialogboxes,appearinthetextlikethis:“AssoonasEnterispressed,theconsoleoutputsI’mrunning.”
NoteWarningsorimportantnotesappearinaboxlikethis.
TipTipsandtricksappearlikethis.
ReaderfeedbackFeedbackfromourreadersisalwayswelcome.Letusknowwhatyouthinkaboutthisbook—whatyoulikedordisliked.Readerfeedbackisimportantforusasithelpsusdeveloptitlesthatyouwillreallygetthemostoutof.
Tosendusgeneralfeedback,simplye-mail<[email protected]>,andmentionthebook’stitleinthesubjectofyourmessage.
Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,seeourauthorguideatwww.packtpub.com/authors.
CustomersupportNowthatyouaretheproudownerofaPacktbook,wehaveanumberofthingstohelpyoutogetthemostfromyourpurchase.
DownloadingtheexamplecodeYoucandownloadtheexamplecodefilesfromyouraccountathttp://www.packtpub.comforallthePacktPublishingbooksyouhavepurchased.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.
ErrataAlthoughwehavetakeneverycaretoensuretheaccuracyofourcontent,mistakesdohappen.Ifyoufindamistakeinoneofourbooks—maybeamistakeinthetextorthecode—wewouldbegratefulifyoucouldreportthistous.Bydoingso,youcansaveotherreadersfromfrustrationandhelpusimprovesubsequentversionsofthisbook.Ifyoufindanyerrata,pleasereportthembyvisitinghttp://www.packtpub.com/submit-errata,selectingyourbook,clickingontheErrataSubmissionFormlink,andenteringthedetailsofyourerrata.Onceyourerrataareverified,yoursubmissionwillbeacceptedandtheerratawillbeuploadedtoourwebsiteoraddedtoanylistofexistingerrataundertheErratasectionofthattitle.
Toviewthepreviouslysubmittederrata,gotohttps://www.packtpub.com/books/content/supportandenterthenameofthebookinthesearchfield.TherequiredinformationwillappearundertheErratasection.
PiracyPiracyofcopyrightedmaterialontheInternetisanongoingproblemacrossallmedia.AtPackt,wetaketheprotectionofourcopyrightandlicensesveryseriously.IfyoucomeacrossanyillegalcopiesofourworksinanyformontheInternet,pleaseprovideuswiththelocationaddressorwebsitenameimmediatelysothatwecanpursuearemedy.
Pleasecontactusat<[email protected]>withalinktothesuspectedpiratedmaterial.
Weappreciateyourhelpinprotectingourauthorsandourabilitytobringyouvaluablecontent.
QuestionsIfyouhaveaproblemwithanyaspectofthisbook,youcancontactusat<[email protected]>,andwewilldoourbesttoaddresstheproblem.
Chapter1.DivingintotheJavaScriptCoreYoumayhaveownedaniPhoneforyearsandregardyourselfasanexperienceduser.Atthesametime,youkeepremovingunwantedcharactersoneatatimewhiletypingbypressingdelete.However,onedayyoufindoutthataquickshakeallowsyoutodeletethewholemessageinonetap.Thenyouwonderwhyonearthyoudidn’tknowthisearlier.Thesamethinghappenswithprogramming.Wecanbequitesatisfiedwithourcodinguntil,allofsudden,werunintoatrickoralesser-knownlanguagefeaturethatmakesusreconsidertheentireworkdoneovertheyears.Itturnsoutthatwecoulddothisinacleaner,morereadable,moretestable,andmoremaintainableway.Soit’spresumedthatyoualreadyhaveexperiencewithJavaScript;however,thischapterequipsyouwiththebestpracticestoimproveyourcode.Wewillcoverthefollowingtopics:
MakingyourcodereadableandexpressiveMasteringmultilinestringsinJavaScriptManipulatingarraysintheES5wayTraversinganobjectinanelegant,reliable,safe,andfastwayThemosteffectivewayofdeclaringobjectsHowtomagicmethodsinJavaScript
MakeyourcodereadableandexpressiveTherearenumerouspracticesandheuristicstomakeacodemorereadable,expressive,andclean.Wewillcoverthistopiclateron,butherewewilltalkaboutsyntacticsugar.Thetermmeansanalternativesyntaxthatmakesthecodemoreexpressiveandreadable.Infact,wealreadyhadsomeofthisinJavaScriptfromtheverybeginning.Forinstance,theincrement/decrementandaddition/subtractionassignmentoperatorsinheritedfromC.foo++issyntacticsugarforfoo=foo+1,andfoo+=barisashorterformforfoo=foo+bar.Besides,wehaveafewtricksthatservethesamepurpose.
JavaScriptapplieslogicalexpressionstoso-calledshort-circuitevaluation.Thismeansthatanexpressionisreadlefttoright,butassoonastheconditionresultisdeterminedatanearlystage,theexpressiontailisnotevaluated.Ifwehavetrue||false||false,theinterpreterwillknowfromthefirsttestthattheresultistrueregardlessofothertests.Sothefalse||falsepartisnotevaluated,andthisopensawayforcreativity.
FunctionargumentdefaultvalueWhenweneedtospecifydefaultvaluesforparameterswecandolikethat:
functionstub(foo){
returnfoo||"Defaultvalue";
}
console.log(stub("Myvalue"));//Myvalue
console.log(stub());//Defaultvalue
Whatisgoingonhere?Whenfooistrue(notundefined,NaN,null,false,0,or""),theresultofthelogicalexpressionisfoootherwisetheexpressionisevaluateduntilDefaultvalueandthisisthefinalresult.
Startingwith6theditionofEcmaScript(specificationofJavaScriptlanguage)wecanusenicersyntax:
functionstub(foo="Defaultvalue"){
returnfoo;
}
ConditionalinvocationWhilecomposingourcodeweshortenitonconditions:”
varage=20;
age>=18&&console.log("Youareallowedtoplaythisgame");
age>=18||console.log("Thegameisrestrictedto18andover");
Intheprecedingexample,weusedtheAND(&&)operatortoinvokeconsole.logiftheleft-handconditionisTruthy.TheOR(||)operatordoestheopposite,itcallsconsole.logiftheconditionisFalsy.
Ithinkthemostcommoncaseinpracticeistheshorthandconditionwherethefunctioniscalledonlywhenitisprovided:
/**
*@param{Function}[cb]-callback
*/
functionfn(cb){
cb&&cb();
};
Thefollowingisonemoreexampleonthis:
/**
*@classAbstractFoo
*/
AbstractFoo=function(){
//callthis.initifthesubclasshasinitmethod
this.init&&this.init();
};
SyntacticsugarwasintroducedtoitsfullextenttotheJavaScriptworldonlywiththeadvanceinCoffeeScript,asubsetofthelanguagethattrans-compiles(compilessource-to-source)intoJavaScript.ActuallyCoffeeScript,inspiredbyRuby,Python,andHaskell,hasunlockedarrow-functions,spreads,andothersyntaxtoJavaScriptdevelopers.In2011,BrendanEich(theauthorofJavaScript)admittedthatCoffeeScriptinfluencedhiminhisworkonEcmaScriptHarmony,whichwasfinalizedthissummerinECMA-2626theditionspecification.Fromamarketingperspective,thespecificationwritersagreedonusinganewnameconventionthatcallsthe6theditionasEcmaScript2015andthe7theditionasEcmaScript2016.YetthecommunityisusedtoabbreviationssuchasES6andES7.Toavoidconfusionfurtherinthebook,wewillrefertothespecificationsbythesenames.NowwecanlookathowthisaffectsthenewJavaScript.
ArrowfunctionsTraditionalfunctionexpressionmaylooklikethis:
function(param1,param2){/*functionbody*/}
Whendeclaringanexpressionusingthearrowfunction(akafatarrowfunction)syntax,wewillhavethisinalessverboseform,asshowninthefollowing:
(param1,param2)=>{/*functionbody*/}
Inmyopinion,wedon’tgainmuchwiththis.Butifweneed,let’ssay,anarraymethodcallback,thetraditionalformwouldbeasfollows:
function(param1,param2){returnexpression;}
Nowtheequivalentarrowfunctionbecomesshorter,asshownhere:
(param1,param2)=>expression
Wemaydofilteringinanarraythisway:
//filterallthearrayelementsgreaterthan2
varres=[1,2,3,4].filter(function(v){
returnv>2;
})
console.log(res);//[3,4]
Usinganarrayfunction,wecandofilteringinacleanerform:
varres=[1,2,3,4].filter(v=>v>2);
console.log(res);//[3,4]
Besidesshorterfunctiondeclarationsyntax,thearrowfunctionsbringthesocalledlexicalthis.Insteadofcreatingitsowncontext,itusesthecontextofthesurroundingobjectasshownhere:
"usestrict";
/**
*@classView
*/
letView=function(){
letbutton=document.querySelector("[data-bind=\"btn\"]");
/**
*Handlebuttonclickedevent
*@private
*/
this.onClick=function(){
console.log("Buttonclicked");
};
button.addEventListener("click",()=>{
//wecansafelyrefersurroundingobjectmembers
this.onClick();
},false);
}
Intheprecedingexample,wesubscribedahandlerfunctiontoaDOMevent(click).
Withinthescopeofthehandler,westillhaveaccesstotheviewcontext(this),sowedon’tneedtobindthehandlertotheouterscopeorpassitasavariablethroughtheclosure:
varthat=this;
button.addEventListener("click",function(){
//cross-cuttingconcerns
that.onClick();
},false);
MethoddefinitionsAsmentionedintheprecedingsection,arrowfunctionscanbequitehandywhendeclaringsmallinlinecallbacks,butalwaysapplyingitforashortersyntaxiscontroversial.However,ES6providesnewalternativemethoddefinitionsyntaxbesidesthearrowfunctions.Theold-schoolmethoddeclarationmaylookasfollows:
varfoo={
bar:function(param1,param2){
}
}
InES6wecangetridofthefunctionkeywordandthecolon.Sotheprecedingcodecanbeputthisway:
letfoo={
bar(param1,param2){
}
}
TherestoperatorAnothersyntaxstructurethatwasborrowedfromCoffeeScriptcametoJavaScriptastherestoperator(albeit,theapproachiscalledsplatsinCoffeeScript).
Whenwehadafewmandatoryfunctionparametersandanunknownnumberofrestparameters,weusedtodosomethinglikethis:
"usestrict";
varcb=function(){
//allavailableparametersintoanarray
varargs=[].slice.call(arguments),
//thefirstarrayelementtofooandshift
foo=args.shift(),
//thenewfirstarrayelementtobarandshift
bar=args.shift();
console.log(foo,bar,args);
};
cb("foo","bar",1,2,3);//foobar[1,2,3]
NowcheckouthowexpressivethiscodebecomesinES6:
letcb=function(foo,bar,...args){
console.log(foo,bar,args);
}
cb("foo","bar",1,2,3);//foobar[1,2,3]
Functionparametersaren’ttheonlyapplicationoftherestoperator.Forexample,wecanuseitindestructionsaswell,asfollows:
let[bar,...others]=["bar","foo","baz","qux"];
console.log([bar,others]);//["bar",["foo","baz","qux"]]
ThespreadoperatorSimilarly,wecanspreadarrayelementsintoarguments:
letargs=[2015,6,17],
relDate=newDate(...args);
console.log(relDate.toString());//FriJul17201500:00:00GMT+0200
(CEST)
ES6alsoprovidesexpressivesyntacticsugarforobjectcreationandinheritance,butwewillexaminethislaterinThemosteffectivewayofdeclaringobjectssection.
MasteringmultilinestringsinJavaScriptMulti-linestringsaren’tagoodpartofJavaScript.Whiletheyareeasytodeclareinotherlanguages(forinstance,NOWDOC),youcannotjustkeepsingle-quotedordouble-quotedstringsinmultiplelines.ThiswillleadtosyntaxerroraseverylineinJavaScriptisconsideredasapossiblecommand.Youcansetbackslashestoshowyourintention:
varstr="Loremipsumdolorsitamet,\n\
consecteturadipiscingelit.Nuncornare,\n\
diamultriciesvehiculaaliquam,mauris\n\
ipsumdapibusdolor,quisfringillaleoligulanonneque";
Thiskindofworks.However,assoonasyoumissatrailingspace,yougetasyntaxerror,whichisnoteasytospot.Whilemostscriptagentssupportthissyntax,it’s,however,notapartoftheEcmaScriptspecification.
InthetimesofEcmaScriptforXML(E4X),wecouldassignapureXMLtoastring,whichopenedawayfordeclarationssuchasthese:
varstr=<>Loremipsumdolorsitamet,
consecteturadipiscing
elit.Nuncornare</>.toString();
NowadaysE4Xisdeprecated,it’snotsupportedanymore.
ConcatenationversusarrayjoinWecanalsousestringconcatenation.Itmayfeelclumsy,butit’ssafe:
varstr="Loremipsumdolorsitamet,\n"+
"consecteturadipiscingelit.Nuncornare,\n"+
"diamultriciesvehiculaaliquam,mauris\n"+
"ipsumdapibusdolor,quisfringillaleoligulanonneque";
Youmaybesurprised,butconcatenationisslowerthanarrayjoining.Sothefollowingtechniquewillworkfaster:
varstr=["Loremipsumdolorsitamet,\n",
"consecteturadipiscingelit.Nuncornare,\n",
"diamultriciesvehiculaaliquam,mauris\n",
"ipsumdapibusdolor,quisfringillaleoligulanonneque"].join("");
TemplateliteralWhataboutES6?ThelatestEcmaScriptspecificationintroducesanewsortofstringliteral,templateliteral:
varstr=`Loremipsumdolorsitamet,\n
consecteturadipiscingelit.Nuncornare,\n
diamultriciesvehiculaaliquam,mauris\n
ipsumdapibusdolor,quisfringillaleoligulanonneque`;
Nowthesyntaxlookselegant.Butthereismore.TemplateliteralsreallyremindusofNOWDOC.Youcanreferanyvariabledeclaredinthescopewithinthestring:
"usestrict";
vartitle="Sometitle",
text="Sometext",
str=`<divclass="message">
<h2>${title}</h2>
<article>${text}</article>
</div>`;
console.log(str);
Theoutputisasfollows:
<divclass="message">
<h2>Sometitle</h2>
<article>Sometext</article>
</div>
Ifyouwonderwhencanyousafelyusethissyntax,Ihaveagoodnewsforyou—thisfeatureisalreadysupportedby(almost)allthemajorscriptagents(http://kangax.github.io/compat-table/es6/).
Multi-linestringsviatranspilersWiththeadvanceofReactJS,Facebook’sEcmaScriptlanguageextensionnamedJSX(https://facebook.github.io/jsx/)isnowreallygainingmomentum.ApparentlyinfluencedbypreviouslymentionedE4X,theyproposedakindofstringliteralforXML-likecontentwithoutanyscreeningatall.ThistypesupportstemplateinterpolationsimilartoES6templates:
"usestrict";
varHello=React.createClass({
render:function(){
return<divclass="message">
<h2>{this.props.title}</h2>
<article>{this.props.text}</article>
</div>;
}
});
React.render(<Hellotitle="Sometitle"text="Sometext"/>,node);
AnotherwaytodeclaremultilinestringsisbyusingCommonJSCompiler(http://dsheiko.github.io/cjsc/).Whileresolvingthe‘require’dependencies,thecompilertransformsanycontentthatisnot.js/.jsoncontentintoasingle-linestring:
foo.txt
Loremipsumdolorsitamet,
consecteturadipiscingelit.Nuncornare,
diamultriciesvehiculaaliquam,mauris
ipsumdapibusdolor,quisfringillaleoligulanonneque
consumer.js
varstr=require("./foo.txt");
console.log(str);
YoucanfindanexampleofJSXuseinChapter6,ALarge-ScaleJavaScriptApplicationArchitecture.
ManipulatingarraysintheES5waySomeyearsagowhenthesupportofES5featureswaspoor(EcmaScript5theditionwasfinalizedin2009),librariessuchasUnderscoreandLo-Dashgothighlypopularastheyprovidedacomprehensivesetofutilitiestodealwitharrays/collections.Today,manydevelopersstillusethird-partylibraries(includingjQuery/Zepro)formethodssuchasmap,filter,every,some,reduce,andindexOf,whiletheseareavailableinthenativeformofJavaScript.Itstilldependsonhowyouusesuchlibraries,butitmaylikelyhappenthatyoudon’tneedthemanymore.Let’sseewhatwehavenowinJavaScript.
ArraymethodsinES5Array.prototype.forEachisprobablythemostusedmethodofthearrays.Thatis,itisthenativeimplementationof_.each,orforexample,ofthe$.eachutilities.Asparameters,forEachexpectsaniterateecallbackfunctionandoptionallyacontextinwhichyouwanttoexecutethecallback.Itpassestothecallbackfunctionanelementvalue,anindex,andtheentirearray.Thesameparametersyntaxisusedformostarraymanipulationmethods.NotethatjQuery’s$.eachhastheinvertedcallbackparametersorder:
"usestrict";
vardata=["bar","foo","baz","qux"];
data.forEach(function(val,inx){
console.log(val,inx);
});
Array.prototype.mapproducesanewarraybytransformingtheelementsofagivenarray:
"usestrict";
vardata={bar:"barbar",foo:"foofoo"},
//convertkey-valuearrayintourl-encodedstring
urlEncStr=Object.keys(data).map(function(key){
returnkey+"="+window.encodeURIComponent(data[key]);
}).join("&");
console.log(urlEncStr);//bar=bar%20bar&foo=foo%20foo
Array.prototype.filterreturnsanarray,whichconsistsofgivenarrayvaluesthatmeetthecallback’scondition:
"usestrict";
vardata=["bar","foo","",0],
//removeallfalsyelements
filtered=data.filter(function(item){
return!!item;
});
console.log(filtered);//["bar","foo"]
Array.prototype.reduce/Array.prototype.reduceRightretrievestheproductofvaluesinanarray.Themethodexpectsacallbackfunctionandoptionallytheinitialvalueasarguments.Thecallbackfunctionreceivefourparameters:theaccumulativevalue,currentone,indexandoriginalarray.Sowecan,foraninstance,incrementtheaccumulativevaluebythecurrentone(returnacc+=cur;)and,thus,wewillgetthesumofarrayvalues.
Besidescalculatingwiththesemethods,wecanconcatenatestringvaluesorarrays:
"usestrict";
vardata=[[0,1],[2,3],[4,5]],
arr=data.reduce(function(prev,cur){
returnprev.concat(cur);
}),
arrReverse=data.reduceRight(function(prev,cur){
returnprev.concat(cur);
});
console.log(arr);//[0,1,2,3,4,5]
console.log(arrReverse);//[4,5,2,3,0,1]
Array.prototype.sometestswhetherany(orsome)valuesofagivenarraymeetthecallbackcondition:
"usestrict";
varbar=["bar","baz","qux"],
foo=["foo","baz","qux"],
/**
*Checkifagivencontext(this)containsthevalue
*@param{*}val
*@return{Boolean}
*/
compare=function(val){
returnthis.indexOf(val)!==-1;
};
console.log(bar.some(compare,foo));//true
Inthisexample,wecheckedwhetheranyofthebararrayvaluesareavailableinthefooarray.Fortestability,weneedtopassareferenceofthefooarrayintothecallback.Hereweinjectitascontext.Ifweneedtopassmorereferences,wewouldpushtheminakey-valueobject.
Asyouprobablynoticed,weusedinthisexampleArray.prototype.indexOf.ThemethodworksthesameasString.prototype.indexOf.Thisreturnsanindexofthematchfoundor-1.
Array.prototype.everytestswhethereveryvalueofagivenarraymeetsthecallbackcondition:
"usestrict";
varbar=["bar","baz"],
foo=["bar","baz","qux"],
/**
*Checkifagivencontext(this)containsthevalue
*@param{*}val
*@return{Boolean}
*/
compare=function(val){
returnthis.indexOf(val)!==-1;
};
console.log(bar.every(compare,foo));//true
IfyouarestillconcernedaboutsupportforthesemethodsinalegacybrowserasoldasIE6-7,youcansimplyshimthemwithhttps://github.com/es-shims/es5-shim.
ArraymethodsinES6InES6,wegetjustafewnewmethodsthatlookratherlikeshortcutsovertheexistingfunctionality.
Array.prototype.fillpopulatesanarraywithagivenvalue,asfollows:
"usestrict";
vardata=Array(5);
console.log(data.fill("bar"));//["bar","bar","bar","bar","bar"]
Array.prototype.includesexplicitlycheckswhetheragivenvalueexistsinthearray.Well,itisthesameasarr.indexOf(val)!==-1,asshownhere:
"usestrict";
vardata=["bar","foo","baz","qux"];
console.log(data.includes("foo"));
Array.prototype.findfiltersoutasinglevaluematchingthecallbackcondition.Again,it’swhatwecangetwithArray.prototype.filter.Theonlydifferenceisthatthefiltermethodreturnseitheranarrayoranullvalue.Inthiscase,thisreturnsasingleelementarray,asfollows:
"usestrict";
vardata=["bar","fo","baz","qux"],
match=function(val){
returnval.length<3;
};
console.log(data.find(match));//fo
Traversinganobjectinanelegant,reliable,safe,andfastwayItisacommoncasewhenwehaveakey-valueobject(let’ssayoptions)andneedtoiterateit.Thereisanacademicwaytodothis,asshowninthefollowingcode:
"usestrict";
varoptions={
bar:"bar",
foo:"foo"
},
key;
for(keyinoptions){
console.log(key,options[key]);
}
Theprecedingcodeoutputsthefollowing:
barbar
foofoo
Nowlet’simaginethatanyofthethird-partylibrariesthatyouloadinthedocumentaugmentsthebuilt-inObject:
Object.prototype.baz="baz";
Nowwhenwerunourexamplecode,wewillgetanextraundesiredentry:
barbar
foofoo
bazbaz
Thesolutiontothisproblemiswellknown,wehavetotestthekeyswiththeObject.prototype.hasOwnPropertymethod:
//…
for(keyinoptions){
if(options.hasOwnProperty(key)){
console.log(key,options[key]);
}
}
Iteratingthekey-valueobjectsafelyandfastLet’sfacethetruth—thestructureisclumsyandrequiresoptimization(wehavetoperformthehasOwnPropertytestoneverygivenkey).Luckily,JavaScripthastheObject.keysmethodthatretrievesallstring-valuedkeysofallenumerableown(non-inherited)properties.Thisgivesusthedesiredkeysasanarraythatwecaniterate,forinstance,withArray.prototype.forEach:
"usestrict";
varoptions={
bar:"bar",
foo:"foo"
};
Object.keys(options).forEach(function(key){
console.log(key,options[key]);
});
Besidestheelegance,wegetabetterperformancethisway.Inordertoseehowmuchwegain,youcanrunthisonlinetestindistinctbrowserssuchas:http://codepen.io/dsheiko/pen/JdrqXa.
Enumeratinganarray-likeobjectObjectssuchasargumentsandnodeList(node.querySelectorAll,document.forms)looklikearrays,infacttheyarenot.Similartoarrays,theyhavethelengthpropertyandcanbeiteratedintheforloop.Intheformofobjects,theycanbetraversedinthesamewaythatwepreviouslyexamined.Buttheydonothaveanyofthearraymanipulationmethods(forEach,map,filter,someandsoon).Thethingiswecaneasilyconvertthemintoarraysasshownhere:
"usestrict";
varnodes=document.querySelectorAll("div"),
arr=Array.prototype.slice.call(nodes);
arr.forEach(function(i){
console.log(i);
});
Theprecedingcodecanbeevenshorter:
arr=[].slice.call(nodes)
It’saprettyconvenientsolution,butlookslikeatrick.InES6,wecandothesameconversionwithadedicatedmethod:
arr=Array.from(nodes);
ThecollectionsofES6ES6introducesanewtypeofobjects—iterableobjects.Thesearetheobjectswhoseelementscanberetrievedoneatatime.Theyarequitethesameasiteratorsinotherlanguages.Besidearrays,JavaScriptreceivedtwonewiterabledatastructures,SetandMap.Setwhichareacollectionofuniquevalues:
"usestrict";
letfoo=newSet();
foo.add(1);
foo.add(1);
foo.add(2);
console.log(Array.from(foo));//[1,2]
letfoo=newSet(),
bar=function(){return"bar";};
foo.add(bar);
console.log(foo.has(bar));//true
Themapissimilartoakey-valueobject,butmayhavearbitraryvaluesforthekeys.Andthismakesadifference.ImaginethatweneedtowriteanelementwrapperthatprovidesjQuery-likeeventsAPI.Byusingtheonmethod,wecanpassnotonlyahandlercallbackfunctionbutalsoacontext(this).Webindthegivencallbacktothecb.bind(context)context.ThismeansaddEventListenerreceivesafunctionreferencedifferentfromthecallback.Howdoweunsubscribethehandlerthen?WecanstorethenewreferenceinMapbyakeycomposedfromaneventnameandacallbackfunctionreference:
"usestrict";
/**
*@class
*@param{Node}el
*/
letEl=function(el){
this.el=el;
this.map=newMap();
};
/**
*Subscribeahandleronevent
*@param{String}event
*@param{Function}cb
*@param{Object}context
*/
El.prototype.on=function(event,cb,context){
lethandler=cb.bind(context||this);
this.map.set([event,cb],handler);
this.el.addEventListener(event,handler,false);
};
/**
*Unsubscribeahandleronevent
*@param{String}event
*@param{Function}cb
*/
El.prototype.off=function(event,cb){
lethandler=cb.bind(context),
key=[event,handler];
if(this.map.has(key)){
this.el.removeEventListener(event,this.map.get(key));
this.map.delete(key);
}
};
Anyiterableobjecthasmethods,keys,values,andentries,wherethekeysworkthesameasObject.keysandtheothersreturnarrayvaluesandanarrayofkey-valuepairsrespectively.Nowlet’sseehowwecantraversetheiterableobjects:
"usestrict";
letmap=newMap()
.set("bar","bar")
.set("foo","foo"),
pair;
for(pairofmap){
console.log(pair);
}
//OR
letmap=newMap([
["bar","bar"],
["foo","foo"],
]);
map.forEach(function(value,key){
console.log(key,value);
});
Iterableobjectshavemanipulationmethodssuchasarrays.SowecanuseforEach.Besides,theycanbeiteratedbyfor…inandfor…ofloops.Thefirstoneretrievesindexesandthesecond,thevalues.
ThemosteffectivewayofdeclaringobjectsHowdowedeclareanobjectinJavaScript?Ifweneedanamespace,wecansimplyuseanobjectliteral.Butwhenweneedanobjecttype,weneedtothinktwiceaboutwhatapproachtotake,asitaffectsthemaintainabilityofourobject-orientedcode.
ClassicalapproachWecancreateaconstructorfunctionandchainthememberstoitscontext:
"usestrict";
/**
*@class
*/
varConstructor=function(){
/**
*@type{String}
*@public
*/
this.bar="bar";
/**
*@public
*@returns{String}
*/
this.foo=function(){
returnthis.bar;
};
},
/**@typeConstructor*/
instance=newConstructor();
console.log(instance.foo());//bar
console.log(instanceinstanceofConstructor);//true
Wecanalsoassignthememberstotheconstructorprototype.Theresultwillbethesameasfollows:
"usestrict";
/**
*@class
*/
varConstructor=function(){},
instance;
/**
*@type{String}
*@public
*/
Constructor.prototype.bar="bar";
/**
*@public
*@returns{String}
*/
Constructor.prototype.foo=function(){
returnthis.bar;
};
/**@typeConstructor*/
instance=newConstructor();
console.log(instance.foo());//bar
console.log(instanceinstanceofConstructor);//true
Inthefirstcase,wehavetheobjectstructurewithintheconstructorfunctionbodymixed
withtheconstructionlogic.InthesecondcasebyrepeatingConstructor.prototype,weviolatetheDoNotRepeatYourself(DRY)principle.
ApproachwiththeprivatestateSohowcanwedoitotherwise?Wecanreturnanobjectliteralbytheconstructorfunction:
"usestrict";
/**
*@class
*/
varConstructor=function(){
/**
*@type{String}
*@private
*/
varbaz="baz";
return{
/**
*@type{String}
*@public
*/
bar:"bar",
/**
*@public
*@returns{String}
*/
foo:function(){
returnthis.bar+""+baz;
}
};
},
/**@typeConstructor*/
instance=newConstructor();
console.log(instance.foo());//barbaz
console.log(instance.hasOwnProperty("baz"));//false
console.log(Constructor.prototype.hasOwnProperty("baz"));//false
console.log(instanceinstanceofConstructor);//false
Theadvantageofthisapproachisthatanyvariablesdeclaredinthescopeoftheconstructorareinthesameclosureasthereturnedobject,andtherefore,availablethroughtheobject.Wecanconsidersuchvariablesasprivatemembers.Thebadnewsisthatwewilllosetheconstructorprototype.Whenaconstructorreturnsanobjectduringinstantiation,thisobjectbecomestheresultofawholenewexpression.
InheritancewiththeprototypechainWhataboutinheritance?Theclassicalapproachwouldbetomakethesubtypeprototypeaninstanceofsupertype:
"usestrict";
/**
*@class
*/
varSuperType=function(){
/**
*@type{String}
*@public
*/
this.foo="foo";
},
/**
*@class
*/
Constructor=function(){
/**
*@type{String}
*@public
*/
this.bar="bar";
},
/**@typeConstructor*/
instance;
Constructor.prototype=newSuperType();
Constructor.prototype.constructor=Constructor;
instance=newConstructor();
console.log(instance.bar);//bar
console.log(instance.foo);//foo
console.log(instanceinstanceofConstructor);//true
console.log(instanceinstanceofSuperType);//true
Youmayrunintosomecode,whereforinstantiationObject.createisusedinsteadofthenewoperator.Hereyouhavetoknowthedifferencebetweenthetwo.Object.createtakesanobjectasanargumentandcreatesanewonewiththepassedobjectasaprototype.Insomeways,thisremindsusofcloning.Examinethis,youdeclareanobjectliteral(proto)andcreateanewobject(instance)withObject.createbasedonthefirstone.Whateverchangesyoudonowonthenewlycreatedobject,theywon’tbereflectedontheoriginal(proto).Butifyouchangeapropertyoftheoriginal,youwillfindthepropertychangedinitsderivative(instance):
"usestrict";
varproto={
bar:"bar",
foo:"foo"
},
instance=Object.create(proto);
proto.bar="qux",
instance.foo="baz";
console.log(instance);//{foo="baz",bar="qux"}
console.log(proto);//{bar="qux",foo="foo"}
InheritingfromprototypewithObject.createIncontrasttothenewoperator,Object.createdoesnotinvoketheconstructor.Sowhenweuseittopopulateasubtypeprototype,wearelosingallthelogiclocatedinasupertypeconstructor.Thisway,thesupertypeconstructorisnevercalled:
//...
SuperType.prototype.baz="baz";
Constructor.prototype=Object.create(SuperType.prototype);
Constructor.prototype.constructor=Constructor;
instance=newConstructor();
console.log(instance.bar);//bar
console.log(instance.baz);//baz
console.log(instance.hasOwnProperty("foo"));//false
console.log(instanceinstanceofConstructor);//true
console.log(instanceinstanceofSuperType);//true
InheritingfromprototypewithObject.assignWhenlookingforanoptimalstructure,Iwouldliketodeclaremembersviaanobjectliteral,butstillhavethelinktotheprototype.Manythird-partyprojectsleverageacustomfunction(extend)thatmergethestructureobjectliteralintotheconstructorprototype.Actually,ES6providesanObject.assignnativemethod.Wecanuseitasfollows:
"usestrict";
/**
*@class
*/
varSuperType=function(){
/**
*@type{String}
*@public
*/
this.foo="foo";
},
/**
*@class
*/
Constructor=function(){
/**
*@type{String}
*@public
*/
this.bar="bar";
},
/**@typeConstructor*/
instance;
Object.assign(Constructor.prototype=newSuperType(),{
baz:"baz"
});
instance=newConstructor();
console.log(instance.bar);//bar
console.log(instance.foo);//foo
console.log(instance.baz);//baz
console.log(instanceinstanceofConstructor);//true
console.log(instanceinstanceofSuperType);//true
Thislooksalmostasrequired,exceptthereisoneinconvenience.Object.assignsimplyassignsthevaluesofsourceobjectstothetargetonesregardlessoftheirtype.Soifyouhaveasourcepropertywithanobject(forinstance,anObjectorArrayinstance),thetargetobjectreceivesareferenceinsteadofavalue.Soyouhavetoresetmanuallyanyobjectpropertiesduringinitialization.
ApproachwithExtendClassExtendClass,proposedbySimonBoudrias,isasolutionthatseemsflawless(https://github.com/SBoudrias/class-extend).HislittlelibraryexposestheBaseconstructorwiththeextendstaticmethod.Weusethismethodtoextendthispseudo-classandanyofitsderivatives:
"usestrict";
/**
*@class
*/
varSuperType=Base.extend({
/**
*@pulic
*@returns{String}
*/
foo:function(){return"foopublic";},
/**
*@constructsSuperType
*/
constructor:function(){}
}),
/**
*@class
*/
Constructor=SuperType.extend({
/**
*@pulic
*@returns{String}
*/
bar:function(){return"barpublic";}
},{
/**
*@static
*@returns{String}
*/
bar:function(){return"barstatic";}
}),
/**@typeConstructor*/
instance=newConstructor();
console.log(instance.foo());//foopublic
console.log(instance.bar());//barpublic
console.log(Constructor.bar());//barstatic
console.log(instanceinstanceofConstructor);//true
console.log(instanceinstanceofSuperType);//true
ClassesinES6TC39(theEcmaScriptworkinggroup)isprettyawareoftheproblem,sothenewlanguagespecificationprovidesextrasyntaxtostructureobjecttypes:
"usestrict";
classAbstractClass{
constructor(){
this.foo="foo";
}
}
classConcreteClassextendsAbstractClass{
constructor(){
super();
this.bar="bar";
}
baz(){
return"baz";
}
}
letinstance=newConcreteClass();
console.log(instance.bar);//bar
console.log(instance.foo);//foo
console.log(instance.baz());//baz
console.log(instanceinstanceofConcreteClass);//true
console.log(instanceinstanceofAbstractClass);//true
Thesyntaxlooksclass-based,butinfactthisasyntacticsugaroverexistingprototypes.YoucancheckwiththetypeofConcreteClass,anditwillgiveyoufunctionbecauseConcreteClassisacanonicalconstructor.Sowedon’tneedanytricktoextendsupertypes,notricktoreferthesupertypeconstructorfromsubtype,andwehaveacleanreadablestructure.However,wecannotassignpropertiesthesameC-likewayaswedonowwithmethods.ThisisstillindiscussionforES7(https://esdiscuss.org/topic/es7-property-initializers).Besidesthis,wecandeclareaclass’sstaticmethodsstraightinitsbody:
classBar{
staticfoo(){
return"staticmethod";
}
baz(){
return"prototypemethod";
}
}
letinstance=newBar();
console.log(instance.baz());//prototypemethod
console.log(Bar.foo()));//staticmethod
Actually,therearemanyintheJavaScriptcommunitywhoconsiderthenewsyntaxasadeviationfromtheprototypicalOOPapproach.Ontheotherhand,theES6classesarebackwardscompatiblewithmostoftheexistingcode.Subclassesarenowsupportedbythelanguageandnoextralibrariesarerequiredforinheritance.AndwhatIpersonallylike
Howto–magicmethodsinJavaScriptInthePHPworld,therearethingssuchasoverloadingmethods,whicharealsoknownasmagicmethods(http://www.php.net/manual/en/language.oop5.overloading.php).Thesemethodsallowustosetalogicthattriggerswhenanonexistingpropertyofamethodisbeingaccessedormodified.InJavaScript,wecontrolaccesstoproperties(valuemembers).Imaginewehaveacustomcollectionobject.InordertobeconsistentintheAPI,wewanttohavethelengthpropertythatcontainsthesizeofthecollection.Sowedeclareagetter(getlength),whichdoestherequiredcomputationwheneverthepropertyisaccessed.Onattemptingtomodifythepropertyvalue,thesetterwillthrowanexception:
"usestrict";
varbar={
/**@type{[Number]}*/
arr:[1,2],
/**
*Getter
*@returns{Number}
*/
getlength(){
returnthis.arr.length;
},
/**
*Setter
*@param{*}val
*/
setlength(val){
thrownewSyntaxError("Cannotassigntoreadonlyproperty'length'");
}
};
console.log(bar.length);//2
bar.arr.push(3);
console.log(bar.length);//3
bar.length=10;//SyntaxError:Cannotassigntoreadonlyproperty
'length'
Ifwewanttodeclaregetters/settersonanexistingobject,wecanusethefollowing:
Object.defineProperty:
"usestrict";
varbar={
/**@type{[Number]}*/
arr:[1,2]
};
Object.defineProperty(bar,"length",{
/**
*Getter
*@returns{Number}
*/
get:function(){
returnthis.arr.length;
},
/**
*Setter
*/
set:function(){
thrownewSyntaxError("Cannotassigntoreadonlyproperty'length'");
}
});
console.log(bar.length);//2
bar.arr.push(3);
console.log(bar.length);//3
bar.length=10;//SyntaxError:Cannotassigntoreadonlyproperty
'length'
Object.definePropertyaswellasthesecondparameterofObject.createspecifiesapropertyconfiguration(whetheritisenumerable,configurable,immutable,andhowitcanbeaccessedormodified).So,wecanachieveasimilareffectbyconfiguringthepropertyasread-only:
"usestrict";
varbar={};
Object.defineProperty(bar,"length",{
/**
*Datadescriptor
*@type{*}
*/
value:0,
/**
*Datadescriptor
*@type{Boolean}
*/
writable:false
});
bar.length=10;//TypeError:"length"isread-only
Bytheway,ifyouwanttogetridofthepropertyaccessorintheobject,youcansimplyremovetheproperty:
deletebar.length;
AccessorsinES6classesAnotherwaybywhichwecandeclareaccessorsisusingtheES6classes:
"usestrict";
/**@class*/
classBar{
/**@constructsBar*/
constructor(){
/**@type{[Number]}*/
this.arr=[1,2];
}
/**
*Getter
*@returns{Number}
*/
getlength(){
returnthis.arr.length;
}
/**
*Setter
*@param{Number}val
*/
setlength(val){
thrownewSyntaxError("Cannotassigntoreadonlyproperty'length'"
);
}
}
letbar=newBar();
console.log(bar.length);//2
bar.arr.push(3);
console.log(bar.length);//3
bar.length=10;//SyntaxError:Cannotassigntoreadonlyproperty
'length'
Besidespublicproperties,wecancontrolaccesstostaticonesaswell:
"usestrict";
classBar{
/**
*@static
*@returns{String}
*/
staticgetbaz(){
return"baz";
}
}
console.log(Bar.baz);//baz
ControllingaccesstoarbitrarypropertiesAlltheseexamplesshowaccesscontroltoknownproperties.However,theremightbeacasewhenIwantacustomstoragewithavariadicinterfacesimilartolocalStorage.ThismustbeastoragethathasthegetItemmethodtoretrievestoredvaluesandthesetItemmethodtosetthem.Besides,thismustworkthesamewayaswhenyoudirectlyaccessorsetapseudo-property(val=storage.aKeyandstorage.aKey="value").ThesecanbeachievedbyusingtheES6Proxy:
"usestrict";
/**
*Customstorage
*/
varmyStorage={
/**@type{Object}key-valueobject*/
data:{},
/**
*Getter
*@param{String}key
*@returns{*}
*/
getItem:function(key){
returnthis.data[key];
},
/**
*Setter
*@param{String}key
*@param{*}val
*/
setItem:function(key,val){
this.data[key]=val;
}
},
/**
*Storageproxy
*@type{Proxy}
*/
storage=newProxy(myStorage,{
/**
*Proxygetter
*@param{myStorage}storage
*@param{String}key
*@returns{*}
*/
get:function(storage,key){
returnstorage.getItem(key);
},
/**
*Proxysetter
*@param{myStorage}storage
*@param{String}key
*@param{*}val
*@returns{void}
*/
set:function(storage,key,val){
returnstorage.setItem(key,val);
}});
storage.bar="bar";
console.log(myStorage.getItem("bar"));//bar
myStorage.setItem("bar","baz");
console.log(storage.bar);//baz
SummaryThischaptergivespracticesandtricksonhowtousetheJavaScriptcorefeaturesforthemaximumeffect.Inthenextchapter,wewilltalkaboutmoduleconceptsandwewilldoawalkthroughonscopesandclosures.Thenextchapterwillexplainthescopecontextandthewaystomanipulateit.
Chapter2.ModularProgrammingwithJavaScriptEngineeringingeneralisallaboutsplittinglargetasksintosmallonesandcomposingthesolutionsofthesetasksinasystem.Insoftwareengineering,webreakthecode-baseintomodulesbyfollowingtheprinciplesoflowcouplingandhighcohesion.Inthischapter,wewilltalkabouttheapproachestocreatemodulesinJavaScriptbycoveringthefollowingtopics:
HowtogetoutofamessusingmodularJavaScriptHowtouseasynchronousmodulesinthebrowserHowtousesynchronousmodulesontheserverJavaScriptbuilt-inmodulesystemTranspilingCommonJSforin-browseruse
HowtogetoutofamessusingmodularJavaScriptHowmanydigitalphotosdoyouhave,probablythousands,ormore?Justimagineifyourimageviewerhadnocapacitytocategorize.Noalbums,nobooks,nocategories,nothing.Itwouldnotbeofmuchuse,doesit?Nowlet’sassumethatyouhaveaJavaScriptapplicationinasinglefileanditgrows.Whenitapproachesthousandormorethanathousandlinesofcode,howevergoodyourcodedesignis,fromamaintainabilityperspective,itstillturnsintoauselesspilelikethatenormouslistofuncategorizedphotos.Insteadofbuildingamonolithicapplication,wehavetowriteseveralindependentmodulesthatcombinetogethertoformanapplication.Thus,webreakacomplexproblemintosimplertasks.
ModulesSo,whatisamodule?Amoduleencapsulatescodeintendedforaparticularfunctionality.Amodulealsoprovidesaninterfacedeclaringwhatelementsthemoduleexposesandrequires.Amoduleisoftenpackagedinasinglefile,whichmakesiteasytolocateanddeploy.Awell-designedmoduleimplieslowcoupling(thedegreeofinterdependencebetweenmodules)andhighcohesion(thedegreetowhichtheelementsofamodulebelongtogether).
WhataretheadvantagesthatmodulesgiveusinJavaScript?
CleanerglobalscopeYouknowinJavaScriptanyassignationthatwedooutofanyfunctionscopemakesanewmemberoftheglobalscope(abuilt-inobjectwindowinabrowserorglobalinNode.js/Io.js).Therefore,wearealwaysatariskofoverridingaccidentallyanalreadydefinedproperty.Onthecontrary,whateverisdeclaredinamodulestayshereunlessweexplicitlyexportit.
PackagingcodeintofilesInserver-sidelanguages,applicationsconsistofnumerousfiles.Oneofthebestpracticeshereisthatafilemaycontainonlyoneclassandhaveonlyoneresponsibility.Besides,afully-qualifiedclassnamemustreflectitsfilelocation.Sowhenwerunintoaproblemonanobject,wecaneasilydeductwheretofinditssourcecode.WecandivideJavaScriptapplicationcodeintoseparatescripts,butthesewillsharethesamescopeandwon’tgiveusanyencapsulation.Moreover,whenthescriptsloadasynchronously,theinternaldependenciesmustbesolved,whichisnoteasytodo.Butifweusemodules,eachisgivenadedicatedfileandhasitsownscope.Amoduleloadertakescareofasynchronousdependencies.
ReuseImagine,whileworkingonaproject,youwroteacodethatsolvesonetask—let’ssayitprovidesaconvenientAPItomanagecookies.Whenswitchingtoanotherproject,yourealizethatyourcookiemanagerwouldquitebeinplacethere.Incaseofspaghetticode,youwouldhavetoextractthecomponentcode,decoupleit,andbindittothenewplace.Ifyouwrotethecomponentasadecently-designedmodule,yousimplytakeitandplugitin.
ModulepatternsWell,weknowthatmoduleshelpandwewanttousethem.HowdoweimplementamoduleinJavaScript?Firstofall,weneedtodetachthemodulecodefromtheglobalscope.Wecanonlydothisbyawrappingmodulecodewithafunction.AcommonpracticehereistogowithImmediatelyInvokedFunctionExpression(IIFE):
IIFE
(function(){
"usestrict";
//variabledefinedinsidethisscopecannotbeaccessedfromoutside
}());
Amodulemustalsohaveaccesspointswiththesurroundingenvironment.Inthesamewayasweusuallydealwithfunctions,wecanpassobjectreferencestoIIFEasarguments.
Import
(function($,Backbone){
"usestrict";
//modulebody
}(jQuery,Backbone));
Youmayhavealsoseenapatternwhereaglobalobject(window)ispassedwitharguments.Thiswaywedonotaccesstheglobalobjectdirectly,butbyareference.Thereisanopinionthattheaccessbyalocalreferenceisfaster.That’snotcompletelytrue.I’vepreparedaCodepenwithsometestsathttp://codepen.io/dsheiko/pen/yNjEar.ItshowsmethatinChrome(v45),alocalreferenceisreally~20percentfaster;however,inFirefox(v39),thisdoesn’tmakeanyconsiderabledifference.
Youcanalsorunapatternvariationwithundefinedintheparameterlist.Aparameterthatwasnotsuppliedwiththeargumentshasanundefinedvalue.So,wedothistricktoensurethatwegettheauthenticundefinedobjectinthescopeeveniftheglobalundefinedobjectisoverridden.
LocalReferences
(function(window,undefined){
"usestrict";
//modulebody
}(window));
Inordertoexposeamoduleelementoutsideitsscope,wecansimplyreturnanobject.Theresultofthefunctioncallcanbeassignedtoanexternalvariable,asshownhere:
Export
/**@modulefoo*/
varfoo=(function(){
"usestrict";
/**
*@private
*@typeString
*/
varbar="bar",
/**
*@type{Object}
*/
foo={
/**
*@public
*@type{String}
*/
baz:"baz",
/**
*@public
*@returns{String}
*/
qux:function(){
return"qux";
}
};
returnfoo;
}());
console.log(foo.baz);//baz
console.log(foo.qux());//qux
AugmentationSometimesweneedtomixthingsupinamodule.Forexample,wehaveamodulethatprovidescorefunctionality,andwewanttoplug-inextensionsdependingonthecontextofuse.Let’ssay,Ihaveamoduletocreateobjectsbasedonpseudo-classdeclarations.
Basically,duringinstantiationitautomaticallyinheritsfromaspecifiedobjectandcallstheconstructormethod.Inaparticularapplication,Iwantthistoalsovalidatetheobjectinterfaceagainstagivenspecification.So,Iplugthisextensiontothebasemodule.Howisitdone?Wepassthereferenceofthebasemoduletotheplugin.Thelinktotheoriginalwillbemaintained,sowecanmodifyitinthescopeoftheplugin:
/**@modulefoo*/
varfoo=(function(){
"usestrict";
/**
*@type{Object}
*/
varfoo={
/**
*@public
*@type{String}
*/
baz:"baz"
};
returnfoo;
}()),
/**@modulebar*/
bar=(function(foo){
"usestrict";
foo.qux="qux";
}(foo||{}));
console.log(foo.baz);//baz
console.log(foo.qux);//qux
ModulestandardsWe’vejustreviewedafewwaystoimplementmodules.However,inpractice,weratherfollowastandardizedAPI.Thesehavebeenprovedbyahugecommunity,adoptedbyreal-worldprojects,andrecognizablebyotherdevelopers.ThetwomostimportantstandardsthatweneedtokeepinmindareAMDandCommonJS1.1,andnowwewouldratherlookatatES6ModuleAPI,whichisgoingtobethenextbigthing.
CommonJS1.1loadsmodulessynchronously.Themodulebodyisexecutedonceduringthefirstloadandtheexportedobjectiscached.Itisdesignedforserver-sideJavaScriptandmostlyusedinNode.js/Io.js.
AMDloadsmodulesasynchronously.Themodulebodyisexecutedonceafterthefirstloadandtheexportedobjectisalsocached.Thisisdesignedforin-browseruse.AMDrequiresascriptloader.ThemostpopularareRequireJS,curl,lsjs,andDojo.
Soon,wecanexpectthescriptenginestogainnativesupportforJavaScriptbuilt-inmodules.TheES6modulestakethebestofthetwoworlds.SimilartoCommonJS,theyhaveacompactsyntaxandsupportforcyclicdependencies,andsimilartoAMD,themodulesloadasynchronouslyandtheloadingisconfigurable.
HowtouseasynchronousmodulesinthebrowserTogetagrasponAMD,wewilldoafewexamples.WewillneedscriptloaderRequireJS(http://requirejs.org/docs/download.html).SoyoucandownloaditandthenaddressthelocalversioninyourHTMLorgiveitanexternallinktoCDN.
Firstofall,let’sseehowwecancreateamoduleandrequestit.Weplacethemoduleinthefoo.jsfile.Weusethedefine()calltodeclarethemodulescope.Ifwepassanobjecttothis,theobjectsimplygetsexported:
foo.js
define({
bar:"bar",
baz:"baz"
});
Whenwepassafunction,itiscalledanditsreturnvalueisexported:
foo.js
define(function(){
"usestrict";
//Construction
return{
bar:"bar",
baz:"baz"
};
});
Nexttofoo.js,weplacemain.js.Thiscodecanbedescribedasfollows:callthegivencallbackwhenallthemodulessuppliedtothefirstargument(hereonlyfoo,whichmeans./foo.js)areloadedandavailable.
main.js
require(["foo"],function(foo){
"usestrict";
document.writeln(foo.bar);
document.writeln(foo.baz);
});
FromtheHTML(index.html),firstweloadRequireJSandthenmain.js:
index.html
<script
src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.18/require.min.j
s"></script>
<scriptsrc="main.js"></script>
Loadingscriptssynchronouslywhenwehavealoaderdoesn’tfeelright.However,wecandothiswiththeonlyscriptelementthat,inaddition,canbeforcedtoloadasynchronously:
index.html
<scriptdata-main="./main"async
src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.18/require.min.j
s"></script>
Withthedata-mainattribute,wetelltheloaderwhatmoduletoloadfirst,wheneverthemoduleisready.Aswefireupindex.html,wewillseethevaluesofthefoomodulepropertiesthatweimportedinmain.js.
index.htmloutputstheexportsoftheasynchronouslyloadedmodules:
Nowwefiddlewithmoredependencies.Sowecreatethebar.jsandbaz.jsmodules:
bar.js
define({
value:"bar"
});
baz.js
define({
value:"baz"
});
Wehavetomodifyfoo.jstoaccessthesemodules:
foo.js
define(["./bar","./baz"],function(bar,baz){
"usestrict";
//Construction
return{
bar:bar.value,
baz:baz.value
};
});
Asyoumayhavenoticed,therequire/definedependencylistsconsistsofmoduleidentifiers.Inourcase,allthemodulesandtheHTMLlocatedinthesamedirectory.Otherwise,weneedtobuildtheidentifiersbasedonrelativepaths(the.jsfileextensioncanbeomitted).IfyoumessupwithapathandRequireJScannotresolvethedependency,itfiresError:Scripterrorfor:<module-id>.Notofmuchhelp,isit?Youcanimproveerrorhandlingonyourown.Afunctionexpressionpassednexttothemodulescopecallbackreceivesanexceptionobjectasanargument.ThisobjecthasspecialpropertiessuchasrequireType(astringcontainingerrortypessuchtimeout,nodefine,scripterror)andrequireModules(anarrayofmoduleIDsaffectedbytheerror).
require(["unexisting-path/foo"],function(foo){
"usestrict";
console.log(foo.bar);
console.log(foo.baz);
},function(err){
console.log(err.requireType);
console.log(err.requireModules);
});
Inawell-graineddesign,modulesarenumerousandareallocatedtoadirectorytree.Inordertoavoidrelativepathcomputationeverytime,youcanconfigurethescriptloaderonce.Sotheloaderwillknowwheretofindthedependencyfilebyaspecifiedalias:
main.js
require.config({
paths:{
foo:"../../module/foo"
}
});
require(["foo"],function(foo){
"usestrict";
console.log(foo.bar);
console.log(foo.baz);
});
Thisgivesabonus.Nowifwedecidedtochangeamodulefilename,wedonotneedtomodifyeveryothermodulethatrequiresit.Wejustneedtochangetheconfiguration:
main.js
require.config({
paths:{
foo:"../../module/foo-v0_1_1"
}
});
require(["foo"],function(foo){
"usestrict";
console.log(foo.bar);
console.log(foo.baz);
});
Byconfiguring,wecanalsoaddressremotemodules.Forexample,herewerefertojQuery,butRequireJSknowsthemoduleendpointfromtheconfigurationand,therefore,
loadsthemodulefromCDN:
require.config({
paths:{
jquery:"https://code.jquery.com/jquery-2.1.4.min.js"
}
});
require(["jquery"],function($){
//usejQuery
});
ProsandconsThemainadvantageoftheAMDapproachisthatmodulesloadasynchronously.Italsomeansthatwhiledeploying,wedon’thavetouploadtheentirecode-base,butjustamoduleischanged.AndsinceabrowsercanhandlemultipleHTTPrequestssimultaneously,thiswayweimproveperformance.However,herecomesahugetrap.It’sreallyquicktoloadacodeinafewseparatepiecesinparallel.Butreal-worldprojectshavemanymoremodules.WiththeHTTP/1.1protocol,whichisstilldominantatthemoment,loadingallofthemwouldtakeunacceptablylongtime.UnlikethenewstandardSPDYandHTTP/2,HTTP/1.1doesn’tcopereallywellwithconcurrencyduringthedownloadingofapage,andincaseofasubstantiallylongqueue,thisresultsinhead-of-lineblocking(https://http2.github.io/faq/).RequreJSprovidesatool(http://requirejs.org/docs/optimization.html)tocombineabunchofmodules.Thiswaywedon’tneedtoloadeverysinglemodule,butonlyafewpackages.Thedependenciespackagedtogetherareresolvedsynchronously.So,onemaysaythatpartlyweabandonthemainbenefitofAMD—asynchronousloading.Meanwhile,wemuststillloada,usuallyquiteheavy,scriptloaderandwrapeverysinglemodulewiththedefine()callback.
Frommyexperience,IwouldratheradviceyoutogosynchronouswiththeCommonJSmodulescompiledintopackagescapableofin-browseruse.
Howto–usesynchronousmodulesontheserverThefollowingexamplesrequireNode.js.ItwilltakejustafewminutestoinstallNode.jsusingthepre-builtinstalleravailableathttps://nodejs.org/download/orevenfasterviaapackagemanagerathttps://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager.
Wewillstartbyputtingasimplelogicintoamodule:
foo.js
console.log("I'mrunning");
Nowwecancallthemodule:
main.js
require("./foo");
Inordertoruntheexample,wewillopentheconsole(underWindows,youcansimplyrunCMD.EXE,butIwouldrecommendanenhancedtoollikeCMDERavailableathttp://cmder.net/).Intheconsole,wetypethefollowing:
nodemain.js
AssoonasEnterispressed,theconsoleoutputsI’mrunning.Sowhenamoduleisrequested,itsbodycodeisinvoked.Butwhatifwerequestthemoduleseveraltimes?
main.js
require("./foo");
require("./foo");
require("./foo");
Theresultisthesame.ItoutputsI’mrunningonlyonce.Thisisbecausethemodulebodycodeisexecutedonlyoncewhenthemoduleisinitiallyrequested.Anexportedobject(probablyproducedbythebodycode)iscachedandactssimilartoasingleton:
foo.js
varfoo=newDate();
main.js
varfirst=require("./foo"),
second=require("./foo");
console.log(first===second);//true
Asyouwilllikelynotice,unlikeAMDwedon’tneedanywrappersinthemodules.Butisitstillisolatedfromaglobalscope?
foo.js
varfoo="foo";
main.js
require("./foo");
console.log(typeoffoo);//undefined
Anyvariablesdefinedinamodulescopearenotavailableoutsidethescope.However,ifyoureallywantanythingtobesharedbetweenthemodulevariablesbehindtheexposedinterface,youcandoitviaaglobalobject(Node.jsisanalogoustoanin-browserWindowsobject).
Sowhataboutexports?CommonJShasapreferenceforsingleexport.Weassigntomodule.exportsareferencetoatypeoravalue,andthiswillbethecachedreturnoftherequiredfunction.Ifwewantmultipleexports,wejustexportanobject:
foo.js
//modulelogic
module.exports={
bar:"bar",
baz:"baz"
};
main.js
varfoo=require("./foo");
console.log(foo.bar);//bar
console.log(foo.baz);//baz
ThefollowingisthemostcommoncaseinNode.jswhereanobjectconstructorisexported:
foo.js
varFoo=function(){
this.bar="bar";
}
module.exports=Foo;
Sothrougharequiredcall,wereceivetheconstructorfunctionwiththeprototypeandcancreateinstances:
main.js
varFoo=require("./foo"),
foo=newFoo();
console.log(foo.bar);//bar
Thesamewayaswerequestthefoomodulefrommain,wecanrequestfromothermodulesaswell:
bar.js
//modulelogic
module.exports="bar";
baz.js
//modulelogic
module.exports="baz";
foo.js
//modulelogic
module.exports={
bar:require("./bar"),
baz:require("./baz")
};
main.js
varfoo=require("./foo");
console.log(foo.bar);//bar
console.log(foo.baz);//baz
ButwhatifNode.jsrunsintocyclicdependencies?Whatifwerequestbackthecallerfromthecalledmodule?Nothingdramatichappens.Asyoumayremember,amodulecodeisexecutedonlyonce.Soifwerequestmain.jsfromfoo.jsaftermain.jsisalreadyperformed,itsbodycodeisn’tinvokedanymore:
foo.js
console.log("Runnnigfoo.js");
require("./main");
main.js
console.log("Runnnigmain.js");
require("./foo");
Whenwerunmain.jswithNode.js,wegetthefollowingoutput:
Runnnigmain.js
Runnnigfoo.js
ProsandconsCommonJShasaconciseandexpressivesyntax.It’sveryeasytouse.Unittestsareusuallywrittentoruninthecommandlineandpreferablyareapartofcontinuousintegration.Awell-designedCommonJSmodulemakesaperfecttestunit,whichyoucanaccessdirectlyfromaNode.js-driventestframework(forexample,Mocha)faroutoftheapplicationcontext.However,CommonJSimpliessynchronousloading,whichisnotsuitableinabrowser.Ifwewanttobypassthislimitation,wehavetotranspilemodulesourcesintoasinglescriptthatresolvesmoduledependenciesinternallywithoutloading(see“TraspilingCommonJSforin-browseruse”).
UMDIfyouwantyourmoduletobeacceptablebothinabrowserasAMDandontheserverasCommonJS,thereisatrick(https://github.com/umdjs/umd).Byaddingawrapperfunction,youcandynamicallybuildtheexportinadesiredformatdependingontheruntimeenvironment.
JavaScript’sbuilt-inmodulesystemWell,bothAMDandCommonJSarecommunitystandardsandnotapartofthelanguagespecification.However,withEcmaScript6thedition,JavaScriptacquireditsownmodulesystem.Atthemoment,nobrowseryetsupportsthisfeature,sowehavetoinstalltheBabel.jstranspilertofiddlewiththeexamples.
SincewealreadyhaveNode.jsthatisdistributedwithNPM(theNode.jspackagemanager),wenowcanrunthefollowingcommand:
npminstallbabel-g
NamedexportsNowwecanwriteamoduleasfollows:
foo.es6
exportletbar="bar";
exportletbaz="baz";
InES6,wecanexportmultipleelements.Anydeclarationprefixedwiththekeywordexportbecomesavailableforimport:
main.es6
import{bar,baz}from"./foo";
console.log(bar);//bar
console.log(baz);//baz
Sincewedon’tyethaveanysupportforES6modulesinthebrowser,wewilltranspilethemintoCommonJSorAMD.HereBabel.jshelpsus:
babel--modulescommon*.es6--out-dir.
Bythiscommand,wemadeBabel.jstranslateallthe*.es6filesofthecurrentdirectoryintoCommonJSmodules.So,wecanrunthederivedmain.jsmodulewithNode.js:
nodemain.js
Similarly,wetranslateES6modulestoAMD:
babel--modulesamd*.es6--out-dir.
index.html
<scriptdata-main="./main"
src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.18/require.min.j
s"></script>
Inthepreviousexample,weenlistedournamedexportsintheimportstatement.Wecouldalsoimporttheentiremoduleandrefertothenamedexportsasproperties:
DefaultexportBesides,wecanalsodoadefaultexport.ThisishowusuallyexportsaredoneinNode.js:
foo.es6
exportdefaultfunctionfoo(){return"foo";}
main.es6
importfoofrom"./foo";
console.log(foo());//foo
Weexportedafunctionandcamewiththeimport.Thiscouldalsobeaclassoranobject.
InAMD,wereceiveexportsascallbackarguments,andinCommonJS,aslocalvariables.ThoughES6doesn’texportvalues,butitexportsthesocalledbindings(references)thatareimmutable.Youcanreadtheirvalues,butifyoutrychangingthem,yougetatypeerror.Babel.jstriggersthiserrorduringcompilation:
foo.es6
exportletbar="bar";
exportfunctionsetBar(val){
bar=val;
};
main.es6
import{bar,setBar}from"./foo";
console.log(bar);//bar
setBar("baz");
console.log(bar);//baz
bar="qux";//TypeError
ThemoduleloaderAPIInadditiontodeclarativesyntaxinaseparatespecification(https://github.com/whatwg/loader/),ES6offersusaprogrammaticAPI.Itallowsustoprogrammaticallyworkwithmodulesandconfiguremoduleloading:
System.import("./foo").then(foo=>{
console.log(foo);
})
.catch(err=>{
console.error(err);
});
UnlikeNode.js,theES6modules,duetotheirdeclarativenature,requireimportsandexportsatthetoplevel.So,thiscannotbeconditional.However,withthepragmaticloaderAPI,wecandootherwise:
Promise.all(["foo","bar","baz"]
.map(mod=>System.import(mod))
)
.then(([foo,bar,baz])=>{
console.log(foo,bar,baz);
});
Herewedefinedacallbackthatisinvokedonlywhenallofthethreespecifiedmodulesareloaded.
ConclusionBothAMDandCommonJSareinterimstandards.AssoonastheJavaScriptbuilt-inmodulesystemgetswidersupportinscriptengines,wedon’treallyneedthemanymore.TheES6modulesloadasynchronously,andtheloadingcanbeconfiguredsimilartoAMD.TheyalsohaveacompactandexpressivesyntaxandsupportforcyclicdependenciessimilartoCommonJS.Inaddition,ESprovidesdeclarativesyntaxforstaticmodulestructure.Suchstructurecanbestaticallyanalyzed(staticchecking,linting,optimization,andsoon).ES6alsoprovidesaprogrammaticloaderAPI.Soyoucanconfigurehowmodulesareloadedandloadmodulesconditionally.Besides,ES6modulescanbeextendedwithmacrosandstatictypes.
Whileeverythinglookssounclouded,thereisstillaflyintheointment.ES6modulescanbepre-loadedsynchronously(with<scripttype="module"></script>),butoftenthereisasynchronousloadingandthisbringsustothesametrapasinthecaseofAMD.NumerousrequestsoverHTTP/1.1causeaharmfuleffectonuserresponsetime(https://developer.yahoo.com/performance/rules.html).Ontheotherhand,SPDYandHTTP/2thatallowmultiplerequestsperTCPconnectionaregettingwidersupportandeventuallywilltaketheplaceofthedubiousHTTP/1.x.Furthermore,W3CworksonastandardcalledPackagingontheWeb(https://w3ctag.github.io/packaging-on-the-web/)thatdescribeshowarchivedfiles(scripts)canbeacceptedfromaURL(hash).So,wewillbeabletobundletheentiredirectorywithmodulesintoanarchive,deploy,andaddresstheminthesamewayaswedowhenwehavetheminadirectory.
TranspilingCommonJSforin-browseruseWhileHTTP/2andPackagingontheWebarestillontheirway,weneedfastmodularapplications.Asitwaspreviouslymentioned,wecandividetheapplicationcodeintoCommonJSmodulesandtranspilethemforin-browseruse.ThemostpopularCommonJStranspilerissurelyBrowserify(http://browserify.org).TheinitialmissionofthistoolwastomakeNode.jsmodulesreusable.Theyquitesucceededinthis.Itmayfeellikemagic,butyoucanreallyuseEventEmitterandsomeotherNode.jscoremodulesontheclient.However,withthemainfocusonNode.jscompatibility,thetoolprovidestoofewoptionsforCommonJScompilation.Forexample,ifyouwantdependencyconfiguration,youhavetouseaplugin.Inareal-worldproject,youwilllikelyendupwithmultipleplugins,whereeachhasaspecificconfigurationsyntax.Sothesetupingeneralgetsover-complicated.Rather,we’llexaminehereanothertoolcalledCommonJSCompiler(https://github.com/dsheiko/cjsc).ThisisaconsiderablysmallutilitydesignedtobringCommonJSmodulesintothebrowser.Thetoolisveryeasytoconfigureanduse,whichmakesitagoodchoicetoillustratetheconcept.
Firstofall,weinstallcjsc:
npminstallcjsc-g
NowwecantakeanexamplefromtheHowtosynchronousmodulesontheserversectionandtranspileitforin-browseruse:
bar.js
//modulelogic
module.exports="bar";
foo.js
//modulelogic
module.exports={
bar:require("./bar")};
main.js
varfoo=require("./foo");
document.writeln(foo.bar);//bar
Thestartingpointismain.js.So,wetellcjsctobundlethismodulewithalltherequireddependenciesrecursivelyintobundle.js:
cjscmain.js-obundle.js
Let’stakealookintothegeneratedfile.cjscreplacedalltherequirecallswithcustom_requireandputthemintothebeginning_requirefunctiondefinition.ThislittletrickallowsyoutorunthecompiledcodeinaNode.js/Io.jsfriendlyenvironmentsuchasNW.js,wheretherequirefunctionisstillneededforlocalpackages.Everymoduleiswrappedinafunctionscopesuppliedwithmodulerelevantobjects(exportsandmodules)plusglobal,whichisareferencetotheglobalobject(window).
CompiledCode
_require.def("main.js",function(_require,exports,module,global)
{
varfoo=_require("foo.js");
console.log(foo.bar);//bar
console.log(foo.baz);//baz
returnmodule;
});
ThegeneratedcodeisagenericJavaScriptthatwecansurelyaddressfromtheHTML:
index.html
<scriptsrc="bundle.js"></script>
OursourcesarestillinaCommonJSmodule.ThismeansthatwecanaccessthemdirectlyfromaNode.js-basedframeworkforunit-testing.TheofficialsiteforMocha.jsTestishttp://mochajs.org/:
varexpect=require("chai").expect;
describe("Foomodule",function(){
it("shouldbypasstheexportofbar",function(){
varfoo=require("./foo");
expect(foo).to.have.property("bar");
expect(foo.bar).to.eql("bar");
});
});
cjschasanumberofoptions.Butinarealproject,typingalongcommand-linewitheverybuildwouldbeannoyingandunproductive:
cjscmain-module.js-obuild.js--source-map=build/*.map\
--source-map-root=../src-M--banner="/*!pkgv.0.0.1*/"
ThatiswhyweusetaskrunnerssuchasGrunt,Gulp,Cake,andBroccoli.Grunt(http://gruntjs.com)isthemostpopulartaskrunneratthemomentandhasanoverwhelmingnumberofpluginsavailable(seetheGruntversusGulpinfographicathttp://sixrevisions.com/web-development/grunt-vs-gulp/).So,weinstallthegruntcommand-lineinterfaceglobally:
npminstall-ggrunt-cli
InordertosetupaGruntproject,weneedtwoconfigurationfiles,package.json(https://docs.npmjs.com/files/package.json)andtheGruntfile.jsfile.ThefirstonecontainsmetadataaboutNPMpackagesrequiredtorunGrunttasks.Thesecondisneededtodefineandconfigurethetasks.
Herewecanstartwithaveryminimalisticpackage.jsonthathasonlyanarbitraryprojectnameanditsversioninasemver(http://semver.org/)format:
package,json
{
"name":"project-name",
"version":"0.0.1"
}
NowwecaninstalltherequiredNPMpackages:
npminstall--save-devgrunt
npminstall--save-devgrunt-cjsc
ThuswegetalocalGruntandaGruntpluginforCommonJscompiler.The--save-devspecialoptioncreatesdevDependencies(ifitdoesn’texist)inthepackage.jsonsectionandpopulatesitwiththeinstalleddependency.Soforinstance,whenwepulltheprojectsourcesfromaversioncontrolsystem,wecanrestoreallthedependenciesbysimplyrunningnpminstall.
InGruntfile.js,wehavetoloadthealreadyinstalledgrunt-cjscpluginandconfigureataskcalledcjsc.Inpractice,wewillneedatleasttwotargetsthatprovidedifferentconfigurationsforthistask.Thefirstone,cjsc:debug,runscjsctoproduceuncompressedcode,providedwithsourcemap.Thesecondone,cjsc:buildisusedtoprepareassetsfordeployment.Sowegetminifiedcodeinbundle.js:
Gruntfile.js
module.exports=function(grunt){
//Projectconfiguration.
grunt.initConfig({
pkg:grunt.file.readJSON("package.json"),
cjsc:{
//Atargettogenerateuncompressedcodewithsourcesmaps
debug:{
options:{
sourceMap:"js/*.map",
sourceMapRoot:"src/",
minify:false
},
files:{"js/bundle.js":"js/src/main.js"}
},
//Atargettobuildprojectforproduction
build:{
options:{
minify:true,
banner:"/*!<%=pkg.name%>-v<%=pkg.version%>-"+
"<%=grunt.template.today(\"yyyy-mm-dd\")%>*/"
},
files:{"js/bundle.js":"js/src/main.js"}
}
}
});
//Loadthepluginthatprovidesthetask.
grunt.loadNpmTasks("grunt-cjsc");
//Makeitdefaulttask
grunt.registerTask("default",["cjsc:build"]);
};
Asyoucanseefromtheconfiguration,cjscisintendedtotranspilejs/src/main.jsintojs/bundle.js.Sowecantakethemoduleofthepreviousexampleandcopytheminto./js/src.
Now,whenwehaveeverythinginplace,wewillrunatask.Forexample,seethefollowing:
gruntcjsc:debug
Asmentionedearlier,wecanconfiguredependencymappingwithcjsc.WejustneedtodescribethedependenciesinanobjectliteralthatcanbesuppliedtocjscasaJSON-fileinthecommand-lineinterfaceorinjectedintoaGruntconfiguration:
{
"jquery":{
"path":"./vendors/jQuery/jquery.js"
},
"underscore":{
"globalProperty":"_"
},
"foo":{
"path":"./vendors/3rdpartyLib/not-a-module.js",
"exports":["notAModule"],
"imports":["jquery"]
}
}
Herewedeclarethejqueryalias(shortcut)foramodulelocatedin./vendors/jQuery/jqueiry.js.Wealsostatethatagloballyexposed"_"(Underscore.js)libraryhastobetreatedasamodule.Attheend,wespecifythepath,exports,andimportsforathird-partycomponent.Thus,wegetthisintheapp(withoutinterventioninitscode)asamodule,thoughit’snotamodule:
cjscmain.js-obundle.js--config=cjsc-conig.json
AlternativelywecanusethefollowingGruntconfiguration:
grunt.initConfig({
cjscmain.js-obundle.js--config=cjsc-conig.json
Gruntconfiguration
grunt.initConfig({
cjsc:{
build:{
options:{
minify:true,
config:require("fs").readFileSync("./cjsc-conig.json")
}
},
files:{"js/bundle.js":"js/src/main.js"}
}
});
BundlingES6modulesforsynchronousloadingWell,aswementionedintheJavaScriptbuilt-inmodulesystemsection,ES6modulesaregoingtobereplacetheAMDandCommonJSstandards.Moreover,wecanalreadywriteES6codeandtranspileitintoES5fornow.AssoonasthesupportforES6acrossscriptagentsisgoodenough,wetheoreticallycanuseourcodeasitis.However,whataboutperformance?Infact,wecancompileES6modulesinCommonJSandthenbundlethemwithcjscforin-browseruse:
foo.es6
exportletbar="bar";
exportletbaz="baz";
main.es6
import{bar,baz}from"./foo";
document.writeln(bar);//bar
document.writeln(baz);//baz
First,wecompileES6intoCommonJSmodules:
babel--modulescommon*.es6--out-dir.
Then,webundleCommonJSmodulesintoascriptsuitableforin-browseruse:
cjscmain.js-obundle.js-M
SummaryModularprogrammingisaconceptcloselyrelatedtoOOPthatencouragesustostructurecodeforbettermaintainability.Inparticular,JavaScriptmodulesprotectglobalscopefrompollution,divideapplicationcodeintomultiplefiles,andallowthereuseofapplicationcomponents.
ThetwomoduleAPIstandardsthataremostlyusedatthemomentareAMDandCommonJS.Thefirstonethatisdesignedforin-browseruseassumesasynchronousloading.Thesecondissynchronousandintendedforserver-sideJavaScript.However,youshouldknowthatAMDhasasubstantialflaw.Awell-grainedapplicationdesignwithaplentyofmodulesoverHTTP/1.1maycauseadisasterintermsofapplicationperformance.Thisisthemajorreasonwhy,recently,thepracticeoftranspilingCommonJSmodulesforin-browseruseisontherise.
BoththeseAPIsshallbeconsideredasinterimstandardsbecausetheupcomingES6modulesstandardismeanttoreplacethem.Atthemoment,therearenoscriptenginessupportingthisfeature,buttherearetranspilers(forexample,Babel.js)thatallowsthetranslationofES6modulesintoCommonJsorAMD.
Chapter3.DOMScriptingandAJAXWhenitcomestoDocumentObjectModel(DOM)manipulationandAJAX,thefirstinstinctcouldbetousejQueryorZepta.Butdoesn’titbotheryouthatyouloadaweightythird-partylibraryforcommontasks,whenabrowserprovideseverythingthatyouneed?SomepeoplepulledinjQueryforcross-browsercompatibility.Well,thelibraryisknowntofixthebrokenDOMAPI.ThiswasreallyhelpfulwhenwesupportedbrowsersasoldasIE7.However,todaywehardlyneedtocareaboutlegacybrowserswhentheirusageshareislessthan0.1percent(http://www.w3schools.com/browsers/browsers_explorer.asp).ModernbrowsersarequiteconsistentinthesupportofWebAPI.Byandlarge,cross-browsercompatibilityisnotanissueanymore.
ThesecondandthemostcommonexcuseisthatthelibrarysimplifiestheamountofcodeyouhavetowritetoqueryandmanipulatetheDOM.Itreallysimplifiesthecodetosomedegree,butthedrawbackisthatnowadayswehaveagenerationofdeveloperswhodon’tknowJavaScriptandWebAPI,butonlyjQuery.Manyofthemcannotsolveasimpletaskwithoutthelibraryandhavenoideawhatactuallyhappenswhentheycallthelibrarymethods.Goodcodemeansportabilityandhighperformance.OnecanhardlyachievethiswithoutaknowledgeofnativeAPI.
Sointhischapter,wewillexaminethenativewayofdealingwithDOMandAJAXwithafocusonhigh-performance.
Thischapterwillcoverthefollowingtopics:
High-speedDOMoperationsCommunicationwiththeserver
High-speedDOMoperationsInordertodealwiththeDOMefficiently,weneedtounderstanditsnature.TheDOMisatreestructurethatrepresentsthedocumentthatisopeninthebrowser.EveryelementoftheDOMisanobjectthatiscallednode.
Everynodebeinganobjecthaspropertiesandmethods(https://developer.mozilla.org/en/docs/Web/API/Node).Therearedifferenttypesofnode.Intheprecedingimage,youcanseeadocumentnode,elementnodes,andtextnodes.Inreality,thetreemayalsocontainspecificnodetypessuchascommentnodes,doctypenodes,andothers.Toillustratetherelationshipswithinthetree,wecansaythatHTMLhastwochildnodesHEADandBODY,whichrelatetoeachotherassiblings.Obviously,HTMListheparentnodetoHEADandBODY.Wecanusetheserelationsthatareaccessiblevianodepropertiestonavigatethroughthetree:
varhtml=document.documentElement;
console.log(html.nodeName);//HTML
varhead=html.childNodes[0];
console.log(head.nodeName);//HEAD
console.log(head.parentNode===html);//true
Thispartisclear,butifwerequestthenextsiblingtobeHEADinsteadofBODY.wewillgetatextnodewithwhitespacesinthecontent(nodeValue):
varsibling=head.nextSibling;
//thesameashtml.childNodes[1]
console.log(sibling.nodeName);//#text
console.dir(sibling.nodeValue);//"\n"
InHTML,weusuallyseparateelementswithspaces,TABs,andLineFeedsforbetterreadabilityandthesealsoformapartofDOM.Sotoaccesselements,weratherusedocumentandelementmethods.
TraversingtheDOMSurelyyouknowhowtofindanelementbyID(document.getElementById)orbytagname(document.getElementsByTagName).YoucanalsosearchforanelementbyaCSSselector(document.querySelector):
<articleid="bar">
<h2>Loremipsum</h2>
</article>
vararticle=document.querySelector("#bar"),
heading=article.querySelector("h2");
Aselectorbuildsfromoneormanytype(tag)selectors,classselectors,IDselectors,attributeselectors,orpseudo-class/elementselectors(http://www.w3.org/TR/CSS21/selector.html%23id-selectors).Consideringthecombinations(tomatchagroup,descendants,orsiblings),thisgivesquiteanumberofpossibleoptions.SoitcanbehardtopickastrategytobindHTMLelementsfromJavaScript.Myadvicewouldbetoalwaysusethedata-*attributeselectors:
<articledata-bind="bar">
<h2data-bind="heading">Loremipsum</h2>
</article>
vararticle=document.querySelector("[data-bind=\"bar\"]"),
heading=article.querySelector("[data-bind=\"heading\"]");
ThiswayweareindependentfromtheHTMLstructure.Ifwechangetags,forexampleforbettersemantics,nothingbreaksontheJavaScriptside.WeareindependentfromCSSclassesandthismeansthatwecansafelyrefactorCSS.AndwearenotlimitedbyID,whichissupposedtobeuniqueperdocument.
WhilequerySelectortakesthefirstelementintheDOMtomatchtheselector,querySelectorAllretrievesallofthem:
<uldata-bind="bar">
<lidata-bind="item">Loremipsum</li>
<lidata-bind="item">Loremipsum</li>
<lidata-bind="item">Loremipsum</li>
</ul>
varul=document.querySelector("[data-bind=\"bar\"]"),
lis=ul.querySelectorAll("[data-bind=\"item\"]");
console.log(lis.length);
ThefoundelementsarerepresentedasaNodeList.Itlookslikeanarray,butit’snot.It’salivecollectionthatisbeingupdatedwitheveryDOMreflow.Considerthefollowingexample:
vardivs=document.querySelectorAll("div"),i;
for(i=0;i<divs.length;i++){
document.appendChild(document.createElement("div"));
}
Theprecedingcodecausesaninfiniteloop,becausewheneverweaccessthenextelementofthecollection,onenewelementisappendedtothecollection,divs.lengthincremented,andwenevermeettheloopcondition.
It’simportanttoknowthataniterationthroughalivecollection(NodeList,HTMLCollection)isslowandconsiderablyresource-expensive.Ifyoudon’tneedittobelive,justconvertthecollectionintoanarraysuchas[].slice.call(nodeList),ascoveredinChapter1,DivingintoJavaScriptCore.InES6,thiscanbedonewiththe[...nodeList]spreadoperator:
varul=document.querySelector("[data-bind=\"bar\"]"),
lis=ul.querySelectorAll("[data-bind=\"item\"]");
console.log([].slice.call(lis));//intoarrayES5way
console.log([...lis]);//intoarrayES6way
Inadditiontoquerying,wecantestwhetherafoundelementmatchesagivenselector:
console.log(el.matches(".foo>.bar"));
console.log(input.matches(":checked"));
ChangingtheDOMWell,nowweknowhowtofindelementsintheDOM.Let’sseehowwecandynamicallyinsertnewelementsintotheDOMtree.Therearedifferentways.WecansimplysetnewHTMLcontentwiththeel.innerHTMLmethod:
vartarget=document.getElementById("target");
target.innerHTML="<div></div>";
Otherwise,wecancreateanode(document.createElement)andinjectitintotheDOM(el.appendChild):
vartarget=document.getElementById("target"),
div=document.createElement("div"),
target.appendChild(div);
Hereyoushouldrememberthateverytimewechangeel.innerHTMLorappendachildtoanelement,wecauseDOMreflow.Whenthishappensrepeatedlyinaloop,itcanslowdowntheapplication.
WhenwepassHTMLviael.innerHTML,thebrowserfirsthastoparsethestring.It’saresource-consumingoperation.However,thiswillgomuchfasterifwecreateelementsexplicitly.Ifweareproducingabatchofsimilarelements,theflowcanbeoptimizedfurther.Insteadofcreatingeveryelementinaloop,wecanclonetheonecreatedoriginally(el.cloneNode),whichiswayfaster:
vartarget=document.getElementById("target"),
/**
*Createacomplexelement
*@returns{Node}
*/
createNewElement=function(){
vardiv=document.createElement("div"),
span=document.createElement("span");
span.appendChild(document.createTextNode("Bar"));
div.appendChild(span);
returndiv;
},
el;
el=createNewElement();
//loopbegins
target.appendChild(el.cloneNode(true));
//loopends
Ontheotherhand,wecancreateadocumentfragment(document.createDocumentFragment)andduringtheloopappendthecreatednodestothefragment.DocumentfragmentisasortofavirtualDOM,whichwemanipulateinsteadoftherealone.Oncewe’redone,wecaninjectthedocumentfragmentasabranchtotherealDOM.Bycombiningthistechniqueandcloning,wearesupposedtogainintermsofperformance.Ineffect,thisisnotcertain(http://codepen.io/dsheiko/pen/vObVOR).Forexample,inWebKitbrowsers,virtualDOM(document.createDocumentFragment)runs
slowerthantherealone.
Aswe’vedonewithperformance,let’sfocusonaccuracy.Ifweneedtoinjectanelementtoanexactposition(forexample,betweenthefooandbarnodes),el.appendChildisn’ttherightmethod.Wehavetogowithel.insertBefore:
parent.insertBefore(el,parent.firstChild);
ToremoveaparticularelementfromtheDOM,wedothefollowingtrick:
el.parentNode.removeChild(el);
Inaddition,wecanreloadanelement,forexample,toresetallthesubscribedlisteners:
functionreload(el){
varelClone=el.cloneNode(true);
el.parentNode&&el.parentNode.replaceChild(elClone,el);
}
StylingtheDOMWhenitcomestostyling,wehavetogowithCSSclasseswhereveritispossible.Thisprovidesbettermaintainability—inheritance,composition,andconcernseparation.Yousurelyknowhowtoassignintendedclassestoanelementviatheel.classNameproperty.However,intherealworld,theel.classListobjectismuchmoreuseful:
el.classList.add("is-hidden");
el.classList.remove("is-hidden");
varisAvailable=true;
el.classList.toggle("is-hidden",!isAvailable);
if(el.classList.contains("is-hidden")){}
Here,inadditiontotheobviousadd/remove/containsmethods,wealsousetoggle.ThismethodeitheraddsorremovesthespecifiedclassdependingontheBooleanpassedasthesecondargument.
Sometimesweneedtomanipulatestylesexplicitly.ApartofDOMthatiscalledCSSObjectModel(CSSOM)providesaninterfacetomanipulatetheCSS.Thus,wecanreadorsetdynamicstylinginformationonanelementusingtheel.styleproperty:
el.style.color="red";
el.style.fontFamily="Arial";
el.style.fontSize="1.2rem";
Alesserknowntechniqueistochangetheactualtextofthestylerule:
el.style.cssText="color:red;font-family:Arial;font-size:1.2rem;";
Asyoucansee,thesecondapproachisnotthatflexible.Youcannotchangeoraccessasingledeclaration,butonlytheentirerule.However,stylingthiswayissubstantiallyfaster(http://codepen.io/dsheiko/pen/qdvWZj).
Whileel.stylecomprisesexplicitstylesofanelement,window.getComputedStylereturnsinherited(computed)styles:
varel=document.querySelector("h1"),
/**
*window.getComputedStyle
*@param{HTMLElement}el
*@param{String}pseudo-pseudo-elementselectorornull
*forregularelements
*@return{CSSStyleDeclaration}
*/
css=window.getComputedStyle(el,null);
console.log(css.getPropertyValue("font-family"));
Thecaseswe’vejustexaminedrefertoinlinestyles.Infact,wecanaccessexternalorinternalstylesheetsaswell:
<styletype="text/css">
.foo{
color:red;
}
</style>
<divclass="foo">foo</div>
<scripttype="text/javascript">
varstylesheet=document.styleSheets[0];
stylesheet.cssRules[0].style.color="red";
//or
//stylesheet.cssRules[0].style.cssText="color:red;";
</script>
Whywouldwedoso?Therearespecialcases.Forexample,ifwewanttomodify,let’ssay,pseudo-elementstyle,wehavetoinvolvestylesheets:
varstylesheet=document.styleSheets[0];
stylesheet.addRule(".foo::before","color:green");
//or
stylesheet.insertRule(".foo::before{color:green}",0);
MakinguseofattributesandpropertiesHTMLelementshaveattributesandwecanaccessthemfromJavaScript:
el.setAttribute("tabindex","-1");
if(el.hasAttribute("tabindex")){}
el.getAttribute("tabindex");
el.removeAttribute("tabindex");
WhileelementattributesaredefinedbyHTML,thepropertiesaredefinedbyDOM.Andthismakesadifference.Forexample,ifyouhaveaninput,initiallybothattributeandproperty(el.value)hasthesamevalue.However,whenauserorascriptchangesthevalue,theattributeisnotaffectedbutthepropertyis:
//attribute
console.log(input.getAttribute("value"));
//property
console.log(input.value);
Asyoumaylikelyknow,inadditiontoglobalattributes,thereisaspecialtype—customdataattributes.TheseattributesaremeanttoprovideanexchangeofproprietaryinformationbetweentheHTMLanditsDOMrepresentation,whichisusedbyscripts.Thegeneralideaisthatyoudefineacustomattributesuchasdata-fooandsetavaluetoit.Thenfromascript,weaccessandchangetheattributeusingtheel.datasetobject:
console.log(el.dataset.foo);
el.dataset.foo="foo";
Ifyoudefineamultipartattributesuchasdata-foo-bar-baz,thecorrespondingdatasetpropertywillbefooBarBaz:
console.log(el.dataset.fooBarBaz);
el.dataset.fooBarBaz="foo-bar-baz";
HandlingDOMeventsPlentyofeventshappeninthebrowser.Itcanbedeviceevents(forexample,thedevicechangespositionororientation),windowevents(forexample,windowsize),aprocess(forexample,pageloading),mediaevents(forexample,videopaused),networkevents(connectionstatuschanged),andofcourse,userinteractionevents(click,keyboard,mouse,andtouch).Wecanmakeourcodelistentotheseeventsandcallthesubscribedhandlerfunctionswhentheeventsoccur.TosubscribeforaneventonaDOMelement,weusetheaddEventListenermethod:
EventTarget.addEventListener(<event-name>,<callback>,<useCapture>);
Intheprecedingcode,EventTargetcanbeawindow,document,anelement,orotherobjectssuchasXMLHttpRequest.
useCaptureisaBooleanbywhichyoucanspecifythewayyouwanttheeventtopropagate.Forexample,auserclicksabutton,whichisinaform,andwehavesubscribedhandlerstobothelementsforthisclickevent.WhenuseCaptureistrue,thehandleroftheformelement(ancestor)willbecalledfirst(capturingflow).Otherwise,formshandlerwillbecalledafterthebutton’shandler(bubblingflow).
callbackisafunctionthatiscalledwhenaneventfires.ItreceivestheEventobjectasanargument,whichhasthefollowingproperties:
Event.type:ThisisthenameoftheeventEvent.target:ThisistheeventtargetonwhichtheeventoccurredEvent.currentTarget:Thisistheeventtargettowhichthelistenerwasattached(targetandcurrentTargetmaydifferwhenweattachthesameeventhandlertomultipleelementsasmentionedathttps://developer.mozilla.org/en-US/docs/Web/API/Event/currentTarget)Event.eventPhase:Thisindicateswhichphaseoftheeventflowisbeingevaluated(none,capturing,attarget,orbubbling)Event.bubbles:ThisindicateswhetherornottheeventisabubblingoneEvent.cancelable:ThisindicateswhetherornotthedefaultactionfortheeventcanbepreventedEvent.timeStamp:Thisspecifiestheeventtime
Eventalsohasthefollowingmethods:
Event.stopPropagation():Thisstopsfurtherpropagationoftheevent.Event.stopImmediatePropagation():Ifwehavemultiplelistenerssubscribedtothesameeventtarget,aftercallingthismethodnoneofremaininglistenerswillbecalled.Event.preventDefault():Thispreventsthedefaultaction.Forexample,ifit’saclickeventonabuttonofthesubmittype,bycallingthismethodwepreventitfromsubmittingtheformautomatically.
Let’stryitnowinpractice:
<formaction="/">
<buttontype="submit">Clickme</button>
</form>
<script>
varbtn=document.querySelector("button")
onClick=function(e){
e.preventDefault();
console.log(e.target);
};
btn.addEventListener("click",onClick,false);
</script>
Here,wesubscribedanonClicklistenertoaclickeventonabuttonelement.Whenthebuttonisclicked,itshowsintheJavaScriptconsolethebuttonelementthattheformisn’tsubmitted.
Ifwewanttosubscribeforkeyboardevents,wecandothisasfollows:
addEventListener("keydown",function(e){
varkey=parseInt(e.key||e.keyCode,10);
//Ctrl-Shift-i
if(e.ctrlKey&&e.shiftKey&&key===73){
e.preventDefault();
alert("Ctrl-Shift-Lpressed");
}
},false);
Themostcommonexampleofprocesseventsisthedocumentreadystatuschange.WecanlistentotheDOMContentLoadedorloadevents.Thefirstoneisfiredwhenthedocumenthasbeencompletelyloadedandparsed.Thesecondonealsowaitsforstylesheets,images,andsubframestofinishloading.Here,thereisaquirk.WehavetocheckreadyState,becauseifweregisteralistenertoaneventafterithasbeenprobablyfired,thecallbackwillbeneverinvoked:
functionready(cb){
if(document.readyState!=="loading"){
cb();
}else{
document.addEventListener("DOMContentLoaded",cb);
}
}
Well,weknowhowtosubscribetoDOMeventswiththeEventTarget.addEventListenermethod.TheEventTargetobjectsalsohaveamethodtounsubscribefromthelisteners.Forexample,seethefollowing:
btn.removeEventListener("click",onClick);
IfwewanttotriggeraDOMevent,forinstancetoemulateabuttonclick,wehavetocreateanewEventobject,setitup,anddispatchontheelementwhenwewanttheeventtofire:
varbtn=document.querySelector("button"),
//CreateEventobject
event=document.createEvent("HTMLEvents");
//Initializeacustomeventthatbubblesupandcannotbecanceled
event.initEvent("click",true,false);
//Dispatchtheevent
btn.dispatchEvent(event);
Inthesameway,wecancreateourcustomevent:
varbtn=document.querySelector("button"),
//CreateEventobject
event=document.createEvent("CustomEvent");
//Subscribetotheevent
btn.addEventListener("my-event",function(e){
console.dir(e);
});
//Initializeacustomeventthatbubblesupandcannotbecanceled
event.initEvent("my-event",true,false);
//Dispatchtheevent
btn.dispatchEvent(event);
CommunicatingwiththeserverManypeopleusethird-partylibrariestomakeanyrequesttoaserver.Butdoweneedtheselibraries?Let’sexamineinthefollowinghowAJAXcanbeusednativelyandwhatwillbethenextcommunicationAPI.
XHRXMLHttpRequest(XHR)isthemainAPIinJavaScripttoexchangedatabetweenclientandserver.XHRwasfirstlypresentedbyMicrosoftinIE5viaActiveX(1999)andhadaproprietarysyntaxinIEbrowseruntilversion7(2006).ThisledtocompatibilityissuesthatcalledforththeriseofAJAX-librariessuchasPrototypeandjQuery.Today,supportforXHRisconsistentacrossallthemajorbrowsers.Ingeneral,toperformanHTMLorHTTPSrequest,wehavetodoanumberoftasks.WecreateaninstanceofXHR,initializearequestviaopenmethod,subscribelistenerstorequest-dependentevents,setrequestheaders(setRequestHeader),andeventuallycallthesendmethod:
varxhr=newXMLHttpRequest();
xhr.open("GET","http://www.telize.com/jsonip?callback=0",true);
xhr.onload=function(){
if(this.status===200){
returnconsole.log(this.response);
}
};
xhr.responseType="json";
xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded"
);
xhr.send(null);
Moreoptionsareavailable.Forexample,wecanleveragetheprogressandaborteventstocontrolfileuploading(https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest).
Itoccurstomethatforasimplecall,thisinterfaceisovercomplicated.ThereareaplentyofimplementationsforXHRwrappersontheInternet.Oneofthemostpopularimplementationscanbefoundathttps://github.com/Raynos/xhr.ItmakestheusageofXHRthissimple:
xhr({
uri:"http://www.telize.com/jsonip",
headers:{
"Content-Type":"application/json"
}
},function(err,resp){
console.log(resp);
})
Besides,thelibraryprovidesamockobjectthatcanbeusedtoreplacerealXHRinunittests.
FetchAPIWejustexaminedtheXHRAPI.Thislookedfine15yearsago,butnowlooksclumsy.Wehavetousewrapperstomakeitmorefriendly.Luckily,thelanguagehasevolvedandnowadayswehaveanewbuilt-inmethodcalledFetchAPI.Justconsiderhoweasyitistomakeacallwithit:
fetch("/rest/foo").then(function(response){
//ConverttoJSON
returnresponse.json();
}).catch(function(err){
console.error(err);
});
Inspiteoftheapparentsimplicity,theAPIisprettypowerful.ThefetchmethodexpectsinthefirstmandatoryargumenteitherastringwitharemotemethodURLoraRequestobject.Requestoptionscanbepassedinthesecondoptionalargument:
fetch("/rest/foo",{
headers:{
"Accept":"application/json",
"Content-Type":"application/json"
}
});
Similartoourprevioussnippet,thefetchmethodreturnsPromise.Promisesarebecomingacommonpracticeforasynchronousordeferredoperations.ThefunctioncalledonthePromise-fulfilledevent(seethen)receivesaResponseobject.Thisfunctionhasanumberofpropertiesandmethods(https://developer.mozilla.org/en-US/docs/Web/API/Response).SowecanconverttheresponseintoJSON,text,blob,orstreamwithcorrespondingmethods,andwecanobtainrequest-relativeinformation:
console.log(response.text());
console.log(response.status);
console.log(response.statusText);
console.log(response.headers.get("Content-Type"));
WhataboutPOSTrequests?FetchhasamixincalledbodythatrepresentsthebodyoftheResponse/Request.WecanpassthePOSTdatathroughthis:
varform=document.querySelector("form[data-bind=foo]"),
inputEmail=form.querySelector("[name=email]"),
inputPassword=form.querySelector("[name=pwd]");
fetch("/feedback/submit",{
method:"post",
body:JSON.stringify({
email:inputEmail.value,
answer:inputPassword.value
})
});
Itacceptsnotonlykey-valuepairs,butalso,forexample,FormData,soyoucansubmitthe
wholeformincludingattachedfilesasitis:
varform=document.querySelector("form[data-bind=foo]");
fetch("/feedback/submit",{
method:"post",
body:newFormData(form)
});
Atthemoment,someofthemajorbrowsers(forexample,IE/Edge,Safari)don’tsupportthisAPI.However,ifyouintendtouseFetchAPI,youcangowiththeFetchpolyfill(https://github.com/github/fetch).
SummaryInthepast,everybrowser’svendorshadcustomDOMimplementationsthatwerelargelyincompatible.However,thishaschanged,andwehaveW3CDOMwellsupportedamongbrowsersatleastforadecade.Today,wecansafelyuseJavaScriptnativeAPItoaccess,manipulate,andstyletheDOM.
InJavaScript,XHRisstillthemainAPItocommunicatebetweenaclientandaserver.It’snotquitedeveloperfriendlythough.So,weusuallywritecustomwrappersforit.
However,anewAPIcalledFetchisproposedandalreadyimplementedinChrome,Firefox,andOpera.ThisnewAPIismucheasiertouse,andcomparedtoXHR,itprovidesamoreimpressiveandflexiblefeatures.
Chapter4.HTML5APIsWhilethelanguagespecification(ECMA-262)changesonceinafewyears,thenewHTML5APIssneakintothelanguagealmostwitheverybrowserupdate.ThealreadyavailableAPIsarequitenumerous.Yetinthischapter,wewillfocusonthosethatareusedtoreconsidertheentiredevelopmentprocess.We’lllearnhowwecanbenefitfrommultithreadingusingwebworkers,howtobuildanapplicationfromreusableindependentwebcomponents,howtostoreandsearchconsiderablyalargeamountofdataintheclientside,andhowtoestablishbidirectionalcommunicationwithaserver.
Inthischapter,wewillcoverthefollowingtopics:
StoringdatainawebbrowserBoostingperformancewithJavaScriptworkersCreatingourfirstwebcomponentLearningtouseserver-to-browsercommunicationchannels
Storingdatainweb-browserAmongtheHTML5features,thereareafewintendedtostoredataontheclientside:WebStorage,IndexedDB,andFileSystemAPI.Webenefitfromthesetechnologieswhenthefollowinghappens:
Wewanttocacheclient-sidedatatomakethemfetch-ablewithoutextraHTTPrequestsWehaveasignificantamountoflocaldatainthewebapplication,andwewantourapplicationtoworkoffline
Let’stakealookatthesetechnologies.
WebStorageAPIInthepast,weonlyhadthemechanismtokeeptheapplicationstate,anditwasusingHTTPcookies.BesidesunfriendlyAPI,cookieshaveafewflaws.Theygenerallyhaveamaximumsizeofabout4KB.Sowesimplycannotstoreanydecentamountofdata.Cookiesdon’treallyfitwhentheapplicationstateisbeingchangedindifferenttabs.CookiesarevulnerabletoCross-SiteScriptingattacks.
NowwehaveanadvancedAPIcalledWebStorage.Itprovidesgreaterstoragecapacity(5-25MBdependingonthebrowser)anddoesn’tattachanydatatotheHTTPrequestheaders.TheretwoJavaScriptbuilt-inobjectsimplementingthisinterface:localStorageandsessionStorage.Thefirstisusedaspersistentdatastorageandthesecondtokeepthedataduringasession.
StorageAPIisverysimpletouse,asshownhere:
varstorage=isPersistent?localStorage:sessionStorage;
storage.setItem("foo","Foo");
console.log(storage.getItem("foo"));
storage.removeItem("foo");
Alternatively,wecanusegetters/settersforconvenience,asfollows:
storage.foo="Foo";
console.log(storage.foo);
deletestorage.foo;
Ifwewanttoiteratethroughthestorage,wecanusestorage.lengthandstorage.key():
vari=0,len=storage.length,key;
for(;i<len;i++){
key=storage.key(i);
storage.getItem(key);
}
Asyoucansee,theWebStorageAPIismuchmoredeveloper-friendlycomparedtocookies.It’salsomorepowerful.Oneofthemostcommonreal-lifeexampleswhereweneedstorageistheshoppingcart.Whiledesigningtheapplication,wehavetokeepinmindthatauser,whilemakingtheirchoices,oftenopenspageswithproductdetailsinmultipletabsorwindows.Soweshouldtakecareofstoragesynchronizationacrossalltheopenpages.
Fortunately,wheneverweupdatethelocalStorage,thestorageeventisfiredonthewindowobject.Sowecansubscribeahandlerforthiseventtoupdatetheshoppingcartwiththeactualdata.Asimplecodeillustratingthisexamplemaylooklikethis:
<html>
<head>
<title>WebStorage</title>
</head>
<body>
<div>
<buttondata-bind="btn">Addtocart</button>
<buttondata-bind="reset">Reset</button>
</div>
<outputdata-bind="output">
</output>
<script>
varoutput=document.querySelector("[data-bind=\"output\"]"),
btn=document.querySelector("[data-bind=\"btn\"]"),
reset=document.querySelector("[data-bind=\"reset\"]"),
storage=localStorage,
/**
*Readfromthestorage
*@return{Arrays}
*/
get=function(){
//FromthestoragewereceiveeitherJSONstringornull
returnJSON.parse(storage.getItem("cart"))||[];
},
/**
*Appendanitemtothecart
*@param{Object}product
*/
append=function(product){
vardata=get();
data.push(product);
//WebStorageacceptssimpleobjects,sowepacktheobjectinto
JSONstringstorage.setItem("cart",JSON.stringify(data));
},
/**Re-renderlistofitems*/
updateView=function(){
vardata=get();
output.innerHTML="";
data&&data.forEach(function(item){
output.innerHTML+=["id:",item.id,"<br/>"].join("");
});
};
this.btn.addEventListener("click",function(){
append({id:Math.floor((Math.random()*100)+1)});
updateView();
},false);
this.reset.addEventListener("click",function(){
storage.clear();
updateView();
},false);
//Updateitemlistwhenanewitemisaddedinanotherwindow/tab
window.addEventListener("storage",updateView,false);
updateView();
</script>
</body>
</html>
Toseethisinaction,wehavetoopenthecodeHTMLintwoormoretabs.NowwhenweclicktheAddtocartbutton,wehavealistoftheordereditemsupdatedineverytab.Asyoumayhaveprobablynoticed,wecanalsocleanupthecartbyclickingtheResetbutton.Thiscallsthestorage.clearmethodandemptiesthelist.IfyouwanttousesessionStoragehereinsteadoflocalStorage,Ihavetowarnyouthatthiswon’twork.ThesessionStorageisisolatedforeverytaborwindow,sowecannotcommunicateacrossthemthisway.
However,wecouldhaveranthisexamplewithsessionStorageifwehadthepageloadedinadifferentframe,butonthesamewindowthough.FollowingscreenshotisanexampleofShoppingcartappinaction:
IndexedDBWebStorageserveswellwhenwehavetostoreaconsiderablysmallamountofdata(megabytes).However,ifweneedstructureddatainamuchgreaterquantityandwewantdoperformancesearchesthroughthisdatausingindices,wewilluseIndexedDBAPI.TheideaofanAPItostoredataindatabasesinabrowserisn’tnew.Afewyearsago,GoogleandtheirpartnerswereactivelyadvocatingastandardcandidatecalledWebSQLDatabase.ThisspecificationhasfailedtomakeitthroughW3Crecommendationthough.Now,wehaveIndexedDBAPIinsteadthatiswidely-supportedalreadyandprovidesasignificantperformanceboost(asynchronousAPIandrobustsearchduetoindexedkeys).
However,theAPIofIndexedDBisprettycomplex.It’salsoquitehardtoreadbecauseofalargeamountofnestedcallbacks:
/**
*@type{IDBOpenDBRequest}
*Syntax:indexedDB.open(DBname,DBversion);
*/
varrequest=indexedDB.open("Cem",2);
/**Reporterror*/
request.onerror=function(){
alert("Opps,somethingwentwrong");
};
/**
*CreateDB
*@param{Event}e
*/
request.onupgradeneeded=function(e){
varobjectStore;
if(e.oldVersion){
return;
}
//defineschema
objectStore=e.currentTarget.result.createObjectStore("employees",{
keyPath:"email"});
objectStore.createIndex("name","name",{unique:false});
//PopulateobjectStorewithtestdata
objectStore.add({name:"JohnDow",email:"[email protected]"});
objectStore.add({name:"DonDow",email:"[email protected]"});
};
/**
*FindarowfromtheDB
*@param{Event}e
*/
request.onsuccess=function(e){
vardb=e.target.result,
req=db.transaction(["employees"]).objectStore("employees").get(
req.onsuccess=function(){
console.log("Employeematching`[email protected]`is`"+
req.result.name+"`");
};
};
Inthissample,wecreatedarequestforopeningDB.IftheDBdoesn’texistoritsversionischanged,theupgradeneededeventisfired.Inthefunctionsubscribedtothisevent,wecandefinetheschemabydeclaringobjectstoresandtheirindices.SoifweneedtoupdatetheschemaoftheexistingDB,wecanincrementtheversionnumber,upgradeneededwillfireagainandthelistenerwillbecalledtoupdatetheschema.Assoonaswehavedefinedtheschema,wecanpopulatetheobjectstorewithsampledata.WhentherequesttoopentheDBiscomplete,werequesttherecordthatmatchestheemailIDdon@company.com.Whentherequestisdone,wegoinsidetheconsole:
Employeematching'[email protected]`is`DonDow'
Prettytangled,isn’tit?ThisAPImakesmethinkofawrapper.ThebestIknowiscalledDexie(http://www.dexie.org).Justcomparehoweasyitistosolvethesametaskwiththeinterfaceitexposes:
<scriptsrc="./Dexie.js"></script>
<script>
vardb=newDexie("Cem");
//DefineDB
db.version(3)
.stores({employees:"name,email"});
//Openthedatabase
db.open().catch(function(err){
alert("Opps,somethingwentwrong:"+err);
});
//PopulateobjectStorewithtestdata
db.employees.add({name:"JohnDow",email:"[email protected]"});
db.employees.add({name:"DonDow",email:"[email protected]"});
//Findanemployeebyemail
db.employees
.where("email")
.equals("[email protected]")
.each(function(employee){
console.log("Employeematching`[email protected]`is`"+employee.name
+"`");
});
</script>
FileSystemAPIWell,inawebapplication,wecanstorekeyvaluepairswithWebStorageandwecancreateanduseIndexedDB.Somethingisstillmissing.Desktopapplicationscanreadandwritefilesanddirectories.Thatiswhatweoftenneedinawebapplicationthatiscapableofrunningoffline.TheFileSystemAPIallowsustocreate,read,andwritetoauser’slocalfilesysteminapplicationscope.Let’stakeupanexample:
window.requestFileSystem=window.requestFileSystem||
window.webkitRequestFileSystem;
/**
*ReadfilefromagivenFileSystem
*@param{DOMFileSystem}fs
*@param{String}file
*/
varreadFile=function(fs,file){
console.log("Readingfile"+file);
//ObtainFileEntryobject
fs.root.getFile(file,{},function(fileEntry){
fileEntry.file(function(file){
//CreateFileReader
varreader=newFileReader();
reader.onloadend=function(){
console.log("Fetchedcontent:",this.result);
};
//Readfile
reader.readAsText(file);
},console.error);
},console.error);
},
/**
*SavefileintoagivenFileSystemandrunonDonewhenready
*@param{DOMFileSystem}fs
*@param{String}file
*@param{Function}onDone
*/
saveFile=function(fs,file,onDone){
console.log("Writingfile"+file);
//ObtainFileEntryobject
fs.root.getFile(file,{create:true},function(fileEntry){
//CreateaFileWriterobjectfortheFileEntry
fileEntry.createWriter(function(fileWriter){
varblob;
fileWriter.onwriteend=onDone;
fileWriter.onerror=function(e){
console.error("Writingerror:"+e.toString());
};
//CreateanewBloboutofthetextwewantintothefile.
blob=newBlob(["LoremIpsum"],{type:"text/plain"});
//Writeintothefile
fileWriter.write(blob);
},console.error);
},console.error);
},
/**
*RunwhenFileSysteminitialized
*@param{DOMFileSystem}fs
*/
onInitFs=function(fs){
constFILENAME="log.txt";
console.log("Openingfilesystem:"+fs.name);
saveFile(fs,FILENAME,function(){
readFile(fs,FILENAME);
});
};
window.requestFileSystem(window.TEMPORARY,5*1024*1024/*5MB*/,onInitFs,
console.error);
Firstofall,werequestforalocalfilesystem(requestFileSystem)that’ssandboxedtotheapplication.Withthefirstargument,westatewhetherthefilesystemshouldbepersistent.Bypassingwindow.TEMPORARYintheargument,weallowthebrowsertoremovethedataautomatically(forexample,whenmorespaceisneeded).Ifwegowithwindow.PERSISTENT,wedeterminethatthedatacannotbecleanedwithoutexplicituserconfirmation.Thesecondargumentspecifieshowmuchspacewecanallocateforthefilesystem.Then,therearetheonSuccessandonErrorcallbacks.Whenthefilesystemiscreated,wereceiveareferencetotheFileSystemobject.Thisobjecthasthefs.rootproperty,wheretheobjectkeepsDirectoryEntryboundtotherootfilesystemdirectory.TheDirectoryEntryobjecthastheDirectoryEntry.getDirectory,DirectoryEntry.getFile,DirectoryEntry.removeRecursevly,andDirectoryEntry.createReadermethods.Intheprecedingexample,wewriteintothecurrent(root)directory,sowesimplyuseDirectoryEntry.getFiletoopenafileofagivenname.Onsuccessfullyopeningafile,wereceiveFileEntrythatrepresentstheopenfile.Theobjecthasafewpropertiessuchas:FileEntry.fullPath,FileEntry.isDirectory,FileEntry.isFile,andFileEntry.nameandmethodssuchasFileEntry.fileandFileEntry.createWriter.ThefirstmethodreturnstheFileobject,whichcanbeusedtoreadfilecontent,andthesecondisusedtowriteinthefile.Bythetimetheoperationiscomplete,wereadfromthefile.Forthis,wecreateaFileReaderobjectandmakeitreadourFileobjectastext.
BoostingperformancewithJavaScriptworkersJavaScriptisasingle-threadedenvironment.So,multiplescriptscannotreallyrunsimultaneously.Yes,weusesetTimeout(),setInterval(),XMLHttpRequestandeventhandlerstoruntasksasynchronously.Sowegainnon-blockingexecution,butthisdoesn’tmeanconcurrency.However,usingwebworkers,wecanrunoneormorescriptsinthebackgroundindependentoftheUIscripts.WebworkersarelongrunningscriptsthatarenotinterruptedbyblockingUIevents.Webworkersutilizemultithreading,sowecanbenefitfrommulticoreCPUs.
Well,wherecanweusewebworkers?Anywherewherewedoprocessor-intensivecalculationsanddon’twantthemblockingtheUIthread.Itcanbegraphics,webgames,crypto,andWebI/O.WecannotmanipulatetheDOMfromawebworkerdirectly,butwehaveaccesstoXMLHttpRequest,WebStorage,IndexedDB,FileSystemAPI,WebSocketsandotherfeatures.
Solet’sseewhatthesewebworkersareinpractice.Byandlarge,weregisteranexistingwebworkerinthemainscriptandcommunicatetothewebworkerusingthePostMessageAPI(https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage):
index.html
<html>
<body>
<script>
"usestrict";
//Registerworker
varworker=newWorker("./foo-worker.js");
//Subscribeforworkermessages
worker.addEventListener("message",function(e){
console.log("Result:",e.data);
},false);
console.log("Startingthetask…");
//Sendamessagetoworker
worker.postMessage({
command:"loadCpu",
value:2000
});
</script>
</body>
</html>
foo-worker.js
"usestrict";
varcommands={
/**
*Emulateresource-consumingoperation
*@param{Number}delayinms
*/
loadCpu:function(delay){
varstart=Date.now();
while((Date.now()-start)<delay);
return"done";
}
};
//Workersdon'thaveaccesstothewindowobject.//Toaccessglobal
objectwehavetouseselfobjectinstead.
self.addEventListener("message",function(e){
varcommand;
if(commands.hasOwnProperty(e.data.command)){
command=commands[e.data.command];
returnself.postMessage(command(e.data.value));
}
self.postMessage("Error:Commandnotfound");
},false);
Hereinindex.html,werequestedthewebworker(foo-worker.js)tosubscribeforworkermessagesandrequestedittoloadtheCPUfor2,000ms,whichrepresentsaresource-consumingprocess.Theworkerreceivesthemessageandchecksforafunctionspecifiedinthecommandproperty.Ifthisexists,theworkerspassthemessagevaluetothefunctionandreplieswiththereturnvalue.
Notethatdespiteoflaunchingsuchanexpensiveprocessbystartingupindex.html,themainthreadstaysnonblocked.Nonetheless,itreportstotheconsolewhentheprocessiscomplete.ButifyoutrytoruntheloadCpufunctionwithinthemainscript,theUIfreezesandmostprobablyresultsinascript-timeouterror.Nowconsiderthis:ifyoucallloadCpuasynchronously(forinstance,withsetTimeout),theUIwillstillhang.Theonlysafewaytodealwithprocessor-sensitiveoperationsistohandthemovertowebworkers.
Webworkerscanbededicatedandshared.Adedicatedworkerisaccessibleonlythroughascript,theonewherewecalltheworker.Sharedworkerscanbeaccessedfrommultiplescripts,eventhoserunningindifferentwindows.ThatmakesthisAPIabitdifferent:
index.html
<script>
"usestrict";
varworker=newSharedWorker("bar-worker.js");
worker.port.onmessage=function(e){
console.log("Workerechoes:",e.data);
};
worker.onerror=function(e){
console.error("Error:",e.message);
};
worker.port.postMessage("Helloworker");
</script>
bar-worker.js
"usestrict";
onconnect=function(e){
varport=e.ports[0];
port.onmessage=function(e){
port.postMessage(e.data);
};
port.start();
};
Theprecedingexampleworkersimplyechoesthereceivedmessage.Iftheworkerdoessomeeffectivecomputation,wewouldbeabletocommanditfromdifferentscriptsondifferentpages.
Theseexamplesshowtheuseofwebworkersforconcurrentcomputations.WhataboutunloadingthemainthreadfromsomeofthewebI/Ooperations?Forexample,wearerequestedtoreportspecifiedUIeventstoaremoteBusinessIntelligenceServer(BIServerisusedheretoreceivestatisticaldata).Thisisnotacorefunctionality,soitwouldbegreattokeepanyloadsthattheserequestsproduceoutofthemainthread.Sowecanuseawebworker.However,aworkerisavailableonlyafterit’sloaded.Normally,thishappensveryfast,butIstillwanttobesurethatnoBIeventsarelostbecausetheworkerwasunavailable.WhatIcandoisembedthewebworkercodeintoHTMLandregisterthewebworkerbydataURI:
<scriptdata-bind="biTracker"type="text/js-worker">
"usestrict";
//HereshallgoyouBIendpoint
constREST_METHOD="http://www.telize.com/jsonip";
/**
*@param{Map}data-BIrequestparams
*@param{Function}resolve
*/
varcall=function(data,resolve){
varxhr=newXMLHttpRequest(),
params=data?Object.keys(data).map(function(key){
returnkey+"="+encodeURIComponent(data[key]);
}).join("&"):"";
xhr.open("POST",REST_METHOD,true);
xhr.addEventListener("load",function(){
if(this.status>=200&&this.status<400){
returnresolve(this.response);
}
console.error("BItracker-badrequest"+this.status);
},false);
xhr.addEventListener("error",console.error,false);
xhr.responseType="json";
xhr.setRequestHeader("Content-Type","application/x-www-form-
urlencoded");
xhr.send(params);
};
/**
*Subscribetowindow.onmessageevent
*/
onmessage=function(e){
call(e.data,function(data){
//respondback
postMessage(data);
})
};
</script>
<scripttype="text/javascript">
"usestrict";
window.biTracker=(function(){
varblob=newBlob([document.querySelector("[data-
bind=\"biTracker\"]").textContent],{
type:"text/javascript"
}),
worker=newWorker(window.URL.createObjectURL(blob));
worker.onmessage=function(oEvent){
console.info("Bi-Trackerresponds:",oEvent.data);
};
returnworker;
}());
//Let'stestit
window.biTracker.postMessage({page:"#main"});
</script>
ByhandingoverthewebI/Otoaworker,wecanalsogetadditionalcontroloverit.Forexample,inreactiontoanetworkstatuschange(theononlineandonofflineevents,andthenavigator.onlinepropertybeingavailabletoworkers),wecanrespondtoanapplicationeitherwiththeactualcallresultsorcachedones.Inotherwords,wecanmakeourapplicationworkoffline.Infact,therearespecialtypesofJavaScriptworkerscalledServiceWorkers.ServiceWorkersinheritfromSharedWorkersandactasaproxybetweenthewebapplicationandthenetwork(https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Social_API/Service_worker_API_reference).
CreatingthefirstwebcomponentYoumightbefamiliarwithHTML5videoelement(http://www.w3.org/TR/html5/embedded-content-0.html#the-video-element).ByplacingasingleelementinyourHTML,youwillgetawidgetthatrunsavideo.Thiselementacceptsanumberofattributestosetuptheplayer.Ifyouwanttoenhancethis,youcanuseitspublicAPIandsubscribelistenersonitsevents(http://www.w3.org/2010/05/video/mediaevents.html).So,wereusethiselementwheneverweneedaplayerandonlycustomizeitforproject-relevantlookandfeel.Ifonlywehadenoughoftheseelementstopickeverytimeweneededawidgetonapage.However,thisisnottherightwaytoincludeanywidgetthatwemayneedinanHTMLspecification.However,theAPItocreatecustomelements,suchasvideo,isalreadythere.Wecanreallydefineanelement,packagethecompounds(JavaScript,HTML,CSS,images,andsoon),andthenjustlinkitfromtheconsumingHTML.Inotherwords,wecancreateanindependentandreusablewebcomponent,whichwethenusebyplacingthecorrespondingcustomelement(<my-widget/>)inourHTML.Wecanrestyletheelement,andifneeded,wecanutilizetheelementAPIandevents.Forexample,ifyouneedadatepicker,youcantakeanexistingwebcomponent,let’ssaytheoneavailableathttp://component.kitchen/components/x-tag/datepicker.Allthatwehavetodoisdownloadthecomponentsources(forexample,usingthebrowserpackagemanager)andlinktothecomponentfromourHTMLcode:
<linkrel="import"href="bower_components/x-tag-
datepicker/src/datepicker.js">
DeclarethecomponentintheHTMLcode:
<x-datepickername="2012-02-02"></x-datepicker>
ThisissupposedtogosmoothlyinthelatestversionsofChrome,butthiswon’tprobablyworkinotherbrowsers.Runningawebcomponentrequiresanumberofnewtechnologiestobeunlockedinaclientbrowser,suchasCustomElements,HTMLImports,ShadowDOM,andtemplates.ThetemplatesincludetheJavaScripttemplatesthatweexaminedinChapter1,DivingintoJavaScriptcore.TheCustomElementAPIallowsustodefinenewHTMLelements,theirbehavior,andproperties.TheShadowDOMencapsulatesaDOMsubtreerequiredbyacustomelement.AndsupportofHTMLimportsassumesthatbyagivenlinktheuser-agentenablesaweb-componentbyincludingitsHTMLonapage.Wecanuseapolyfill(http://webcomponents.org/)toensuresupportforalloftherequiredtechnologiesinallthemajorbrowsers:
<scriptsrc="./bower_components/webcomponentsjs/webcomponents.min.js">
</script>
Doyoufancywritingyourownwebcomponents?Let’sdoit.OurcomponentactssimilartoHTML’sdetails/summary.Whenoneclicksonsummary,thedetailsshowup.Sowecreatex-details.html,whereweputcomponentstylesandJavaScriptwiththecomponentAPI:
x-details.html
<style>
.x-details-summary{
font-weight:bold;
cursor:pointer;
}
.x-details-details{
transition:opacity0.2sease-in-out,transform0.2sease-in-out;
transform-origin:topleft;
}
.x-details-hidden{
opacity:0;
transform:scaleY(0);
}
</style>
<script>
"usestrict";
/**
*Objectconstructorrepresentingx-detailselement
*@param{Node}el
*/
varDetailsView=function(el){
this.el=el;
this.initialize();
},
//CreatesanobjectbasedintheHTMLElementprototype
element=Object.create(HTMLElement.prototype);
/**@lendDetailsView.prototype*/
Object.assign(DetailsView.prototype,{
/**
*@constractsDetailsView
*/
initialize:function(){
this.summary=this.renderSummary();
this.details=this.renderDetails();
this.summary.addEventListener("click",this.onClick.bind(this),
false);
this.el.textContent="";
this.el.appendChild(this.summary);
this.el.appendChild(this.details);
},
/**
*Rendersummaryelement
*/
renderSummary:function(){
vardiv=document.createElement("a");
div.className="x-details-summary";
div.textContent=this.el.dataset.summary;
returndiv;
},
/**
*Renderdetailselement
*/
renderDetails:function(){
vardiv=document.createElement("div");
div.className="x-details-detailsx-details-hidden";
div.textContent=this.el.textContent;
returndiv;
},
/**
*Handlesummaryonclick
*@param{Event}e
*/
onClick:function(e){
e.preventDefault();
if(this.details.classList.contains("x-details-hidden")){
returnthis.open();
}
this.close();
},
/**
*Opendetails
*/
open:function(){
this.details.classList.toggle("x-details-hidden",false);
},
/**
*Closedetails
*/
close:function(){
this.details.classList.toggle("x-details-hidden",true);
}
});
//Fireswhenaninstanceoftheelementiscreated
element.createdCallback=function(){
this.detailsView=newDetailsView(this);
};
//Exposemethodopen
element.open=function(){
this.detailsView.open();
};
//Exposemethodclose
element.close=function(){
this.detailsView.close();
};
//Registerthecustomelement
document.registerElement("x-details",{
prototype:element
});
</script>
FurtherintheJavaScriptcode,wecreateanelementbasedonagenericHTMLelement(Object.create(HTMLElement.prototype)).Herewecouldinheritfromacomplexelement(forexample,video)ifneeded.Weregisterax-detailscustomelementusingtheearlieronecreatedasaprototype.Withelement.createdCallback,wesubscribeahandlerthatwillbecalledwhenacustomelementiscreated.Hereweattachourviewtotheelementtoenhanceitwiththefunctionalitythatweintendforit.NowwecanusethecomponentinHTML,asfollows:
<!DOCTYPEhtml>
<html>
<head>
<title>X-DETAILS</title>
<!--ImportingWebComponent'sPolyfill-->
<!--uncommentfornon-Chromebrowsers
scriptsrc="./bower_components/webcomponentsjs/webcomponents.min.js">
</script-->
<!--ImportingCustomElements-->
<linkrel="import"href="./x-details.html">
</head>
<body>
<x-detailsdata-summary="Clickme">
Nunciaculisacerateuporttitor.Curabiturfacilisisligulaeturna
egestasmollis.Aliquamegetconsequattellus.Sedullamcorperanteest.In
tortorlectus,ultricesvelipsumeget,ultriciesfacilisisnisl.
Suspendisseporttitorblanditarcuetimperdiet.
</x-details>
</body>
</html>
X-detailsweb-componentinactionisshowninthefollowingscreenshot:
Learningtouseserver-to-browsercommunicationchannelsUsingXHRorFetchAPI,wecanrequestastatefromtheserver.Thisisaone-waycommunication.Ifwewantreal-timecommunication,weneedthisintheoppositedirectionaswell.Forexample,wemaywantusernotifications(yourposthasbeenliked,newcomment,ornewprivatemessage)topopupassoonasthecorrespondingrecordschangeintheDB.TheserversidehasconnectiontotheDB,soweexpecttheservertonotifytheclient.Inthepast,toreceivetheseeventsontheclient,wewereusingtricksthatwereknownundertheumbrellatermCOMET(hiddeniframe,longpolling,taglongpolling,andothers).NowwecangowithnativeJavaScriptAPIs.
Server-SentEventsThetechnologythatprovidesawaytosubscribetoserver-sideeventsistheServer-SentEvents(SSE)API.Ontheclient,weregisteraserverstream(EventSource)andsubscribetotheeventcomingfromit:
varsrc=newEventSource("./sse-server.php");
src.addEventListener("open",function(){
console.log("Connectionopened");
},false);
src.addEventListener("error",function(e){
if(e.readyState===EventSource.CLOSED){
console.error("Connectionclosed");
}
},false);
src.addEventListener("foo",function(e){
vardata=JSON.parse(e.data);
console.log("Receivedfromtheserver:",data);
},false);
Here,wesubscribedalistenertoaspecificeventcalled"foo".Ifyouwantyourcallbacktobeinvokedoneveryserverevent,justusesrc.onmessage.Asfortheserverside,wejustneedtosettheMIMEtypetext/event-streamandsendeventpayloadblocksseparatedwithpairsofnewlines:
event:foo\n
data:{time:"date"}\n\n
SSEworksviaanHTTPconnection,soweneedawebservertocreateastream.PHPisconsiderablysimplerandawidelyusedserver-sidelanguage.Chancesarethatyouarealreadyfamiliarwithitssyntax.Ontheotherhand,PHPisn’tdesignedforapersistentconnectionoflongduration.Yet,wecantrickitbydeclaringaloopthatmakesourPHPscriptneverending:
<?PHP
set_time_limit(0);
header("Content-Type:text/event-stream");
header("Cache-Control:no-cache");
date_default_timezone_set("Europe/Berlin");
functionpostMessage($event,$data){
echo"event:{$event}",PHP_EOL;
echo"data:",json_encode($data,true),PHP_EOL,PHP_EOL;
ob_end_flush();
flush();
}
while(true){
postMessage("foo",array("time"=>date("r")));
sleep(1);
}
YoumayhaveseenSSEexampleswheretheserverscriptoutputsthedataonceandterminatestheprocess(forexample,http://www.html5rocks.com/en/tutorials/eventsource/basics/).Thatisalsoaworkingexample,becauseeverytimetheconnectionisterminatedbytheserver,thebrowserrenewstheconnection.However,thiswaywedonothaveanybenefitofSSEthatworksthesameaspolling.
Noweverythinglooksready,sowecanruntheHTMLcode.Aswedothis,wegetthefollowingoutputintheconsole:
Connectionopened
Receivedfromtheserver:Object{time="Tue,25Aug201510:31:54+0200"}
Receivedfromtheserver:Object{time="Tue,25Aug201510:31:55+0200"}
Receivedfromtheserver:Object{time="Tue,25Aug201510:31:56+0200"}
Receivedfromtheserver:Object{time="Tue,25Aug201510:31:57+0200"}
Receivedfromtheserver:Object{time="Tue,25Aug201510:31:58+0200"}
Receivedfromtheserver:Object{time="Tue,25Aug201510:31:59+0200"}
Receivedfromtheserver:Object{time="Tue,25Aug201510:32:00+0200"}
Receivedfromtheserver:Object{time="Tue,25Aug201510:32:01+0200"}
Receivedfromtheserver:Object{time="Tue,25Aug201510:32:02+0200"}
...
WebSocketsWell,withXHR/Fetchwecommunicatefromclienttoserver.WithSSE,wedothisintheoppositedirection.Butcanwehavecommunicationbothwaysatonce?AnotherHTML5goodycalledWebSocketsprovidesbidirectional,full-duplexclient-servercommunications.
TheclientsidelookssimilartoSEE.WejustregistertheWebSocketserver,subscribetoitsevents,andsendtoitourevents:
varrtm=newWebSocket("ws://echo.websocket.org");
rtm.onopen=function(){
console.log("Connectionestablished");
rtm.send("hello");
};
rtm.onclose=function(){
console.log("Connectionclosed");
};
rtm.onmessage=function(e){
console.log("Received:",e.data);
};
rtm.onerror=function(e){
console.error("Error:"+e.message);
};
Thisdemosourceatws://echo.websocket.orgsimplyechoesanymessagessenttoit:
Connectionestablished
Received:hello
Needsomethingmorepractical?Ibelievethemostillustrativecasewouldbeachat:
demo.html
<style>
input{
border-radius:5px;
display:block;
font-size:14px;
border:1pxsolidgrey;
margin:3px0;
}
button{
border-radius:5px;
font-size:14px;
background:#189ac4;
color:white;
border:none;
padding:3px14px;
}
</style>
<formdata-bind="chat">
<inputdata-bind="whoami"placeholder="Enteryourname">
<inputdata-bind="text"placeholder="Enteryourmsg"/>
<buttontype="submit">Send</button>
</form>
<h3>Chat:</h3>
<outputdata-bind="output">
</output>
<script>
varwhoami=document.querySelector("[data-bind=\"whoami\"]"),
text=document.querySelector("[data-bind=\"text\"]"),
chat=document.querySelector("[data-bind=\"chat\"]"),
output=document.querySelector("[data-bind=\"output\"]"),
//createwsconnection
rtm=newWebSocket("ws://localhost:8001");
rtm.onmessage=function(e){
vardata=JSON.parse(e.data);
output.innerHTML+=data.whoami+"says:"+data.text+"<br/>";
};
rtm.onerror=function(e){
console.error("Error:"+e.message);
};
chat.addEventListener("submit",function(e){
e.preventDefault();
if(!whoami.value){
returnalert("Youhaveenteryourname");
}
if(!text.value){
returnalert("Youhaveentersometext");
}
rtm.send(JSON.stringify({
whoami:whoami.value,
text:text.value
}));
});
</script>
Herewehaveaformwithtwoinputfields.Thefirstexpectsaperson’snameandthesecond,thechatmessage.Whentheformissubmitted,thevaluesofbothinputsaresenttotheWebSocketserver.Serverresponseisdisplayedintheoutputelement.UnlikeSSE,WebSocketsrequireaspecialprotocolandserverimplementationtogetworking.Toruntheexample,wewilltakeasimplenodejs-basedserverimplementation,nodejs-websocket(https://github.com/sitegui/nodejs-websocket):
ws.js
/**@type{module:nodejs-websocket}*/
varws=require("nodejs-websocket"),
/**@type{Server}*/
server=ws.createServer(function(conn){
conn.on("text",function(str){
console.log("Received"+str);
broadcast(str);
});
}).listen(8001),
/**
*Broadcastmessage
*@param{String}msg
*/
broadcast=function(msg){
server.connections.forEach(function(conn){
conn.sendText(msg);
});
};
Thescriptcreatesaserveravailableontheport8001thatlistenstotheWebSocketmessages,andwhenanymessageisreceived,theportbroadcastsittoalltheavailableconnections.Wecanfireuptheserverlikethis:
nodews.js
Nowweopenourdemochatintwodifferentbrowsers.Whenwesendamessagefromoneofthem,themessageshowsupinbothbrowsers.FollowingscreenshotshowstheWebSocket-drivenchatinFirefox:
FollowingscreenshotshowstheWebSocket-drivenchatinChrome:
Notehowfasttheclientsreacttotheevents.Communicationthroughsocketsgivesirrefutableadvantages.
ThereareanumberofWebSocketserverimplementationsforvariouslanguages,forexample,Socket.IO(http://socket.io)forNode.js,Jetty(http://www.eclipse.org/jetty)forJava,Faye(http://faye.jcoglan.com)forRuby,Tornado(http://www.tornadoweb.org)forPython,andevenoneforPHPcalledRatchet(http://socketo.me).However,Iwouldliketodrawyourattentiontoalanguage-agnosticWebSocketdaemon—Websocketd(http://websocketd.com/).It’slikeCommonGatewayInterface(CGI),butforWebSockets.soyoucanwritetheserverlogininyourfavoritelanguageandthenattachyourscripttothedaemon:
websocketd--port=8001my-script
SummaryHTML5provideanumberofawesomeAPIs,andwejustexaminedsomeofthem.AmongbrowserstorageAPIs,therearelocalStorageandsessionStoragethatextendthecookiesrelict.Botharecapableofstoringmegabytesofdataandcanbeeasilysynchronizedacrossdifferentbrowserwindows/tabs.IndexedDBallowsustostoreevengreaterquantityofdataandprovidesaninterfaceforhigh-performancesearchesusingindices.WecanalsouseFileSystemAPItocreateandoperatealocalfilesystemboundtothewebapplication.
WhileJavaScriptisasingle-threadedenvironment,wecanstillrunscriptsinmultiplethreads.WecanregisterdedicatedorsharedWebWorkersandhandoveranyprocessor-intensiveoperations,leavingthemainthreadandtheUIunaffected.WealsocanleverageaspecialkindofJavaScriptworkers—ServiceWorkers–asaproxybetweenthewebapplicationandthenetwork.ThisenablescontroltonetworkI/Owhenthebrowsersswitchesmodeonline/offline.
Nowadayswecancreateowncustomadvancedelementsthatcanbeeasilyreused,restyled,andenhanced.TheassetsrequiredtorendersuchelementsareHTML,CSS,JavaScript,andimagesarebundledasWebComponents.So,weliterallycanbuildtheWebnowfromthecomponentssimilartohowbuildingsaremadefrombricks.
Inthepast,weusedtricksknownasCOMETtoexchangeeventsbetweenserverandclient.NowwecanuseSSEAPItosubscribeforservereventssentoverHTTP.WecanalsouseWebSocketsforbidirectional,full-duplexclient-servercommunications.
Chapter5.AsynchronousJavaScriptNowadaysInternetusersareimpatient,alagof2-3secondsduringpageloadingornavigationandtheylosetheirinterestandwilllikelyleavetheserviceforsomethingelse.Ourhighestpriorityistoreduceuserresponsetime.ThemainapproachhereisknownasCuttingthemustard(http://www.creativebloq.com/web-design/responsive-web-design-tips-bbc-news-9134667).Weextractthecomponentsofanapplicationrequiredforcoreexperienceandloadthemfirst.Then,progressivelyweaddanenhancedexperience.AsforJavaScript,whatwehavetocarethemostaboutarenonblockingflows.Thus,wehavetoavoidloadingscriptssynchronouslypriortoHTMLrendering,andwehavetowrapalllong-runningtasksintoasynchronouscallbacks.Thisissomethingthatyoumostprobablyalreadyknow.Butdoyoudoitefficiently?
Inthischapter,wewillcoverthefollowingtopics:
NonblockingJavaScriptError-firstcallbackThecontinuation-passingstyleHandlingasynchronousfunctionsintheES7wayParalleltasksandtaskserieswiththeAsync.jslibraryEventhandlingoptimization
NonblockingJavaScriptFirstofall,let’slookatwhatreallyhappenswhenwedothingsasynchronously.WheneverweinvokeafunctioninJavaScript,itcreatesanewstackframe(executionobject).Everyinnercallgetsintothisframe.HeretheframesarepushedandpoppedfromthetopofthecallstackintheLIFO(lastin,firstout)manner.Inotherwords,inthecode,wecallthefoofunctionandthenthebarfunction;however,duringexecution,foocallsthebazfunction.Inthiscase,inthecallstack,wehavethefollowingsequence:foo,baz,andonlythenbar.Sobariscalledafterthestackframeoffooisempty.IfanyofthefunctionsperformaCPU-intensivetask,allthesuccessivecallswaitforittofinish.However,JavaScriptengineshaveEventQueues(ortaskqueues).
IfwesubscribeafunctiontoaDOMeventorpassacallbacktoatimer(setTimeoutorsetInterval)orthroughanyWebI/OAPIs(XHR,IndexedDB,andFileSystem),itendsupinacorrespondingqueue.Then,thebrowser’seventloopdecideswhenandwhichcallbacktopushinthecallbackstack.Hereisanexample:
functionfoo(){
console.log("CallingFoo");
}
functionbar(){
console.log("CallingBar");
}
setTimeout(foo,0);
bar();
UsingsetTimeout(foo,0),westatethatfooshallbecalledimmediately,andthenwecallbar.However,foolandsinaqueueandtheeventloopputsitdeeperinthecallstack:
CallingBar
CallingFoo
ThisalsomeansthatifthefoocallbackperformsaCPU-intensivetask,itdoesn’tblockthemainexecutionflow.Similarly,anasynchronously-madeXHR/Fetchrequestdoesn’t
lockuptheinteractionwhilewaitingfortheserver’sresponse:
functionbar(){
console.log("Barcomplete");
}
fetch("http://www.telize.com/jsonip").then(function(response){
console.log("Fetchcomplete");
});
bar();
//Console:
//Barcomplete
//Fetchcomplete
Howdoesthisapplytoreal-worldapplications?Hereisacommonflow:
"usestrict";
//ThisstatementloadsimaginaryAMDmodules
//YoucanfinddetailsaboutAMDstandardin
//"Chapter2:ModularprogrammingwithJavaScript"
require(["news","Session","User","Ui"],function(News,Session,
User,Ui){
varsession=newSession(),
news=newNews(),
ui=newUi({el:document.querySelector("[data-bind=ui]")});
//loadnews
news.load(ui.update);
//authorizeuser
session.authorize(function(token){
varuser=newUser(token);
//loaduserdata
user.load(function(){
ui.update();
//loaduserprofilepicture
user.loadProfilePicture(ui.update);
//loadusernotifications
user.loadNotifications(ui.update);
});
});
});
TheloadingofJavaScriptdependenciesisqueued,sothebrowsercanrenderanddelivertheUItotheuserwithoutwaitingforthat.Assoonasthescriptsarefullyloaded,theapplicationpushestwonewtaskstothequeue:loadnewsandauthorizeuser.Again,noneofthemblocksthemainthread.Onlywhenanyoftheserequestscompleteandthemainthreadgetsinvolved,itenhancestheUIaccordingtothenewlyreceiveddata.Assoonasauserisauthorizedandthesessiontokenisretrieved,wecanloaduserdata.Afterthetaskiscompleted,wequeuenewones.
Asyoucansee,asynchronouscodeishardertoreadcomparedtosynchronousone.Theexecutionsequencescanbequitecomplex.Besides,wehavetotakeextracareforerrorcontrol.Whengoingforsynchronouscode,wecanwrapablockoftheprogramwithtry/catchandinterceptanyerrorsthrownduringexecution:
functionfoo(){
thrownewError("Foothrowsanerror");
}
try{
foo();
}catch(err){
console.log("Theerroriscaught");
}
However,ifthecallisqueued,itslipsoutofthetry/catchscope:
functionfoo(){
thrownewError("Foothrowsanerror");
}
try{
setTimeout(foo,0);
}catch(err){
console.log("Theerroriscaught");
}
Yeah,asynchronousprogramminghasitsquirks.Togetagriponthis,wewillexaminetheexistingpracticesofwritingasynchronouscode.
Sotomakethecodeasynchronous,wequeueataskandsubscribeforaneventthatisfiredwhenthetaskiscomplete.Actually,wegoforEvent-DrivenProgramming,andinparticular,weapplyaPubSubpattern.Forexample,theEventTargetinterface,whichwetoucheduponinChapter3,DOMScriptingandAJAX,inanutshell,isaboutsubscribinglistenerstoeventsonDOMelementsandfiringtheseeventseitherfromUIorprogrammatically:
varel=document.createElement("div");
event=newCustomEvent("foo",{detail:"foodata"});
el.addEventListener("foo",function(e){
console.log("Fooeventcaptured:",e.detail);
},false);
el.dispatchEvent(event);
//Fooeventcaptured:foodata
BehindtheDOM,weuseasimilarprinciple,butimplementationsmaydiffer.Probablythemostpopularinterfaceisbasedontwomainmethods,obj.on(tosubscribeahandler)andobj.trigger(tofireanevent):
obj.on("foo",function(data){
console.log("Fooeventcaptured:",data);
});
obj.trigger("foo","foodata");
ThisishowPubSubisimplementedinabstractionframeworks,forexample,Backbone.jQueryusesthisinterfaceonDOMeventsalso.Theinterfacegaineditsmomentumthroughsimplicity,butitdoesn’treallyhelpwithspaghetticodeanddoesn’tcovererrorhandling.
Error-firstCallbackThepatternusedacrossalltheasynchronousmethodsinNode.jsiscalledError-firstCallback.Hereisanexample:
fs.readFile("foo.txt",function(err,data){
if(err){
console.error(err);
}
console.log(data);
});
Anyasynchronousmethodexpectsoneoftheargumentstobeacallback.Thefullcallbackargumentlistdependsonthecallermethod,butthefirstargumentisalwaysanerrorobjectornull.Whenwegofortheasynchronousmethod,anexceptionthrownduringfunctionexecutioncannotbedetectedinatry/catchstatement.TheeventhappensaftertheJavaScriptengineleavesthetryblock.Intheprecedingexample,ifanyexceptionisthrownduringthereadingofthefile,itlandsonthecallbackfunctionasthefirstandmandatoryparameter.Regardlessofitswidespreaduse,thisapproachhasitsflaws.Whilewritingrealcodewithdeepcallbacksequences,itiseasytorunintoaso-calledCallbackHell(http://callbackhell.com/).Thecodebecomesprettyhardtofollow.
Continuation-passingstyleWeoftenneedachainofasynchronouscalls,thatis,asequenceoftaskswhereonetaskisstartedafteranotheriscompleted.Weareinterestedinaneventualresultofasynchronouscallschain.Inthiscase,wecanbenefitfromContinuation-passingstyle(CPS).JavaScripthasalreadyabuilt-inPromiseobject.WeuseittocreateanewPromiseobject.WeputourasynchronoustaskinthePromisecallbackandinvoketheresolvefunctionoftheargumentlisttonotifythePromisecallbackthatthetaskisresolved:
"usestrict";
/**
*Incrementagivenvalue
*@param{Number}val
*@returns{Promise}
*/
varfoo=function(val){
/**
*Returnapromise.
*@param{Function}resolve
*/
returnnewPromise(function(resolve){
setTimeout(function(){
resolve(val+1);
},0);
});
};
foo(1).then(function(val){
console.log("Result:",val);
});
//Result:5
Intheprecedingexample,wecalledfoo,thatreturnsPromise.Usingthismethod,wesetahandlerthatinvokeswhenPromiseisfulfilled.
Whatabouterrorcontrol?WhencreatingPromise,wecanusethefunctiongiveninthesecondargument(reject)toreportafailure:
"usestrict";
/**
*MakeGETrequest
*@param{String}url
*@returns{Promise}
*/
functionajaxGet(url){
returnnewPromise(function(resolve,reject){
varreq=newXMLHttpRequest();
req.open("GET",url);
req.onload=function(){
//Ifresponsestatusisn't200somethingwentwrong
if(req.status!==200){
//Earlyexit
returnreject(newError(req.statusText));
}
//Everythingisok,wecanresolvethepromise
returnresolve(JSON.parse(req.responseText));
};
//Onnetworkerrors
req.onerror=function(){
reject(newError("NetworkError"));
};
//Maketherequest
req.send();
});
};
ajaxGet("http://www.telize.com/jsonip").then(function(data){
console.log("YourIPis",data.ip);
}).catch(function(err){
console.error(err);
});
//YourIPis127.0.0.1
ThemostexcitingpartaboutPromisesisthattheycanbechained.Wecanpipethecallbackstoqueueasynchronoustasksortransformvalues:
"usestrict";
/**
*Incrementagivenvalue
*@param{Number}val
*@returns{Promise}
*/
varfoo=function(val){
/**
*Returnapromise.
*@param{Function}resolve
*@param{Function}reject
*/
returnnewPromise(function(resolve,reject){
if(!val){
returnreject(newRangeError("Valuemustbegreaterthanzero"
));
}
setTimeout(function(){
resolve(val+1);
},0);
});
};
foo(1).then(function(val){
//chainingasynccall
returnfoo(val);
}).then(function(val){
//transformingoutput
returnval+2;
}).then(function(val){
console.log("Result:",val);
}).catch(function(err){
console.error("Errorcaught:",err.message);
});
//Result:5
Notethatifwepass0tothefoofunction,theentryconditionthrowsanexceptionandwewillendupinacallbackofthecatchmethod.Ifanexceptionisthrowninoneofthecallbacks,itappearsinthecatchcallbackaswell.
APromisechainisresolvedinamannersimilartothatofawaterfallmodel—thetasksareinvokedoneafteranother.WecanalsocausePromisetoresolveafterseveralparallelprocessingtasksarecompleted:
"usestrict";
/**
*Incrementagivenvalue
*@param{Number}val
*@returns{Promise}
*/
varfoo=function(val){
returnnewPromise(function(resolve){
setTimeout(function(){
resolve(val+1);
},100);
});
},
/**
*Incrementagivenvalue
*@param{Number}val
*@returns{Promise}
*/
bar=function(val){
returnnewPromise(function(resolve){
setTimeout(function(){
resolve(val+2);
},200);
});
};
Promise.all([foo(1),bar(2)]).then(function(arr){
console.log(arr);
});
//[2,4]
ThePromise.allstaticmethodisnotyetsupportedinallthelatestbrowsers,butyoucangetthisviaapolyfillathttps://github.com/jakearchibald/es6-promise.
AnotherprobabilityistocausePromisetoresolveorrejectwheneveranyoftheconcurrentlyrunningtasksarecompleted:
Promise.race([foo(1),bar(2)]).then(function(arr){
console.log(arr);
});
//2
HandlingasynchronousfunctionsintheES7wayWealreadyhavethePromiseAPIinJavaScript.TheupcomingtechnologyisAsync/AwaitAPIandispresentedinaproposal(https://tc39.github.io/ecmascript-asyncawait/)forEcmaScript7thedition.ThisdescribeshowwecandeclareasynchronousfunctionsthatcanhaltwithoutblockinganythingandwaitfortheresultofPromise:
"usestrict";
//Fetcharandomjoke
functionfetchQuote(){
returnfetch("http://api.icndb.com/jokes/random")
.then(function(resp){
returnresp.json();
}).then(function(data){
returndata.value.joke;
});
}
//Reporteitherafetchedjokeorerror
asyncfunctionsayJoke()
{
try{
letresult=awaitfetchQuote();
console.log("Joke:",result);
}catch(err){
console.error(err);
}
}
sayJoke();
Atthemoment,theAPIisn’tsupportedinanybrowser;however,youcanrunitusingtheBabel.jstranspileronruntime.Youcanalsofiddlewiththisexampleonlineathttp://codepen.io/dsheiko/pen/gaeqRO.
Thisnewsyntaxallowsustowriteacodethatrunsasynchronouslywhileappearingtobesynchronous.Thus,wecanusecommonconstructionssuchastry/catchforasynchronouscalls,whichmakesthecodemuchmorereadableandeasiertomaintain.
ParalleltasksandtaskserieswiththeAsync.jslibraryAnotherapproachtodealwithasynchronouscallsisalibrarycalledAsync.js(https://github.com/caolan/async).Whenusingthislibrary,wecanexplicitlyspecifyhowwewantthebatchoftaskstoberesolved—asawaterfall(chain)orinparallel.
Inthefirstcase,wecansupplyanarrayofcallbackstoasync.waterfall,assumingwhenoneiscompleted,thenextoneisinvoked.Wecanalsopasstheresolvedvaluefromonecallbacktoanotherandreceivetheaggregatevalueorthethrownexceptioninamethod’son-donecallback:
/**
*Concatgivenarguments
*@returns{String}
*/
functionconcat(){
varargs=[].slice.call(arguments);
returnargs.join(",");
}
async.waterfall([
function(cb){
setTimeout(function(){
cb(null,concat("foo"));
},10);
},
function(arg1,cb){
setTimeout(function(){
cb(null,concat(arg1,"bar"));
},0);
},
function(arg1,cb){
setTimeout(function(){
cb(null,concat(arg1,"baz"));
},20);
}
],function(err,results){
if(err){
returnconsole.error(err);
}
console.log("Alldone:",results);
});
//Alldone:foo,bar,baz
Similarly,wepassanarrayofcallbackstoasync.parallel.Thistime,allofthemruninparallel,butwhenallareresolved,wereceivetheresultsorthethrownexceptioninthemethod’son-donecallback:
async.parallel([
function(cb){
setTimeout(function(){
console.log("fooiscomplete");
cb(null,"foo");
},10);
},
function(cb){
setTimeout(function(){
console.log("bariscomplete");
cb(null,"bar");
},0);
},
function(cb){
setTimeout(function(){
console.log("baziscomplete");
cb(null,"baz");
},20);
}
],function(err,results){
if(err){
returnconsole.error(err);
}
console.log("Alldone:",results);
});
//bariscomplete
//fooiscomplete
//baziscomplete
//Alldone:['foo','bar','baz']
Surely,wecancombinetheflows.Besides,thelibraryprovidesiterationmethods,suchasmap,filter,andeach,thatappliestothearrayofasynchronoustasks.
Async.jswasthefirstprojectofthiskind.Today,therearemanylibrariesinspiredbythis.IfyouwantalightweightandrobustsolutionsimilartoAsync.js,IwouldrecommendthatyoucheckContra(https://github.com/bevacqua/contra).
EventhandlingoptimizationItmusthavehappenedtoyouwhilewritingaforminlinevalidatorthatyourunintoaproblem.Asyoutypeit,theuser-agentkeepssendingvalidationrequeststotheserver.ThiswayyoumightquicklypollutethenetworkwithspawningXHRs.Anothersortofproblemthatyoumaybefamiliarwith,isthatsomeUIevents(touchmove,mousemove,scroll,andresize)arefiredintensivelyandsubscribedhandlersmayoverloadthemainthread.Theseproblemscanbesolvedusingoneoftwoapproachesknownasdebouncingandthrottling.Bothfunctionsareavailableinthird-partylibrariessuchasUnderscoreandLodash(_.debounceand_.throttle).However,theycanbeimplementedwithalittleocodeandonedoesn’tneedtodependonextralibrariesforthisfunctionality.
DebouncingBydebouncing,weensurethatahandlerfunctioniscalledonceforarepeatedlyemittedevent:
/**
*Invokeagivencallbackonlyafterthisfunctionstopsbeingcalled
`wait`milliseconds
*usage:
*debounce(cb,500)(..arg);
*
*@param{Function}cb
*@param{Number}wait
*@param{Object}thisArg
*/
functiondebounce(cb,wait,thisArg){
/**
*@type{number}
*/
vartimer=null;
returnfunction(){
varcontext=thisArg||this,
args=arguments;
window.clearTimeout(timer);
timer=window.setTimeout(function(){
timer=null;
cb.apply(context,args);
},wait);
};
}
Let’ssaywewantawidgettolazyloadonlywhenitcomesintoview,whichinourcaserequiresausertoscrollthepageatleastby200pixelsdownwards:
varTOP_OFFSET=200;
//Lazy-loading
window.addEventListener("scroll",debounce(function(){
varscroll=window.scrollY||window.pageYOffset||
document.documentElement.scrollTop;
if(scroll>=TOP_OFFSET){
console.log("Loadthedeferredwidget(ifnotyetloaded)");
}
},20));
Ifwesimplysubscribealistenertothescrollevent,itwillbecalledquiteanumberoftimesbetweenthetimeintervalwhentheuserstartsandstopsscrolling.Thankstothedebounceproxy,thehandlerthatcheckswhetherit’sthetimetoloadthewidgetornotiscalledonlyonce,whentheuserstopsscrolling.
ThrottlingBythrottling,wesethowoftenthehandlerisallowedtobecalledwhiletheeventisfired:
/**
*Invokeagivencallbackevery`wait`msuntilthisfunctionstops
beingcalled
*usage:
*throttle(cb,500)(..arg);
*
*@param{Function}cb
*@param{Number}wait
*@param{Object}thisArg
*/
functionthrottle(cb,wait,thisArg){
varprevTime,
timer;
returnfunction(){
varcontext=thisArg||this,
now=+newDate(),
args=arguments;
if(!prevTime||now>=prevTime+wait){
prevTime=now;
returncb.apply(context,args);
}
//holdontoit
clearTimeout(timer);
timer=setTimeout(function(){
prevTime=now;
cb.apply(context,args);
},wait);
};
}
Soifwesubscribeahandlertothemousemoveeventonacontainerviathrottle,thehandlerfunctiononceatime(secondhere)untilthemousecursorsleavesthecontainerboundaries:
document.body.addEventListener("mousemove",throttle(function(e){
console.log("Thecursoriswithintheelementat",e.pageX,",",
e.pageY);
},1000),false);
//Thecursoriswithintheelementat946,715
//Thecursoriswithintheelementat467,78
Writingcallbacksthatdon’timpactlatency-criticaleventsSomeofthetasksthatwehavedonotbelongtoacorefunctionalityandmayruninthebackground.Forexample,wewanttodispatchanalyticsdatawhilescrolling.WedothiswithoutdebouncingorthrottlingthatwouldoverloadtheUIthreadandwouldlikelymaketheappunresponsive.Debouncingisn’trelevanthereandthrottlingwon’tgiveprecisedata.However,wecanusetherequestIdleCallbacknativemethod(https://w3c.github.io/requestidlecallback/)toschedulethetaskatthetimewhenuser-agentisidle.
SummaryOneofourmostprioritizedgoalsistoreduceuser-responsetime,thatis,theapplicationarchitecturemustensuretheuserflowisneverblocked.Thiscanbeachievedbyqueuinganylong-runningtasksforasynchronousinvocation.However,ifyouhaveanumberofasynchronouscallsamongwhichsomeareintendedtoruninparallelandsomesequentially,withouttakingspecialcare,it’seasytorunintoaso-calledCallbackHell.AproperuseofsuchapproachesasContinuation-passingstyle(PromiseAPI),theAsync/AwaitAPI,oranexternallibrarysuchasAsync.jsmaysignificantlyimproveyourasynchronouscode.Wealsohavetorememberthatsomeeventssuchasscroll/touch/mousemove,whilebeingintensivelyfired,maycauseunnecessaryCPUloadbycallingsubscribedlistenersfrequently.Wecanavoidtheseproblemsusingdebouncingandthrottlingtechniques.
Bylearningthebasisofasynchronousprogramming,wecanwritenonblockingapplications.InChapter6,ALarge-ScaleJavaScriptApplicationArchitecture,wewilltalkabouthowtomakeourapplicationsscalableandimprovethemaintainabilityingeneral.
Chapter6.ALarge-ScaleJavaScriptApplicationArchitectureAnyexperiencedprogrammerworkshardtomakethecodereusableandmaintainable.Hereweareguidedbytheprinciplesofobject-orientedprogramming,suchasencapsulation,abstraction,inheritance,composition,andpolymorphism.Inadditiontothesefundamentals,wefollowthefivebasicprinciplesofobject-orientedprogramminganddesigndefinedbyRobertC.MartinandknownundertheacronymSOLID(https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)).Whenduringcodereviewwerunintoaviolationofanyoftheseprinciples,it’sconsideredasacodesmellandresultsinrefactoring.Atthecoreofthetasksthatwesolveeverydayindevelopment,oftenliethecommonproblemsthatwemeetagainandagain.Inthischapter,wewillcoverthemostcommonuniversalarchitecturalsolutionsandconceptsinJavaScriptdevelopment:
DesignpatternsinJavaScriptUnderstandingconcernseparationinJavaScriptusingJavaScriptMV*Frameworks
DesignpatternsinJavaScriptAbstractbulletproofsolutionshavebeenknownforlongandareusuallyreferredtoasDesignPatterns.Theoriginal23DesignPatternsinprogrammingwerefirstcollectedinDesignPatterns:ElementsofReusableObject-OrientedSoftware,aninfluentialbookpublishedin1995byErichGamma,RichardHelm,RalphJohnson,andJohnVlissides(GoF).Thesepatternsarelanguage-agnostic.Nonetheless,AddyOsmaniinhisonlinebookLearningJavaScriptDesignPatterns(http://addyosmani.com/resources/essentialjsdesignpatterns/book/)showshowsomeoftheGoF’spatternscanbeimplementedparticularlyinJavaScript.
Herewewon’trepeathiswork;insteadwe’llexaminehowwecancombinethepatterns.OneofthecommonproblemsinJavaScriptdevelopmentiscommunicationbetweendynamicallycreatedobjects.Forinstance,wehaveanobjectandneedtocallamethod(baz)ofobjectbarfromfoo.However,wecannotknowifbarisalreadyavailable.GoF’spatternmediatorencouragesustodedicateanobjectthatisusedtoproxycommunicationsbetweenotherobjects.Thus,wepromoteloosecouplingbykeepingobjectsfromdirectinteraction.Inourcase,despitecallingbar.baz,weinformthemediatoraboutourintent.Themediatorwilldothecallwhenbarisavailable:
"usestrict";
classEventEmitter{
/**Initialize*/
constructor(){
/**
*@accessprivate
*@type{EventHandler[]}
*/
this.handlers=[];
}
/**
*Subscribeacbhandlerforagiveneventintheobjectscope
*@param{String}ev
*@param{Function}cb
*@param{Object}[context]
*@returns{EventEmitter}
*/
on(ev,cb,context){
this.handlers.push({
event:ev,
callback:cb,
context:context
});
returnthis;
}
/**
*Emitagiveneventintheobject
*@param{String}ev
*@param{...*}[arg]
*@returns{EventEmitter}
*/
trigger(ev,...args){
this.handlers.forEach(function(evObj){
if(evObj.event!==ev||!evObj.callback.apply){
return;
}
evObj.callback.apply(evObj.context||this,args);
},this);
returnthis;
}
}
window.mediator=newEventEmitter();
Here,weusedtheES6syntax,whichservesjustperfectlytodescribeacodedesign.WithES6,theintendcanbeshowntobeconciseandplain,whileintheJavaScripteditionES5andolderweneedadditionallinesofcodetoachievethesameresult.
Intheprecedingexample,wecreatedamediatorobjectbyinstantiatingtheEventEmitterclass.EventEmitterimplementsamessagingpatternknownasPubSub.Thispatterndescribesamessageexchangewhereoneobjectsendsaneventaddressedtoanotherobjectandthesecondobjectcallsthehandlers,ifany,whichsubscribedfortheevent.Inotherwords,ifwesubscribeahandlerfunctionofthefooobjectforthemyeventmediatorevent(mediator.on),wecantheninvokethehandleroffoofromanyotherobjectbypublishingthemyeventeventonthemediator(mediator.trigger).Let’slookatanexample.Ourimaginaryapplicationislocalized.Itstartswithaloginscreen.Whenuserssignsin,thescreenjumpstothedashboardwiththenews.Usermaychangethelanguageonanyofthescreens.However,inthefirststage,thenewsviewobjectisn’tyetevencreated,whileinthesecondstage,theloginviewobjectisalreadydestroyed.However,ifweusethemediator,wecantriggerthetranslateeventandalltheavailablesubscriberswillreceivethemessage:
classNews{
/**Initialize*/
constructor(){
mediator.on("translate",this.update,this);
}
/**@param{String}lang*/
update(lang){
//fetchnewsfromremotehostforagivenlang
console.log("Newsloadedfor",lang);
}
}
classLanguage{
/**@param{String}lang*/
change(lang){
mediator.trigger("translate",lang);
}
}
letlanguage=newLanguage();
newNews()
language.change("de");
Wheneverauserchangesthelanguage(language.change),thecorrespondingeventisbroadcastedthroughthemediator.Whenthenewsinstanceisavailable,itcallstheupdatemethodthatreceivesaneventpayload.Inarealapplication,thisinstancewouldloadnewsforthegivenlanguageandupdatetheview.
Sowhatdidweachieve?Whenusingamediatorandanevent-drivenapproach(PubSub),ourobjects/modulesarelooselycoupledandtherefore,theoverallarchitecturebetteracceptsrequirementchanges.Besides,wegainmoreflexibilityinunittesting.
Atthetimethisbookwaswritten,nobrowserprovidednativesupportfortheES6classstatement.However,youcanrunthegivencodeusingBabel.jsrun-time(https://babeljs.io/docs/usage/browser/)ortranspiring.
Whentheapplicationgrowsandwearegettingtoomanyeventsthatarecirculating,itmakessensetoencapsulateeventhandlingintoaseparatemessagehubobject.HerecomestomindtheFacadepattern,whichdefinesaunifiedhigh-levelinterfaceforotherinterfaces:
classFacade{
constructor(){
mediator.on("show-dashboard",function(){
this.dashboard.show()
this.userPanel.remove();
},this)
.on("show-userpanel",function(a){
this.dashboard.hide()
this.userPanel=newUserPanel(this.user);
},this)
.on("authorized",function(user){
this.user=user;
this.topBar=newTopBar(user.name);
this.dashboard=newDashboard(user.lang);
this.mainMenu=newMainMenu(user.lang);
},this)
.on("logout",function(){
this.userPanel.remove();
this.topBar.remove();
this.dashboard.remove();
this.mainMenu.remove();
this.login=newLogin();
},this);
}
}
AfterinitializingtheFacadeclass,wecantriggeracomplexflowwheremultiplemodulesareinvolvedbysimplyfiringaneventonthemediator.Thiswayweencapsulatebehaviorallogicintoadedicatedobject;thismakesthecodemorereadableandthewholesystemeasiertomaintain.
UnderstandingconcernseparationinJavaScriptWhilewritingJavaScript(especiallyclient-side),oneofthemajorchallengesistoavoidspaghetticode,wherethesamemodulerenderstheuserview,handlesuserinteractions,anddoesthebusinesslogic.Suchamodulemayquicklygrowintoamonsterofasourcefile,whereadeveloperrathergetslostthanspotsandresolvesaproblem.
TheMVCProgrammingparadigmknownasModelViewController(MVC)splitstheapplicationfunctionalityintoseparatelayerssuchaspresentation,data,anduserinput.MVCinanutshellimpliesthatauserinteractswiththeviewlandinacontrollermodulethatmanipulatesamodel,whichupdatestheview.InJavaScript,thecontrollerisusuallyanobserverthatlistenstoUIevents.Auserclicksabutton,theeventisfired,andthecontrolleraddressesthecorrespondingmodel.Forexample,thecontrollerrequeststhemodeltosendsubmitteddatatotheserver.Theviewisnotifiedaboutthemodelstatechangeandreactsaccordingly,let’ssayitdisplaysamessage,Datasaved.CollaborationofcomponentsinMVCpatternisshowninthefollowingimage:
Asyousee,wecankeepalltheuserinputhandlersencapsulatedinasinglemodule(heretheController),wecanabstractthedatalayerfollowingDomain-Drivendesignpracticesintoamodelmodule.Eventually,wehaveaviewmoduleresponsibleforupdatingtheUI.So,themodelhasnoknowledgeaboutthecomponent’spresentation(HTML,CSS)andknowsnothingaboutDOMevents—that’sjustpuredataandoperationsonit.ThecontrollerknowsonlytheeventsfromtheviewandtheviewAPI.Andfinally,theviewknowsnothingaboutthemodelandcontroller,butexposesitsAPIandsendsevents.Thus,wehaveanefficientarchitecturethatiseasytomaintainandtest.
However,inthecaseofaJavaScript-builtUI,it’snotthateasytodrawalinebetweenthe
viewlogicandthecontrollerone.HerewegethandyMVCderivatives:MVPandMVVM.MVP.
ThePinMVPstandsforPresenterthatservesuserrequests.Thepresenterlistenstotheviewevents,retrievesdata,manipulatesit,andupdatesthepresentationusingtheviewAPI.ThePresentercaninteractwithmodelstopersistdata.Asyoucanseeinthefollowingdiagram,thePresenteractslikeamanagerthatreceivesarequest,processesitusingavailableresources,anddirectstheviewtochange.FollowingimageshowscollaborationofcomponentsinMVPpattern:
MVPprovidesbettertestabilityandconcernseparationcomparedtoMVC.YoucanfindanexampleofaTODOapplicationimplementingMVPathttp://codepen.io/dsheiko/pen/WQymbG.
MVVMApassiveviewofMVPismostlyaboutdatabindingsandproxyingofUIevents.Infact,that’ssomethingwecanabstract.TheviewinModelViewViewModel(MVVM)approachmaynotrequireanyJavaScriptatall.Usually,theviewisHTML-extendedwithdirectivesknownbyViewModel.Themodelrepresentsdomain-specificdataandexposesconcomitantmethodssuchasvalidation.TheViewModelisamiddlemanbetweenviewandmodel.Itconvertsthedataobjectsfromthemodelfortheview.Forinstance,whenamodelpropertycontainsarawdatetime,theViewModelconvertsitintotheformexpectedintheviewlike1January2016,00:01.FollowingimageshowscollaborationofcomponentsinMVVMpattern:
TheMVVMpatternhastheadvantageofbothimperativeanddeclarativeprogramming.Itmaydrasticallyreducethedevelopmenttimebyabstractingmostofthegenericviewlogicinacommonbindermodule.ThepatterngainsmomentumwithpopularJavaScriptframeworkssuchasKnockout,Angular,andMeteor.YoucanfindanexampleofanRSSreaderapplicationbasedonMVVMpatternathttps://msdn.microsoft.com/en-us/magazine/hh297451.aspx.
UsingJavaScriptMV*frameworksWhenstartinganewscalablewebapplication,youhavetodecidewhethertogowithaframeworkornot.It’shardnowtofindanylargeprojectsthatarenotbuiltonthetopofaframework.Yettherearedrawbacksinusingframeworks;justtakealookatZeroFrameworkManifesto(http://bitworking.org/news/2014/05/zero_framework_manifesto).However,ifyoudecideinfavorofframeworks,thenyouareinaquandaryaboutwhichonetopick.Thisisindeednotaneasytask.JavaScriptframeworkstodayarequitenumerous;justtakealookatthevarietyavailableatTodoMVC(http://todomvc.com).It’shardlyfeasibletoreviewallofthem,butwecanbrieflyexamineafewofthemostpopularframeworks.Accordingtorecentsurveys(forexample,http://ashleynolan.co.uk/blog/frontend-tooling-survey-2015-results),amongthemosttrendyareAngular,React,andBackbone.Allthreegivequitedissimilardevelopmentparadigms.SotheyarefittingtomakeanoverallpictureofJavaScriptframeworksingeneral.
BackboneBackbone(http://backbonejs.org)isverylightweightandeasytostartwith.Thisistheonlypopularframeworkwhereyoucangrasptheentirecodebaseinaconsiderablyshorttime(http://backbonejs.org/docs/backbone.html).Inherently,Backbonegivesyouaconsistentabstractionandnothingbesidesthis.Byandlarge,weencapsulatealltheUI-relatedlogicintoasubtypeofBackbone.View.Anydatarequiredbytheview,weputthisintoaderivativeofBackbone.ModelorBackbone.Collection(whenit’salistofentries).Eventually,weroutehash-basednavigationrequestsbymeansofBackbone.Route.
Let’sconsideranexample.Ourimaginaryapplicationallowsustolookupforacontactbyagivenemailaddress.Sincewewantthistobeuserfriendly,theapplicationformisexpectedtovalidateaswetypeinit.Forthis,weneedalittleHTML:
<formdata-bind="fooForm">
<labelfor="email">Email:</label>
<inputid="email"name="email"required/>
<spanclass="error-msg"data-bind="errorMsg"></span>
<buttondata-bind="submitBtn"type="submit">Submit</button>
</form>
Herewehaveaninputcontrol,asubmitbutton,andacontainerforapossibleerrormessage.Inordertomanagethis,wewillusethefollowingBackbone.View:
ContactSearchView.js
"usestrict";
/**@class{ContactSearchView}*/
varContactSearchView=Backbone.View.extend(/**@lends
ContactSearchView.prototype*/{
events:{
"submit":"onSubmit"
},
/**@constructs{ContactSearchView}*/
initialize:function(){
this.$email=this.$el.find("[name=email]");
this.$errorMsg=this.$el.find("[data-bind=errorMsg]");
this.$submitBtn=this.$el.find("[data-bind=submitBtn]");
this.bindUi();
},
/**Bindhandlers*/
bindUi:function(){
this.$email.on("input",this.onChange.bind(this));
this.model.on("invalid",this.onInvalid.bind(this));
this.model.on("change",this.onValid.bind(this));
},
/**Handleinputonchangeevent*/
onChange:function(){
this.model.set({
email:this.$email.val(),
//Hacktoforcemodelrunningvalidationonrepeatingpayloads
"model:state":(1+Math.random())*0x10000
},{validate:true});
},
/**Handlemodelininvalidstate*/
onInvalid:function(){
varerror=arguments[1];
this.$errorMsg.text(error);
this.$submitBtn.prop("disabled","disabled");
},
/**Handlemodelinvalidstate*/
onValid:function(){
this.$errorMsg.empty();
this.$submitBtn.removeProp("disabled");
},
/**Handleformsubmit*/
onSubmit:function(e){
e.preventDefault();
alert("Lookingupfor"+this.model.get("email"));
}
});
Intheconstructor(theinitializemethod),webindtheactingnodesoftheHTMLtothepropertiesoftheviewandsubscribehandlerstoUIandthemodelevents.Then,weregisterlistenermethodsonthesubmitformandtheinputform.Thesecondhandlerisinvokedaswetype,anditupdatesthemodel.Themodelrunsavalidation,andaccordingtotheresults,itrespondswithainvalidorchangemodelevent.Inthecaseoftheinvalidevent,theviewshowsuptheerrormessage,otherwiseit’shidden.
Nowwecanaddthemodel,asshownhere:
ContactSearchModel.js
"usestrict";
/**@class{ContactSearchModel}*/
varContactSearchModel=Backbone.Model.extend(/**@lends
ContactSearchModel.prototype*/{
/**@type{Object}*/
defaults:{
email:""
},
/**
*Validateemail
*@param{String}email
*/
isEmailValid:function(email){
varpattern=/^[a-zA-Z0-9\!\#\$\%\&\'\*\+\-\/\=\?\^\_\`\
{\|\}\~\.]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,4}$/g;
returnemail.length&&pattern.test(email);
},
/**
*Validatemodel
*@param{Map}attrs
*/
validate:function(attrs){
if(!attrs.email){
return"Emailisrequired.";
}
if(!this.isEmailValid(attrs.email)){
return"Invalidemailaddress.";
}
}
});
Thismodeldefinesdomaindatainthedefaultspropertyandprovidesthevalidatemethodthatiscalledautomaticallywhenwesetorsavethemodel.
Nowwecancombinealltogetherandinitializetheview:
<!DOCTYPEhtml>
<html>
<scripttype="text/javascript"
src="//ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<scripttype="text/javascript"
src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.5.2/underscore-
min.js"></script>
<scripttype="text/javascript"
src="//cdnjs.cloudflare.com/ajax/libs/backbone.js/1.0.0/backbone-min.js">
</script>
<scripttype="text/javascript"src="ContactSearchView.js"></script>
<scripttype="text/javascript"src="ContactSearchModel.js"></script>
<style>
fieldset{border:0;}
.error-msg{color:red;}
</style>
<body>
<formdata-bind="fooForm">
<fieldset>
<labelfor="email">Email:</label>
<inputid="email"name="email"required/>
<spanclass="error-msg"data-bind="errorMsg"></span>
</fieldset>
<fieldset>
<buttondata-bind="submitBtn"type="submit">Submit</button>
</fieldset>
</form>
<script>
//Renderfooview
newContactSearchView({
el:$("[data-bind=fooForm]"),
model:newContactSearchModel
});
</script>
</body>
</html>
Backboneitselfissurprisinglysmallinsize(6.5Kgzipped),butwiththejQueryandUnderscoredependencies,thismakesquiteabundle.Bothdependencieswerevitalinthepast,butnowthat’sunderthequestion—doweneedthematall?So,itmakessensetochecktheExoskeleton(http://exosjs.com/)project,whichisanoptimizedversionofBackbonethatworksperfectlywithoutthedependencies.
AngularAngular(http://Angular.org)nowseemstobethemostpopularJavaScriptframeworkintheworld.ItissupportedbyGoogleandisconsideredasaframeworkthatsolvesmostroutinetasksforyou.Inparticular,Angularhasafeaturecalledtwo-waybinding,meaningthatUIchangespropagatetotheboundmodeland,viceversa,andmodelchanges(forexample,byXHR)updatetheUI.
InAngularJS,wedefinebehaviorstraightinHTMLwithdirectives.DirectivesarecustomelementsandattributesthatassumeUIlogicsimilartowebcomponents.Actually,youcancreatefunctionalwidgetsinAngularJSwithoutwritingasinglelineofJavaScriptcode.ModelsinAngularJSaresimpledatacontainersandunlikeBackbone,havenoconnectiontoexternalsources.Whenweneedtoreadorwritedata,weuseservices.WhenanydataissenttoView,wecanusefilterstoformattheoutput.Theframeworkleveragesdependencyinjection(DI)patternallowingtoinjectcorecomponentsintoeachotherasdependencies.Thatmakesthemoduleseasiertomeetrequirementchangesandunit-test.Let’sseethisinpractice:
<!DOCTYPEhtml>
<html>
<script
src="http://ajax.googleapis.com/ajax/libs/Angular/1.3.14/angular.min.js">
</script>
<style>
fieldset{border:0;}
.error-msg{color:red;}
</style>
<body>
<formng-app="contactSearch"name="csForm"ng-submit="submit()"ng-
controller="csController">
<fieldset>
<labelfor="email">Email:</label>
<inputid="email"name="email"ng-model="email"required
ng-pattern="/^[a-zA-Z0-9\!\#\$\%\&\'\*\+\-\/\=\?\^\_\`\
{\|\}\~\.]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,4}$/"/>
<spanclass="error-msg"ng-show="csForm.email.$dirty&&
csForm.email.$invalid">
<spanng-show="csForm.email.$error.required">Emailisrequired.
</span>
<spanng-show="csForm.email.$error.pattern">Invalidemailaddress.
</span>
</span>
</fieldset>
<fieldset>
<buttontype="submit"ng-disabled="csForm.email.$dirty&&
csForm.email.$invalid">Submit</button>
</fieldset>
</form>
<script>
"usestrict";
angular.module("contactSearch",[]).controller("csController",[
"$scope",function($scope){
$scope.email="";
$scope.submit=function(){
alert("Lookingupfor"+$scope.email);
};
}]);
</script>
</body>
</html>
Inthisexample,wedeclaredaninputfieldandboundittoamodelemail(ng-modeldirective).FormvalidationworksinthesamewayasinHTML5forms:ifwedeclareaninputtypeemailanditgetsvalidatedaccordingly.Herewegowithadefaulttexttypeandusetheng-pattern(similartoHTML5’spattern)attributetosetthesamevalidationrulesforemailasintheBackbonecase.Further,werelyontheng-showdirectivetodisplayerrormessagesblockwhentheinputstateisempty(csForm.email.$dirty)orinvalid(csForm.email.$invalid).Inthiscase,thesubmitbutton,onthecontrary,ishidden.Usingtheng-controllerandng-submitdirectives,webindthecsControllercontrollerandtheon-submithandlertotheform.InthebodyofcsController(JavaScript),$scope.submitexpectsahandlerfunctionfortheformsubmitevent.
AsyoucanseewithAngular,ittakesmuchlesscodeintotaltoimplementthesametask.However,oneshouldacceptthatkeepingapplicationlogicinHTMLmakesitreallyhardtoreadthecode.
Furthermore,Angularsubscribesmanywatchersperdirective(intendedhandlers,automaticdirtychecking,andsoon)andmakesitslowandresource-expensiveonthepageswithnumerousinteractiveelements.Ifyouwanttotuneyourapplicationperformance,youratherlearnAngularsourcecodeandit’llbeachallengingtaskwith~11.2Klinesofcode(version1.4.6).
ReactReact(https://facebook.github.io)isaprojectofFacebookthatisn’treallyaframework,butratheralibrary.TheuniqueapproachofReactimpliesacomponent-basedapplication.Inherently,ReactdefinestheViewsofthecomponentsutilizingtheso-calledVirtualDOM,whichmakesUIrenderingandupdatingsurprisinglyfast.WiththisfocusonView,Reactcomprisesatemplateengine.Optionally,theReactcomponentscanbewritteninasubsetofJavaScriptcalledJSXwhereyoucanputHTMLtemplateswithinJavaScript.JSXcanbeparseddynamicallyasinthefollowingexample,oritcanbeprecompiled.SinceReactdealswithViewsonlyandmakesnoassumptionsaboutotherconcerns,itmakessensetousethisinconjunctionwithotherframeworks.Thus,Reactcanbepluggedintoaframework(forexample,asdirectivesinAngularorViewsinBackbone).
Whileimplementingthecontactsearchapplicationthistime,wewilluseReacttocontroltheViewofourexamplebysplittingitintotwocomponents(FormViewandEmailView).ThefirstonedefinestheViewforthesearchform:
/**@class{FormView}*/
varFormView=React.createClass({
/**Createaninitialstatewiththemodel*/
getInitialState:function(){
return{
email:newEmailModel()
};
},
/**
*Updatestateoninputchangeevent
*@param{String}value-changedvalueoftheinput
*/
onChange:function(value){
this.state.email.set("email",value);
this.forceUpdate();
},
/**Handleformsubmit*/
onSubmit:function(e){
e.preventDefault();
alert("Lookingupfor"+this.state.email.get("email"));
},
/**Renderform*/
render:function(){
return<formonSubmit={this.onSubmit}>
<fieldset>
<labelhtmlFor="email">Email:</label>
<EmailViewmodel={this.state.email}onChange={this.onChange}/>
</fieldset>
<fieldset>
<buttondata-bind="submitBtn"type="submit">Submit</button>
</fieldset>
</form>;
}
});
Intherendermethod,wedeclaredtheViewofthecomponentusingtheJSXnotation.
ThismakesitmucheasiertomanipulatetheVirtualDOM.SimilartoAngular,wecanaddressthecomponentscopedirectlyintheHTML.Thus,wesubscribetotheformsubmiteventandtotheinputchangeeventbyreferringtothecorrespondinghandlersintheonSubmitandonChangeattributes.SinceReactprovidesnobuilt-inmodel,wereusedContactSearchModel,themodelwecreatedwhileexploringBackbone.
YoumightnoticeaEmailViewcustomtaginJSX.Thisishowwerefertooursecondcomponent,whichrepresentsanemailinputcontrol:
/**@class{EmailView}*/
varEmailView=React.createClass({
/**
*Delegateinputon-changedeventtothefromview
*@param{Event}e
*/
onChanged:function(e){
this.props.onChange(e.target.value);
},
/**Renderinput*/
render:function(){
varmodel=this.props.model;
return<span>
<inputid="email"type="text"value={model.email}onChange=
{this.onChanged}/>
<spanclassName="error-msg"data-bind="errorMsg">{model.isValid()?
"":model.validationError}</span>
</span>;
}
});
Hereweboundtheemailinputtothemodelandtheerrormessagecontainertothemodelstate.WealsopassedtheinputonChangeeventtotheparentcomponent.
Well,nowwecanaddthecomponentsintheHTMLandrendertheform:
<!DOCTYPEhtml>
<html>
<head>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/react.js">
</script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/JSXTransformer.js"
></script>
<scripttype="text/javascript"
src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.5.2/underscore-
min.js"></script>
<scripttype="text/javascript"
src="//cdnjs.cloudflare.com/ajax/libs/backbone.js/1.0.0/backbone-min.js">
</script>
<scripttype="text/javascript"src="ContactSearchModel.js"></script>
<style>
fieldset{border:0;}
.error-msg{color:red;}
</style>
</head>
<body>
<divdata-bind="app"></div>
<scripttype="text/jsx">
/**@jsxReact.DOM*/
//Pleaseinsertherebothcomponents
//FormViewandEmailView
//renderapp
React.render(
<FormView/>,
document.querySelector("[data-bind=app]")
);
</script>
</body>
</html>
Weaddressthecomponentsinthetemplatessuchasweb-componentsbythecorrespondingcustomelements.Donotconfuseyourselfoverthesimilarity,Reactcomponentsareabstractedfromthebrowser,whileweb-componentsworksimilartobrowser-nativethings.ThecoreconceptofReactisthattheVirtualDOMallowsustoavoidunnecessaryDOMreflowcyclesthatmakethelibrarypreferableforhigh-performanceapplications.ReactisreallygoodtorenderstaticpagesontheserverusingNode.js.Thus,wecanreuseapplicationcomponentsbetweenserverandclientsides.
SummaryWritingmaintainablecodeisanart.ProbablythebestbookthatprovidesguidanceonthisisCleanCode:AHandbookofAgileSoftwareCraftsmanshipbyRobertC.Martin.It’saboutnamingfunctions,methods,classes,commenting,codeformatting,andofcourse,aboutthecorrectuseofOOPandSOLID.However,whenreusingsolutionsdescribedinthisbook,orinanyoftheDesignPatternsseries,wehavetotranslatethemintoJavaScript,anditcanbechallengingduetothenatureofthelanguage.Onahigherlevel,wehavetosplitthecodeintolayerssuchaspresentation,businesslogic,dataaccess,andpersistence,whereeachbundleofcodeaddressestheoneconcernandislooselycoupledwithothers.Here,wemaychooseanapproachtogowith.IntheJavaScriptworld,it’susuallyaderivativeofMVC(MVPorMVVMorother).Consideringthis,adecentprogrammingdesignrequiresalotofabstraction.Today,wecanusenumerousframeworks.Theyprovidediverseprogrammingparadigms.
Chapter7.JavaScriptBeyondtheBrowserOriginally,JavaScriptwasdesignedasaclient-sidescriptinglanguage,buttoday,itisusedliterallyeverywhere:inserverscripting,mobileanddesktopsoftwareprogramming,gamedevelopment,DBquerying,hardwarecontrol,andOSautomation.Whenyouhaveexperienceinclient-sideJavaScript,withalittleadditionalknowledge,youcanapplyyourskillsinotherprogrammingareasaswell.Here,wewilllearnhowtowriteacommand-linetool,web-server,desktopapplication,andmobilesoftwareusingJavaScript.
Inthischapter,wewillbelearningaboutthefollowing:
Levelingupthecodingofacommand-lineprograminJavaScriptBuildingaweb-serverwithJavaScriptWritingadesktopHTML5applicationUsingPhoneGaptomakeamobilenativeapp
Levellingupthecodingofacommand-lineprograminJavaScriptYoumusthaveheardaboutNode.js.Thisisanopensourcecross-platformdevelopmentenvironmentthatallowsthecreationofweb-servers,networking,andothertoolsusingJavaScript.Node.jsextendsclassicalJavaScriptwithacollectionofspecializedmodules.ThesemoduleshandlefilesystemI/O,networking,OS-leveloperations,binarydata,cryptographyfunctions,datastreams,andothers(https://nodejs.org/api/index.html).Node.jsusesanevent-drivenI/Omodel.SimilartoJavaScript,itoperatesonsingle-threadperformingnon-blockingcalls.Sotimeconsumingfunctionscanrunconcurrentlybyinvokingacallbackwhenitcompletes.
TogetthefeelofNode.js,westartwithanexamplethatsimplyprintsHelloworld:
hello.js
console.log("Helloworld!");
Nowlet’sopentheconsole(command-lineinterface:CMDinWindows,orTerminalinLinuxandMacOS),navigatetotheexamplescriptlocation,andrunthefollowing:
nodehello.js
Herewego,wegetHelloworld!intheoutput.
FollowingscreenshotshowstheWindowsCMD
TheNode.jsmodulesfollowtheCommonJSspecificationinthesamewaythatweexaminedinChapter2,ModularProgrammingwithJavaScript:
foo.js
console.log("Runningfoo.js");
module.exports="foo";
main.js
varfoo=require("./foo");
console.log("Runningmain.js");
console.log("Exportedvalue:",foo);
Aswerunmain.js,wearesupposedtogetthefollowingoutput:
Runningfoo.js
Runningmain.js
Exportedvalue:foo
TheNode.jsnativemodulessuchasfs(https://nodejs.org/api/index.html)don’trequiredownloading.Wemayjustrefertotheminrequire(),andattheruntime,itwillbeknownwheretofindthem:
"usestrict";
varfs=require("fs");
fs.readFile(__filename,"UTF-8",function(err,data){
if(err){
thrownewError(err);
}
console.log("Sourceof",__filename,":\n",data);
});
HereweusethefilesystemI/O(fs)moduletoreadafile.The__filenamepropertyofamodulescopecontainstheabsolutepathoftheexecutingsourcefile.RemembertheErrorFirstCallbackapproachthatweexaminedinChapter5,AsynchronousJavaScriptThatisthemaininterfaceforasynchronousfunctionsinNode.js.
Let’snowtrysomethingmorepractical.We’llwriteautilitythatrecursivelyscansallthesourcefilesinagivendirectorytomakesureeveryfilehasblockcommentswithup-to-datecopyrights.Firstofall,weneedamodulethatcantestwhetherasuppliedblockcommenttextcontainstheactualcopyrightline:
./Lib/BlockComment.js
/**
*Blockcommententity
*@class
*@param{String}code
*/
varBlockComment=function(code){
return{
/**
*Checkablockcomment
*@returns{Boolean}
*/
isValid:function(){
varlines=code.split("\n");
returnlines.some(function(line){
vardate=newDate();
returnline.indexOf("@copyright"+date.getFullYear())!==-1;
});
}
};
};
module.exports=BlockComment;
Here,wehaveaconstructorthatcreatesanobjectrepresentingBlockComment.Theobjecthasamethod(isValid)totestitsvalidity.SoifwecreateaninstanceofBlockCommentwithablockcommenttext,wecanvalidatethisagainstourrequirements:
varcomment=newBlockComment("/**\n*@copyright2015\n*/");
comment.isValid()//true
Now,wewillwriteamodulecapableoftestingwhetherallthecopyrightlinesinagivensourcecodehastheactualyear:
./Lib/SourceFile.js
/**@type{module:esprima}*/
varesprima=require("esprima"),
/**
*Sourcefileentity
*@class
*@param{String}fileSrc
*@param{module:Lib/BlockComment}BlockComment-dependencyinjection
*/
SourceFile=function(fileSrc,BlockComment){
return{
/**
*Testifsourcefilehasvalidcopyright
*/
isValid:function(){
varblockComments=this.parse(fileSrc);
returnBoolean(blockComments.filter(function(comment){
returncomment.isValid();
}).length);
},
/**
*ExtractalltheblockcommentsasarrayofBlockCommentinstances
*@param{String}src
*@returns{Array}-collectionofBlockComment
*/
parse:function(src){
returnesprima.parse(src,{
comment:true
}).comments.filter(function(item){
returnitem.type==="Block";
}).map(function(item){
returnnewBlockComment(item.value);
});
}
};
};
module.exports=SourceFile;
InthisexampleweintroducedaSourceFileobjectthathastwomethods,parseandisValid.Theprivatemethod,parse,extractsalltheblockcommentsfromagivenJavaScriptsourcecodeandreturnsanarrayoftheBlockCommentobjects.TheisValidmethodcheckswhetherallthereceivedBlockCommentobjectsmeetourrequirements.Inthesemethods,tomanipulatearrays,weuseArray.prototype.filterandArray.prototype.mapthatweexaminedinChapter1,DivingintoJavaScriptCore.
ButhowcanwereliablyextractblockCommentsfromaJavaScriptsource?Thebestwayistogowithabulletproofsolutioncalledtheesprimaparser(http://esprima.org/)that
performscodestaticanalysisandreturnsafullsyntaxtreeincludingcomments.However,esprimaisathird-partypackagethatissupposedtobedownloadedandlinkedfromtheapplication.Ingeneral,apackagemaydependonotherpackages,whichalsohavedependencies.Itlookslikethatbringingtherequireddependenciestogethermaybeahellofawork.Fortunately,Node.jsisdistributedwiththeNPMpackagemanager.ThetoolcanbeusedtoinstallandmanageintheNPMrepository(https://www.npmjs.com/)third-partymodules.NPMdoesn’tjustdownloadtherequestedmodules,butalsoresolvesthemoduledependencies,allowingawell-grainedstructureofreusablecomponentsinthescopeofaprojectorintheglobalscope.
So,tomakeesprimaavailableinourapplication,wesimplyrequestitfromNPMusingthiscommand:npminstallesprima.
Byrunningthiscommandintheconsole,weautomaticallygetanewnode_modulessubdirectorywiththeesprimapackageinit.Ifthepackagerequiresanydependencies,theywillbefetchedandallocatedinnode_modules.AssoonasthepackageisinstalledbyNPM,Node.jscanfinditbyitsname.Forexample,require("esprima").NowwhenwehavetheSourceFileobject,wejustneedthemainscriptthatwillreadthefilesfromagivendirectoryandtestthemwithSourceFile:
copyright-checker.js
/**@type{module:cli-color}*/
varclc=require("cli-color"),
/**@type{module:fs-walk}*/
walk=require("fs-walk"),
/**@type{module:path}*/
path=require("path"),
/**@type{module:fs}*/
fs=require("fs"),
/**
*Sourcefileentity
*@type{module:Lib/SourceFile}
*/
SourceFile=require("./Lib/SourceFile"),
/**@type{module:Lib/BlockComment}*/
BlockComment=require("./Lib/BlockComment"),
/**
*Command-linefirstargument(ifnonegiven,gowith".")
*@type{String}
*/
dir=process.argv[2]||".";
console.log("Checkingin"+clc.yellow(dir));
//Traversedirectorytreerecursivelybeginningfrom'dir'
walk.files(dir,function(basedir,filename){
/**@type{Function}*/
varnext=arguments[3],
/**@type{String}*/
fpath=path.join(basedir,filename),
/**@type{String}*/
fileSrc=fs.readFileSync(fpath,"UTF-8"),
/**
*Getentityassociatedwiththefilelocatedinfpath
*@type{SourceFile}
*/
file=newSourceFile(fileSrc,BlockComment);
//ignorenon-jsfiles
if(!filename.match(/\.js$/i)){
returnnext();
}
if(file.isValid()){
console.log(fpath+":"+clc.green("valid"));
}else{
console.log(fpath+":"+clc.red("invalid"));
}
next();
},function(err){
err&&console.log(err);
});
Inthiscode,wereliedonathird-partymodule,cli-color,tocolorizethecommand-lineoutput.Weusedthefs-walkmoduletorecursivelytraversethroughadirectory.AndtheNode.jsnativemodule,path,allowsustoresolvetheabsolutepathbyagivenrelativedirectoryandfilename,andthefsbuilt-inmoduleisusedtoreadafile.
Asweintendtorunourapplicationfromtheconsole,wecanusecommand-lineoptionstopassonadirectorythatwewanttotest:
nodecopyright-checker.jssome-dir
Wecanextractscriptargumentsfromabuilt-inprocess(process.argv)object.Forthiscommand,process.argvwillcontainanarraylikethis:
["node","/AbsolutePath/copyright-checker.js","some-dir"]
Sointhemainscript,nowwecanpassthethirdelementofthisarraytowalk.files.Thefunctionwilltraversethroughthegivendirectoryandrunthecallbackforeveryfilefound.Inthecallbackfunction,ifafilenamelookslikeJavaScript,wereadthecontentandtestitusingtheSourceFileobject.
Beforewecanrunthemainscript,weneedtoaskNPMforthird-partypackagesthatwearegoingtouseinthescript:
npminstallfs-walkcli-color
Nowwearegoodtogo.Aswerunnodecopyright-checker.jsfixtures,wegetareportonthevalidityoftheJavaScriptfileslocatedinfixtures.
FollowingscreenshotshowstheMacOSXterminal:
BuildingawebserverwithJavaScriptWe’vejustlearnthowtowritecommand-linescriptswithNode.js.However,thisrun-timeismostlyknownasserver-sideJavaScript,meaningthisisthesoftwaretorunanHTTP-server.Actually,Node.jsisespeciallygreatforthiskindofjob.IfwelaunchaserverapplicationbasedonNode.js,itrunspermanently,initializedonlyonce.Forinstance,wemaycreateasingleDBconnectionobjectandreuseitwheneversomeonerequeststheapplication.Besides,itgrantsusalltheflexibilityandpowerofJavaScriptincludingevent-driven,non-blockingI/O.
Sohowcanwemakeuseofthis?ThankstotheHTTPnativemoduleofNode.js,asimpleweb-servercanbeimplementedaseasyasthis:
simple-server.js
"usestrict";
/**@type{module:http}*/
varhttp=require("http"),
/**@type{HttpServer}*/
server=http.createServer(function(request,response){
response.writeHead(200,{"Content-Type":"text/html"});
response.write("<h1>Requested:"+request.url+"</h1>");
response.end();
});
server.listen(80);
console.log("Serverislistening…");
HerewecreatedaserverwithadispatchercallbacktohandleHTTPrequests.Then,wemaketheserverlistenonport80.Nowrunnodesimple-server.jsfromtheconsole,andthenhithttp://localhostinabrowser.Wewillseethefollowing:
Requested:/
Sowejustneedtorouteincomingrequests,readthecorrespondingHTMLfiles,andsendthemwiththeresponsetomakeasimplestaticwebserver.Orwecaninstalltheexistingmodules,connectandserve-static:
npminstallconnectserve-static
Andimplementtheserverusingthis:
"usestrict";
/**@type{module:connect}*/
varconnect=require("connect"),
/**@type{module:serve-static}*/
serveStatic=require("serve-static");
connect().use(serveStatic(__dirname)).listen(80);
Inpractice,routingrequestscanbeachallengingtask,sowerathergowithaframework.Forexample,Express.js(http://expressjs.com).Then,ourroutingmaylooklikethis:
"usestrict";
/**@type{module:express}*/
varexpress=require("express"),
/**@type{module:http}*/
http=require("http"),
/**@type{Object}*/
app=express();
//SendcommonHTTPheaderforeveryincomingrequest
app.all("*",function(request,response,next){
response.writeHead(200,{"Content-Type":"text/plain"});
next();
});
//Sayhelloforthelandingpage
app.get("/",function(request,response){
response.end("Welcometothehomepage!");
});
//Showuseifforrequestslikehttp://localhost/user/1
app.get("/user/:id",function(request,response){
response.end("RequestedID:"+req.params.id);
});
//Show`Pagenotfound`foranyotherrequests
app.get("*",function(request,response){
response.end("Opps…Pagenotfound!");
});
http.createServer(app).listen(80);
WritingadesktopHTML5applicationHaveyoueverwonderedaboutwritingadesktopapplicationwithHTML5andJavaScript?Nowadays,wecandothisquiteeasilywithNW.js.Thisprojectisacross-platformapplicationruntimebasedonChromiumandNode.js.So,itprovidesaframelessbrowserwhereboththeDOMAPIandNode.jsAPIareavailable.Inotherwords,wecanrunNW.jsclassicalweb-applications,accesslow-levelAPIs(filesystem,network,processes,andsoon),andreusethemodulesoftheNPMrepository.Interesting?We’llstartatutorialwherewewillcreateasimpleHTML5applicationandrunitwithNW.js.It’llbearosterapplicationwithaformtoenternamesandalistofalreadysubmittedones.ThenameswillbestoredinlocalStorage.Let’srockit.
SettinguptheprojectFirstofall,wehavetodownloadtheNW.jsrun-timerelevanttoourplatform(MacOSX,Windows,orLinux)fromhttp://nwjs.io.NexttotheNW.jsexecutable(nw.exe,new.app,ornw.dependingontheplatform),weplacethepackage,jsonfile(https://github.com/nwjs/nw.js/wiki/manifest-format)wherewedescribeourproject:
{
"name":"roster",
"main":"wwwroot/index.html",
"window":{
"title":"TheRoster",
"icon":"wwwroot/roaster.png",
"position":"center",
"resizable":false,
"toolbar":false,
"frame":false,
"focus":true,
"width":800,
"height":600,
"transparent":true
}
}
Ourpackage.jsonfilehasthreemainfields.namecontainsauniquenameassociatedwiththeproject.Notethatthisvaluewillbeapartofthedirectorypathwhereapplicationdata(sessionStorage,localStorage,andsoon)isstored.mainacceptsarelativepathtothemainHTMLpageoftheproject.Eventually,windowdescribesthebrowserwindowwheretheHTMLwillbedisplayed.
AddingtheHTML5applicationAccordingtothemainfieldinpackage.json,wewillplaceourindex.htmlintothewwwrootsubdirectory.WecantryitwithasimpleHTMLlikethis:
<html>
<body>
Helloworld!
</body>
</html>
NW.jstreatstheHTMLinthesamewayasabrowser,soifwenowlaunchtheNW.jsexecutable,wewillseeHelloworld!.TogiveitlookandfeelwecanaddCSSandJavaScript.SowecanwritethecodeforNW.jsinthesamewayaswedoitforabrowser.Here,wehaveagoodopportunitytoapplytheprinciplesthatwelearnedinChapter6,ALarge-ScaleJavaScriptApplicationArchitecture.Inordertomaketheexampleconcisebutexpressive,wewilltaketheAngularJSapproach.First,wecreatetheHTML.Themarkupofthebodywillbeasfollows:
<mainclass="container">
<form>
<divclass="form-group">
<labelfor="name">Name</label>
<inputclass="form-control">
</div>
<buttonclass="btnbtn-danger">EmptyList</button>
<buttontype="submit"class="btnbtn-primary">Submit</button>
</form>
<tableclass="tabletable-condensed">
<tr>
<td></td>
</tr>
</table>
</main>
Wedefinedaformtosubmitnewnamesandatabletodisplaythealreadystorednames.Tomakeitprettier,weusedBootstrap(http://getbootstrap.com)styles.TheCSSfilecanbeloadedfromaCDNasshownhere:
<linkrel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css
">
NowwewillbringittolifebyaddingtheAngularJSdirectives:
<html>
<bodyng-app="myApp">
<mainng-controller="RosterController"class="container">
<formng-submit="submit()">
<divclass="form-group">
<labelfor="name">Name</label>
<inputclass="form-control"id="name"name="name"ng-
model="name"requiredplaceholder="Name">
</div>
<buttonng-click="empty()"class="btnbtn-danger">Empty
List</button>
<buttontype="submit"class="btnbtn-primary">Submit</button>
</form>
<tableclass="tabletable-condensed">
<trng-repeat="personinpersons">
<td>{{person.value}}</td>
</tr>
</table>
</main>
</body>
</html>
HerewedeclaresamyAppmodulescope(<bodyng-app="myApp">).Withinthis,wedefinedaRosterControllercontroller.Intheboundariesofthecontroller,webindourinputfieldtothemodelname(<inputng-model="name">)andsethandlersfortheformsubmitandEmptyListbuttonclickevents(<formng-submit="submit()">and<buttonng-click="empty()">).Lastly,wemakeatemplateboundoutofthetabletothe$scope.personscollection.Sowheneverthecollectionchanges,thetableisupdated:
<tableclass="tabletable-condensed">
<trng-repeat="personinpersons">
<td>{{person.value}}</td>
</tr>
</table>
NowitistimetoaddsomeJavaScripttoourHTML:
<script>
varapp=angular.module("myApp",["ngStorage"]);
app.controller("RosterController",function($scope,$localStorage){
varsync=function(){
$scope.persons=JSON.parse($localStorage.persons||"[]");
};
sync();
$scope.name="";
$scope.submit=function(){
sync();
$scope.persons.push({value:$scope.name});
$localStorage.persons=JSON.stringify($scope.persons);
};
$scope.empty=function(){
$localStorage.persons="[]";
sync();
};
});
</script>
Asweintendtostorethedatasubmittedintheform,wecanuselocalStoragethatwediscussedinChapter4,HTML5APIs.InordertogetlocalStorageintheAngularJSway,weusedthengStoragemodule(https://github.com/gsklee/ngStorage).So,wespecifythepluginduringmoduleinitialization,andthismakesthepluginavailableinthecontrollerasaparameter($localStorage).Inthecontrollerbody,wehaveafunctionsyncthatsets
$scope.personswiththepersonarrayfromlocalStorage.Wecallthesyncfunctionintheformsubmithandler($scope.submit)andintheEmptyListbuttonon-clickhandler($scope.empty).Itcausesthepersontabletoupdateeverytime.Duringthehandlingofthesubmitevent,weappendthevalueofthe$scope.personsinputandsave$scope.personsinlocalStorage.
Inordertoenablethisfunctionality,wehavetoloadtheAngularJSandngStorageplugins:
<script
src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js">
</script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/ngStorage/0.3.6/ngStorage.min.j
s"></script>
NowwelaunchtheNW.jsexecutableandgettheapplicationworking.FollowingscreenshotshowsRoasterexampleappinNW.jswithoutstyles:
It’sallnice,butaswerunNW.jsframeless,wehavenowaytoevenclosetheapplication.Besides,wecannotdragtheapplicationwindowwithinthedesktop.Thisiseasytofix.WecanaddanHTMLfragmenttotheHTMLbodywithtwobuttonstocloseandminimizetheapplication:
<headerng-controller="ToolbarController">
<ahref="#"ng-click="minimize()">Minimize</a>
<ahref="#"ng-click="close()">Close</a>
</header>
NowwesubscribelistenerstothesebuttonsthatcallthecloseandminimizeNW.jsWindowAPI(https://github.com/nwjs/nw.js/wiki/Window)methodsrespectively:
varwin=require("nw.gui").Window.get();
app.controller("ToolbarController",function($scope){
$scope.close=function(){
win.close();
};
$scope.minimize=function(){
win.minimize();
};
});
Inordertomakeourwindowdrag-able(https://github.com/nwjs/nw.js/wiki/Frameless-window),wecanusethe-webkit-app-regionCSSpseudo-class.Wesetthiswiththedragvalueonthehandlecontainer(header)andwiththeno-dragvalueonanyclickableelementswithinit:
header{
-webkit-app-region:drag;
}
headera{
-webkit-app-region:no-drag;
}
Inaddition,weprettifythelookandfeelofthepage.NotethatwithNW.js,wecanhaveatransparentbackground.Sowesettheborder-radiusonthehtmlelementandthewindowgetsrounded:
html{
height:100%;
border-radius:20px;
background-color:rgba(0,0,0,0);
}
body{
min-height:100%;
background:linear-gradient(tobottom,#deefff0%,#98bede100%);
overflow:auto;
}
header{
text-align:right;
width:auto;
padding:12px;
background:rgba(255,255,255,0.5);
border-radius:20px20px00;
-webkit-app-region:drag;
}
headera{
margin:12px;
-webkit-app-region:no-drag;
}
NowwecanlaunchourNW.jsexecutableagain.RoasterexampleappinNW.jswithstylesisshowninthefollowingscreenshot:
NotethatonMacOSX/Linux,wehavetolaunchwithspecialarguments(https://github.com/nwjs/nw.js/wiki/Transparency)togetthetransparencyeffect.Forexample,wehavetodothefollowingonMacOSX:
open-n./nwjs.app--args--enable-transparent-visuals–disable-gpu
DebuggingStillsomethingismissing.Ifanythinggoeswrong,howcanwedebugandtracetheerrors?Thereareafewoptionsavailable:
LaunchtheNW.jsexecutablewiththe--enable-loggingargumentandgetthelogsinstdout.LaunchtheNW.jsexecutablewith--remote-debugging-portandaccesstheDevToolsapplicationinaremotelyrunningChrome.Forinstance,westartuptheprojectasnw--remote-debugging-port=9222andlookforthehttp://localhost:9222pageinChrome.Enabletoolbarandframeforthewindowinpackage.json.
Thefirstoptionisn’tquitehandyindebugging.ThesecondprovidesyouwithalimitedversionofDevTools,andthelastoptionbringstheframeandcanmaketheapplicationlookterrible.Fortunately,wecancallDevToolsprogrammaticallyfromtheapp.So,ontheDEVELOPMENT/TESTenvironment,youcanaddthiscodethatshowsupDevToolsbypressingCtrl+Shift+I:
console.info("Herewego!");
document.addEventListener("keydown",function(e){
varkey=parseInt(e.key||e.keyCode,10);
//Ctrl-Shift-i
if(e.ctrlKey&&e.shiftKey&&key===73){
e.preventDefault();
win.showDevTools();
}
},false);
ProgrammaticallycalledDevToolsinNW.JSareshowninthefollowingscreenshot:
PackagingTohavearealdesktopapplicationexperience,wecanbundletheprojectsassetsandNW.jsfilesintoasingleexecutable.FirstlyusingZIP,wecompressourprojectdirectory(wwwroot)andtheaccompanyingfiles(thenode_modulesdirectoryandtheNAPIplugins)intoapp.nw.Then,wecombinethearchivewiththeNW.jsexecutable.InWindows,thiscanbedoneasfollows:
runcopy/bnw.exe+app.nwapp.exe
IfthedistributionofNW.jsthatistargetedforyourplatformcontainsanycomponents(forexample,theWindowsdistributionincludesDLLs),injectthemintothenewlycreatedapplicationexecutableusingEnigmaVirtualBox(http://enigmaprotector.com).Voilà,nowwecandistributetheprojectinasinglefile.
UsingPhoneGaptomakeamobilenativeappWell,nowwecanmakedesktopapplicationswithJavaScriptbutwhataboutnativemobileapplications?Thereareanumberofweb-basedframeworksavailableformobiledevelopment(https://en.wikipedia.org/wiki/Multiple_phone_web-based_application_framework).OneofthemosttrendingsolutionsiscalledAdobePhoneGap,whichisbuiltontopoftheApacheCordovaproject.Byandlarge,thePhoneGapapplicationconsistsofaweb-stack(HTML5,CSS,andJavaScript).Despitethefactthatnowadays,HTML5providesaccesstosomeofthenativefeatures(accelerometer,camera,contacts,vibration,GPS,andothers),thesupportacrossdifferentdevicesisinconsistentandquirky,andperformanceisrelativelypoor.SoPhoneGaprunsHTML5insideanativeWebViewonadeviceandprovidesaccesstodeviceresourcesandAPIs(https://en.wikipedia.org/wiki/Foreign_function_interface).Asaresult,wecanwriteamobileapplicationbasedonHTML5andbuilditwithPhoneGapforthedevicesandOSthatwesupport(iPhone,Android,Blackberry,Windows,Ubuntu,FirefoxOS,andothers).AgoodpointhereisthatwecanreusethecomponentscreatedfortheWebwhiledevelopingformobile.Infact,wecanbundletheRosterapplicationthatwemadeforNW.jsasamobileapp.Solet’sdothis.
SettinguptheprojectFirstofallweneedaframework.TheeasiestwaytoinstallitisbyusingtheNPMtool:
npminstall-gcordova
The-goptionmeansthatweinstallthisgloballyonthemachineanddon’tneedtodoitwhensettingupanynewproject.
Nowwecancreateanewprojectwiththefollowingcommand:
cordovacreaterosterorg.tempuri.rosterRoster
Intherostersubdirectory,thetoolcreatesaboilerplatefilestructurefortheprojectnamedRosterthatisregisteredwithintheorg.tempuri.rosternamespace.
Now,weneedtoinformPhoneGapabouttheplatformsthatwewanttosupport.So,wenavigatetotherostersubdirectoryandtypethefollowing:
cordovaplatformaddios
cordovaplatformaddandroid
BuildingtheprojectInthewwwsubdirectory,wecanfindaplaceholderHTML5application.WecanreplacethiswiththerosterapplicationwrittenforNW.js(withoutanenvironment-specificheadercontaineranditslistenerscode,ofcourse).Inordertocheckwhethertheprojectwasproperlyinitialized,werunthefollowing:
cordovabuildios
cordovaemulateios
Alternatively,wecanusethis:
cordovabuildandroid
cordovaemulateandroid
Thisbuildstheprojectanddisplaysitwithaplatform-specificemulator.OnaMac,thisishowitlooks.RosterexampleappbyPhoneGapisshowninthefollowingscreenshot:
AddingpluginsAsitwasmentionedalready,withPhoneGap,wecanaccessnativedevicefeatures(http://phonegap.com/about/feature).Moreover,wecanalsoinstallandusenativepluginsavailableintheCordovarepository(http://cordova.apache.org/plugins/).Let’stakeoneofthese—cordova-plugin-vibration.Wecanaddittotheprojectaseasyasthis:
cordovapluginaddcordova-plugin-vibration
Aswehavetheplugin,wecanuseitsAPIinourJavaScriptcode:
//Vibratefor3seconds
navigator.vibrate(3000);
DebuggingAsfordebuggingamobileapplication,thereareanumberofoptions(https://github.com/phonegap/phonegap/wiki/Debugging-in-PhoneGap).Themainideaistoreachtheapplicationwithadesktopinspectortool.InthecaseofiOS,wegowiththeSafariWebInspectordesktop.JustfindtheiPhoneSimulatoroptionintheDevelopmenuandpressWebViewcorrespondingtoyourapplicationHTML.Similarly,wecanaccessAndroidWebViewinChromeDevTools(https://developer.chrome.com/devtools/docs/remote-debugging#debugging-webviews).
SummaryThewidelyspreadNode.jsrun-timeextendsJavaScriptwithalow-levelAPI,whichunlocksforusonthemethodsofcreatingcommand-linetools,web-servers,andspecializedservers(forexampleUDP-TCP/WebSocket/SSEservers).ToseehowfarwecangobeyondtheWeb,justconsiderastandaloneOSNodeOSbuiltwithNode.js.WithHTML5andJavaScriptwecanwriteadesktopsoftwareandeasilydistributeitacrossdifferentplatforms.Similarly,wecancomposeamobileapplicationoutofHTML5/JavaScriptandnativeAPIs.UsingtoolssuchasPhoneGap,wecanbuildtheapplicationfordiversemobileplatforms.
Inthischapter,welearnedhowtoaccessDevToolstodebugNW.jsandPhoneGapapplications.Inthenextchapter,wewilltalkabouthowtouseDevToolsefficiently.
Chapter8.DebuggingandProfilingDebuggingisatrickypartofprogramming.Bugsduringdevelopmentareunavoidable.Whateverourexperience,wehavetospendquiteatimeonhuntingthem.Ithappens.Bythelooksofthecodeyoumaynotfindthebug,thereprobablymustbenoproblemwiththeapplication,yetadeveloperfightsforhoursuntiltheyrunintoasillyreasonsuchasamisprintedpropertyname.Muchofthistimecouldbesavedbymakingabetteruseofbrowserdevelopmenttools.Sowewillconsiderinthischapterthefollowingtopics:
HowtodiscoverbugsGettingthebestfromaconsoleAPIHowtotuneperformance
HuntingbugsDebuggingisaboutfindingandresolvingdefectsthatpreventtheintendedapplicationbehavior.Here,whatiscrucialistofindthecodecausingtheproblem.Whatdoweusuallydowhenweencounterabug?Let’ssay,wehaveaformthatisassumedtorunavalidationonasubmitevent,butitdoesn’t.Firstofall,wehaveanumberofassumptionstobemet.Forexample,ifthereferencetotheformelementisvalid,iftheeventandmethodnamewerespelledcorrectlyduringregisteringalistener,iftheobjectcontextisnotlostinthebodyofthelistener,andsoon.
Somebugscanbediscoveredautomaticallysuchasbyvalidatinginputandoutputontheentryandexitpointsofmethods(seeDesignbycontractat:https://en.wikipedia.org/wiki/Design_by_contract).However,wehavetospototherbugsmanually,andherewecanusetwooptions.Startingfromthepointwherethecodeissurelycorrectstepbysteptotheproblempoint(bottom-updebugging),oronthecontrary,steppingbackfromthebreakpointtofindthesourceofthebreak.Here,browserdevelopmenttoolscancomeinhandy.
ThemostadvancedoneisChromeDevTools.WecanopentheSourcespanelinitandsetbreakpointsinthecode.Thebrowserstopsexecutionwhilereachingabreakpointandshowsapanewithanactualvariablescopeandcallstack.Italsoprovidescontrolsthatonecanusetostep-throughthecodebackandforthonelineatatime.Followingscreenshotshowsdebuggingwiththehelpofbreakpoints:
However,thiscanbetrickytonavigatethroughcodebaseinDevTools.Fortunately,youcansetabreakpointoutofthebrowserdirectlyintheIDE.Youjustneedtoputthedebuggerstatementonthelinewhereyouwantthebrowsertobreak.
Sometimes,itishardtofigureoutwhat’sgoingonwiththeDOM.WecanmakeDevToolstodoabreakontheDOMeventssuchasnoderemoval,nodemodification,andsubtree
changes.JustnavigatetotheHTMLelementintheSourcespanel,right-click,andchoosetheBreakon…option.
Besides,intheSourcepanelthereisatabcalledXHRBreakpointswherewecansetalistofURLs.ThebrowserwillthenbreakwhenanyoftheURLsarerequested.
YoucanalsofindaniconinformofstopsigninthetopofSourcepanelsidebar.Ifthisbuttonisclicked,DevToolswillbreakonanycaughtexceptionandbringtoyouthethrowlocationinthesourcecode.FollowingscreenshotshowshowtousePauseonCaughtExceptiontool:
NoteFormoreinformation,seehttps://developer.chrome.com/devtools/docs/javascript-debugging.
GettingthebestfromaconsoleAPIDespiteitbeingnotapartofJavaScript,wealluseconsoleAPIextensivelytofindoutwhatisreallyhappeningduringanapplifecycle.TheAPI,onceintroducedbytheFirebugtool,isnowavailableineverymajorJavaScriptagent.Mostdevelopersjustdosimpleloggingusingmethodssuchaserror,trace,log,andthedecoratorsuchasinfoandwarn.Well,whenwepassanyvaluestoconsole.log,theyarepresentedtotheJavaScriptConsolepanel.Usually,wepassastringdescribingacaseandalistofvariousobjectsthatwewanttoinspect.However,didyouknowthatwecanrefertotheseobjectsdirectlyfromthestringinthemannerofthePHPsprintf?Sothestringgivenasthefirstargumentcanbeatemplatethatcontainsformatspecifiersfortherestofthearguments:
varnode=document.body;
console.log("Element%shas%dchildnodes;JavaScriptobject%O,DOM
element%o",
node.tagName,
node.childNodes.length,
node,
node);
Theavailablespecifiersare%sforstrings,%dfornumbers,%oforDOMelements,and%OforJavaScriptobjects(thesameasconsole.dir).Besides,thereisaparticularspecifierthatallowsustostyletheconsole.logreport.Thiscanbeveryuseful.Inpractice,theapplicationconsolereceivestoomanylogrecords.Itgetshardtomakeoutthedesiredmessagesamongahundredsimilarmessages.Whatwecandoiscategorizethemessagesandstylethemaccordingly:
console.log.user=function(){
varargs=[].slice.call(arguments);
args.splice(0,0,"%cUSER",
"background-color:#7DB4B5;border-radius:3px;color:#fff;font-
weight:bold;");
console.log.apply(console,args);
};
console.log.event=function(){
varargs=[].slice.call(arguments);
args.splice(0,0,"%cEVENT",
"background-color:#f72;border-radius:3px;color:#fff;font-weight:
bold;");
console.log.apply(console,args);
};
console.log("Genericlogrecord");
console.log.user("UserclickbuttonFoo");
console.log.event("Bartriggers`Baz`eventonQux");
Inthisexample,wedefinedtwomethodsextendingconsole.log.OneprefixesconsolemessageswithUSERoncyanandisintendedforuseractionevents.ThesecondprependsreportswithEVENTandismeanttohighlightmediatorevents.Followingscreenshotexplainscolorizedoutputwithconsole.log:
Anotherlesserknowntrickistheuseofconsole.assertforassertionsincodelogic.So,weassumethataconditionistrueanduntilitiseverythingisfineandwegetnomessages.Butassoonasitfails,wegetarecordintheconsole:
console.assert(sessionId>0,"Sessioniscreated");
Followingscreenshotshowshowtouseconsoleassertions:
Sometimesweneedtoknowhowoftenaneventhappens.Herewecanusetheconsole.countmethod:
functionfactory(constr){
console.count("Factoryiscalledfor"+constr);
//returnnewwindow[constr]();
}
factory("Foo");
factory("Bar");
factory("Foo");
Thisdisplaysintheconsolethespecifiedmessageandanauto-updatingcounternexttoit.Followingscreenshotshowshowtouseconsole.count:
NoteYoucanfindoutmoreaboutworkingwiththeconsoleathttps://developer.chrome.com/devtools/docs/console.
TuningperformancePerformancemakesuserexperience.IfittakestoolongtoloadapageoraUItorespond,theuserislikelytoleavetheapplicationandnevercomeback.It’sespeciallytruewithwebapps.InChapter3,DOMScriptingandAJAX,wecompareddifferentapproachestomanipulatetheDOM.Inordertofindouthowfastanapproachis,weuseaperformancebuilt-inobject:
"usestrict";
varcpuExpensiveOperation=function(){
vari=100000;
while(--i){
document.body.appendChild(document.createElement("div"));
}
},
//Starttesttime
s=performance.now();
cpuExpensiveOperation();
console.log("Processtook",performance.now()-s,"ms");
performance.now()returnsahighresolutiontimestampthatrepresentstimeinmillisecondsaccuratetomicroseconds.Thisisdesignedandwidelyusedforbenchmarking.However,atime/timeEndconsoleobjectalsoprovidesmethodstomeasuretime:
console.time("cpuExpensiveOperationtook");
cpuExpensiveOperation();
console.timeEnd("cpuExpensiveOperationtook");
Followingscreenshotshowsmeasuringtimewithconsole:
Ifweneedtoknowwhatexactlyisgoingonduringanoperationexecution,wecanrequestaprofileforthatperiod:
console.profile("cpuExpensiveOperation");
cpuExpensiveOperation();
console.profileEnd("cpuExpensiveOperation");
FollowingscreenshotshowshowtouseconsoleAPIforprofiling:
Moreover,wecanmarktheexacttimeoftheeventintheTimelinepanelofDevTools:
cpuExpensiveOperation();
console.timeStamp("cpuExpensiveOperationfinished");
FollowingscreenshotshowshowtomarkeventsonTimelineduringarecordingsession:
Whentuningperformance,wehavetopayparticularattentiontotheresponsetime.Thereareanumberoftechniquesthatcanbeusedtoimproveuserexperienceduringbootstrap(non-blockingJavaScriptandCSSloading,criticalCSS,movingstaticfilesCDN,andothers).Well,let’ssayyoudecidetoloadCSSasynchronously(https://www.npmjs.com/package/asynccss)andcacheintolocalStorage.Buthowwouldyoutestwhetheryougainedanythingfromthis?Fortunately,DevToolshasafilmstripfeature.WejustneedtoopentheNetworkpanel,enableScreenshotcapturingandreloadthepage.
DevToolsshowsustheprogressofthepageloadframeafterframeastheuserseesthepageduringtheloadprocess.Besides,wecanmanuallysetaconnectionspeed(throttling)foratestandfindouthowitaffectsthefilmstrip.Followingscreenshotshowshowtogettingfilmstripofpageloading:
SummaryDebuggingisanintegralpartofwebdevelopment.Itcanalsobeaprettysluggishandtedioustask.Withbrowserdevelopmenttools,wecanreducethetimespentonhuntingbugs.Wecansetbreakpointsinacodeandmovestepbysteptothesourceoftheprobleminthesamewaythattheprogramdoes.WhenusingChromeDevTools,wecanwatchforDOMmodificationeventsandforspecificURLrequests.Whentuningperformance,wecanmeasuretimewithtime/timeEndandrequestaprocessprofilewithprofile/profileEnd.Usingfeaturessuchasfilmstripandthrottling,wecanlookatthepageloadondifferentconnections.
WestartedthisbookbyreviewingJavaScriptcorefeatures.We’velearnedhowtomakeacodemoreexpressivebymeansofsyntacticsugar,practicedobjectiterationandcollectionnormalization,comparedvariousapproachestodeclareanobjectincludingES6classes,andfoundouthowtousethemagicmethodsofJavaScript.Then,wedivedintomodularprogramming.WetalkedaboutmodulepatternandmodulesingeneralandreviewedthreemainapproachestomodularizationinJavaScriptAMD,CommonJS,andES6modules.Thenexttopicwasaboutkeepinghigh-performanceDOMmanipulations.WealsoexaminedFetchAPI.WealsoconsideredsomeofmostexcitingHTML5APIssuchStorage,IndexedDB,workers,SSE,andWebSocket,andthetechnologiesunderthehoodofWebComponent.WeconsideredtechniquestoleverageaJavaScripteventloopandtobuildnonblockingapplications.WepracticedwithdesignpatternsinJavaScriptandcoveredconcernseparations.Wewroteasimpleapplicationinthreeframeworks,Backbone,Angular,andReact.WetriedoutNode.jsbycreatingacommand-lineutilityandexposingawebserver.WealsocreatedademodesktopapplicationwithNW.jsanditsmobileversionwithPhoneGap.Atlast,wetalkedaboutbughunting.
IndexA
accesscontrolling,toarbitraryproperties/Controllingaccesstoarbitraryproperties
accessors,inES6classesabout/AccessorsinES6classes
advantages,modulecleanerglobalscope/Cleanerglobalscopecode,packagingintofiles/Packagingcodeintofilesreuse/Reuse
AMDabout/Modulestandards
AndroidWebView,inChromeDevToolsreferencelink/Debugging
AngularURL/Angularabout/Angular
arbitrarypropertiesaccess,controllingto/Controllingaccesstoarbitraryproperties
array-likeobjectenumerating/Enumeratinganarray-likeobject
arrayjoinversusconcatenation/Concatenationversusarrayjoin
arraymethods,inES5about/ArraymethodsinES5
arraymethods,inES6about/ArraymethodsinES6
arraysmanipulating,inES5/ManipulatingarraysintheES5way
arrowfunctions/ArrowfunctionsAsync.js
URL/ParalleltasksandtaskserieswiththeAsync.jslibraryparalleltasks/ParalleltasksandtaskserieswiththeAsync.jslibrarytaskserious/ParalleltasksandtaskserieswiththeAsync.jslibrary
Async/AwaitAPIreferencelink/HandlingasynchronousfunctionsintheES7way
asynccssreferencelink/Tuningperformance
asynchronousfunctionshandling,inES7/HandlingasynchronousfunctionsintheES7way
asynchronousmodules,usinginbrowsersabout/Howtouseasynchronousmodulesinthebrowser
BBabel.js
URL/DesignpatternsinJavaScriptBackbone
about/BackboneURL/Backbone
BootstrapURL/AddingtheHTML5application
BrowserifyURL/TranspilingCommonJSforin-browseruse
bugsdiscovering/Huntingbugs
built-inmodulesystem,JavaScriptabout/JavaScript’sbuilt-inmodulesystemnamedexports/Namedexportsdefaultexport/DefaultexportmoduleloaderAPI/ThemoduleloaderAPIconclusion/Conclusion
BusinessIntelligenceServer(BIServer)/BoostingperformancewithJavaScriptworkers
CCallbackHell
about/Error-firstCallbackURL/Error-firstCallback
callbackswriting,don’timpactlatency-criticalevents/Writingcallbacksthatdon’timpactlatency-criticalevents
classes,inES6about/ClassesinES6
CMDERURL/Howto–usesynchronousmodulesontheserver
code,enhancingabout/Makeyourcodereadableandexpressivefunctionargumentdefaultvalue/Functionargumentdefaultvalueconditionalinvocation/Conditionalinvocationarrowfunctions/Arrowfunctionsmethoddefinitions/Methoddefinitionsrestoperator/Therestoperatorspreadoperator/Thespreadoperator
codingofcommand-lineprogramlevelingup,inJavaScript/Levellingupthecodingofacommand-lineprograminJavaScript
collections,ofES6/ThecollectionsofES6COMET
about/Learningtouseserver-to-browsercommunicationchannelsCommonGatewayInterface(CGI)/WebSocketsCommonJS
traspiling,forin-browseruse/TranspilingCommonJSforin-browseruseCommonJS1.1
about/ModulestandardsCommonJSCompiler
URL/Multi-linestringsviatranspilers,TranspilingCommonJSforin-browseruse
communication,withserverabout/Communicatingwiththeserver
communicationAPIabout/CommunicatingwiththeserverXHR/XHRFetchAPI/FetchAPI
concatenationversusarrayjoin/Concatenationversusarrayjoin
concernseparation,inJavaScriptabout/UnderstandingconcernseparationinJavaScript
ModelViewViewModel(MVVM)/MVVMconditionalinvocation/Conditionalinvocationconsole
referencelink/GettingthebestfromaconsoleAPIconsoleAPI
using/GettingthebestfromaconsoleAPIContinuation-passingstyle(CPS)
about/Continuation-passingstyleContra
URL/ParalleltasksandtaskserieswiththeAsync.jslibraryCross-SiteScriptingattacks
about/WebStorageAPICustomElements/Creatingthefirstwebcomponent
Ddata
storing,inweb-browser/Storingdatainweb-browserdatepickercomponent
referencelink/Creatingthefirstwebcomponentdebouncing
about/Debouncingdebugging,JavaScript
referencelink/Huntingbugsdefaultexport
about/DefaultexportDesignbycontract
referencelink/Huntingbugsdesignpatterns,JavaScript
about/DesignpatternsinJavaScriptdesktopHTML5application
writing/WritingadesktopHTML5applicationproject,settingup/Settinguptheprojectadding/AddingtheHTML5applicationdebugging/Debuggingpackaging/Packaging
DexieURL/IndexedDB
DOMtraversing/TraversingtheDOMmodifying/ChangingtheDOMstyling/StylingtheDOM
DOMeventshandling/HandlingDOMevents
DoNotRepeatYourself(DRY)principle/Classicalapproach
EEcmaScriptforXML(E4X)/MasteringmultilinestringsinJavaScriptEnigmaVirtualBox
URL/PackagingError-firstCallback
about/Error-firstCallbackES5
arrays,manipulatingin/ManipulatingarraysintheES5wayarraymethods/ArraymethodsinES5
ES6arraymethods/ArraymethodsinES6collections/ThecollectionsofES6classes/ClassesinES6
ES6classesaccessors/AccessorsinES6classes
ES6modulesbundling,forsynchronousloading/BundlingES6modulesforsynchronousloading
ES7asynchronousfunctions,handlingin/HandlingasynchronousfunctionsintheES7way
esprimaparserreferencelink/Levellingupthecodingofacommand-lineprograminJavaScript
Event.currentTargetreferencelink/HandlingDOMevents
eventhandlingoptimizationabout/Eventhandlingoptimizationdebouncing/Debouncingthrottling/Throttling
EventQueuesabout/NonblockingJavaScript
ExoskeletonURL/Backbone
Express.jsURL/BuildingawebserverwithJavaScript
ExtendClassabout/ApproachwithExtendClassURL/ApproachwithExtendClass
FFetchAPI
about/FetchAPIURL/FetchAPI
FileSystemAPIabout/FileSystemAPI
foreignfunctioninterfacereferencelink/UsingPhoneGaptomakeamobilenativeapp
fsmodulereferencelink/Levellingupthecodingofacommand-lineprograminJavaScript
functionargumentdefaultvalue/Functionargumentdefaultvalue
Hhigh-speedDOMoperations
about/High-speedDOMoperationsDOM,traversing/TraversingtheDOMDOM,modifying/ChangingtheDOMDOM,styling/StylingtheDOMattributes,using/Makinguseofattributesandpropertiesproperties,using/MakinguseofattributesandpropertiesDOMevents,handling/HandlingDOMevents
HTML5videoelementreferencelink/Creatingthefirstwebcomponent
HTMLImports/CreatingthefirstwebcomponentHTTPcookies
about/WebStorageAPI
IImmediatelyInvokedFunctionExpression(IIFE)/Modulepatternsin-browseruse
CommonJS,traspilingfor/TranspilingCommonJSforin-browseruseIndexedDB
about/IndexedDBinheritance
withprototypechain/Inheritancewiththeprototypechain
JJavaScript
multilinestrings,masteringin/MasteringmultilinestringsinJavaScriptmagicmethods/Howto–magicmethodsinJavaScriptbuilt-inmodulesystem/JavaScript’sbuilt-inmodulesystemdesignpatterns/DesignpatternsinJavaScriptconcernseparation/UnderstandingconcernseparationinJavaScriptcodingofcommand-lineprogram,levelingupin/Levellingupthecodingofacommand-lineprograminJavaScriptweb-server,buildingwith/BuildingawebserverwithJavaScript
JavaScriptMV*frameworksabout/UsingJavaScriptMV*frameworksBackbone/BackboneAngular/AngularReact/React
JavaScriptworkersperformance,boostingwith/BoostingperformancewithJavaScriptworkersreferencelink/BoostingperformancewithJavaScriptworkers
JettyURL/WebSockets
JSXURL/Multi-linestringsviatranspilers
LLearningJavaScriptDesignPatterns
URL/DesignpatternsinJavaScriptLIFO(lastin,firstout)/NonblockingJavaScriptlocalStorageinterface
about/WebStorageAPI
MMacOSXTerminal/Levellingupthecodingofacommand-lineprograminJavaScriptmagicmethods,JavaScript/Howto–magicmethodsinJavaScriptmessmodularJavaScript
gettingoutof/HowtogetoutofamessusingmodularJavaScriptmethoddefinitions/MethoddefinitionsMocha.js
URL/TranspilingCommonJSforin-browseruseModelViewController(MVC)/UnderstandingconcernseparationinJavaScriptModelViewViewModel(MVVM)
about/MVVMreferencelink/MVVM
moduleabout/Modulesadvantages/Modules
moduleloaderAPIabout/ThemoduleloaderAPI
modulepatternsabout/Modulepatterns
modulestandardsabout/Modulestandards
multi-linestringsviatraspilers/Multi-linestringsviatranspilers
multilinestringsmastering,inJavaScript/MasteringmultilinestringsinJavaScript
Nnamedexports
about/NamedexportsngStoragemodule
URL/AddingtheHTML5applicationNode
referencelink/High-speedDOMoperationsNode.js
URL/Howto–usesynchronousmodulesontheserver,Levellingupthecodingofacommand-lineprograminJavaScript
nodejs-websocketreferencelink/WebSockets
nonblockingJavaScriptabout/NonblockingJavaScript
NPMrepositoryreferencelink/Levellingupthecodingofacommand-lineprograminJavaScript
NW.jsURL/Settinguptheproject
NW.jsWindowAPIURL/AddingtheHTML5application
Oobject
traversing/Traversinganobjectinanelegant,reliable,safe,andfastwayObject.assign
used,forinheritingfromprototype/InheritingfromprototypewithObject.assign
Object.createused,forinheritingfromprototype/InheritingfromprototypewithObject.create
objects,declaringabout/Themosteffectivewayofdeclaringobjectsclassicalapproach/Classicalapproachprivatestate/Approachwiththeprivatestate
Pperformance
boosting,withJavaScriptworkers/BoostingperformancewithJavaScriptworkersabout/Tuningperformancetuning/Tuningperformance
PhoneGap,usedformakingmobilenativeappabout/UsingPhoneGaptomakeamobilenativeappproject,settingup/Settinguptheprojectproject,building/Buildingtheprojectplugins,adding/Addingpluginsdebugging/Debugging
PostMessageAPIURL/BoostingperformancewithJavaScriptworkers
Promiseabout/FetchAPI
Promise.allstaticmethodreferencelink/Continuation-passingstyle
RRatchet
URL/WebSocketsReact
about/ReactURL/React
requestIdleCallbacknativemethodreferencelink/Writingcallbacksthatdon’timpactlatency-criticalevents
RequireJSURL/Prosandcons
Responseobjectreferencelink/FetchAPI
restoperatorabout/Therestoperator
Sselectors
referencelink/TraversingtheDOMServer-SentEvents(SSE)
about/Server-SentEventsserver-to-browsercommunicationchannels
about/Learningtouseserver-to-browsercommunicationchannelsServer-SentEvents(SSE)/Server-SentEventsWebSockets/WebSockets
sessionStorageinterfaceabout/WebStorageAPI
ShadowDOM/CreatingthefirstwebcomponentSocket.IO
URL/WebSocketsspreadoperator
about/Thespreadoperatorsynchronousloading
ES6modules,bundlingfor/BundlingES6modulesforsynchronousloadingsynchronousmodules,onserver
about/Howto–usesynchronousmodulesontheserverpros/Prosandconscons/Prosandcons
Ttemplateliteral
about/Templateliteralthrottling
about/ThrottlingTODOapplication,MVPimplementation
URL/UnderstandingconcernseparationinJavaScriptTodoMVC
URL/UsingJavaScriptMV*frameworksTornado
URL/WebSockets
Wweb-basedframeworks,formobiledevelopment
referencelink/UsingPhoneGaptomakeamobilenativeappweb-browser
data,storingin/Storingdatainweb-browserweb-server
building,withJavaScript/BuildingawebserverwithJavaScriptwebcomponent
creating/CreatingthefirstwebcomponentWebsocketd
URL/WebSocketsWebSockets
about/WebSocketsWebSQLDatabase
about/IndexedDBWebStorage
about/WebStorageAPIWebStorageAPI
about/WebStorageAPIWindowsCMD
about/Levellingupthecodingofacommand-lineprograminJavaScript