javascript unlocked - pepa.holla.cz - co se jinam...

286

Upload: lykhue

Post on 30-Jul-2018

225 views

Category:

Documents


0 download

TRANSCRIPT

JavaScriptUnlocked

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

JavaScriptUnlocked

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.

www.PacktPub.com

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

themostisthatthissyntaxallowsustomakethecodecleanerandmoremaintainable.

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:

main.es6

import*asfoofrom"./foo";

console.log(foo.bar);//bar

console.log(foo.baz);//baz

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(

"[email protected]");

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

pros/Prosandconscons/Prosandcons

augmentationabout/Augmentation

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

GGrunt

URL/TranspilingCommonJSforin-browseruse

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

Kkey-valueobject

iterating/Iteratingthekey-valueobjectsafelyandfast

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

UUMD

about/UMDURL/UMD

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

XXHR

about/XHRreferences/XHR

ZZeroFrameworkManifesto

URL/UsingJavaScriptMV*frameworks