object-oriented javascript - third edition · 2017. 4. 20. · es6 object literals object...
TRANSCRIPT
Object-Oriented JavaScript - Third Edition
Table of Contents
Object-OrientedJavaScript-ThirdEditionCreditsAbouttheAuthorsAbouttheReviewerwww.PacktPub.comWhysubscribe?
CustomerFeedbackPrefaceWhatthisbookcoversWhatyouneedforthisbookWhothisbookisforConventionsReaderfeedbackCustomersupportErrataPiracyQuestions
1.Object-OrientedJavaScriptAbitofhistoryBrowserwarsand renaissanceThe presentThefuture
ECMAScript5StrictmodeinES6
ECMAScript6BrowsersupportforES6Babel
Object-orientedprogrammingObjectsClassesEncapsulationAggregationInheritancePolymorphism
OOPsummarySettingupyourtrainingenvironmentWebKit'swebinspectorJavaScriptCoreonaMacMoreconsoles
Summary2.PrimitiveDataTypes, Arrays,Loops,andConditionsVariablesVariablesarecasesensitive
OperatorsPrimitivedatatypesFindingoutthevaluetype-thetypeofoperatorNumbersOctalandhexadecimalnumbersBinaryLiteralsExponentliteralsInfinityNaNNumber.isNaNNumber.isInteger
StringsStringconversionsSpecialstringsStringtemplateliterals
BooleansLogicaloperatorsOperatorprecedenceLazyevaluationComparison
UndefinedandnullSymbols
PrimitivedatatypesrecapArraysAdding/updatingarrayelementsDeletingelementsArraysofarrays
ConditionsandloopsCode blocksTheifconditionTheelseclauseCheckingifavariableexistsAlternativeifsyntaxSwitchDon'tforgettobreak
LoopsWhileloopsDo-whileloops
ForloopsFor...inloops
CommentsExercisesSummary
3.FunctionsWhatisafunction?Calling a functionParameters
DefaultparametersRestparametersSpreadoperatorsPredefinedfunctionsparseInt()parseFloat()isNaN()isFinite()Encode/decodeURIseval()Abonus-thealert()function
ScopeofvariablesVariablehoisting
BlockscopeFunctionsaredataAnonymousfunctionsCallbackfunctionsCallbackexamples
ImmediatefunctionsInner(private)functionsFunctionsthatreturnfunctionsFunction,rewritethyself!
ClosuresScope chainBreakingthechain withaclosureClosure#1Closure#2Adefinitionandclosure#3
ClosuresinaloopGetterandsetterIterator
IIFEversusblocksArrowfunctions
ExercisesSummary
4.ObjectsFromarraystoobjectsElements,properties,methods,andmembersHashesandassociativearraysAccessinganobject'spropertiesCalling an object's methodsAlteringproperties/methodsUsingthethisvalueConstructorfunctionsTheglobalobjectTheconstructorpropertyTheinstanceofoperatorFunctionsthatreturnobjectsPassingobjectsComparingobjectsObjectsintheWebKitconsoleLoggingusingtheconsole.logmethod
ES6objectliteralsObjectpropertiesandattributesES6objectmethodsCopypropertiesusingObject.assignComparevalueswithObject.is
DestructuringBuilt-inobjectsObjectArrayAfewarraymethods
ES6arraymethodsArray.fromCreatingarraysusingArray.ofArray.prototype methodsFunctionPropertiesoffunctionobjectsUsingtheprototypepropertyMethodsoffunctionobjectsCallandapplyTheargumentsobjectrevisited
LexicalthisinarrowfunctionsInferringobjecttypesBoolean
NumberStringAfewmethodsofstringobjects
MathDateMethodstoworkwithdateobjectsCalculatingbirthdays
RegExpPropertiesofRegExpobjectsMethodsofRegExpobjectsStringmethodsthatacceptregularexpressionsasargumentssearch()andmatch()replace()Replacecallbackssplit()Passingastring whenaRegExpisexpectedErrorobjects
ExercisesSummary
5.ES6IteratorsandGeneratorsFor...ofloopIteratorsanditerablesIteratorsIterables
GeneratorsIteratingovergenerators
CollectionsMapIteratingovermapsConvertingmapstoarrays
SetWeakMapandWeakSet
Summary6.PrototypeTheprototypepropertyAddingmethodsandpropertiesusingtheprototype
Usingtheprototype'smethodsandpropertiesOwnpropertiesversusprototypepropertiesOverwritingaprototype'spropertywithanownpropertyEnumeratingproperties
UsingisPrototypeOf()methodThesecret__proto__link
Augmentingbuilt-inobjectsAugmentingbuilt-inobjects-discussionPrototypegotchas
ExercisesSummary
7.InheritancePrototypechainingPrototype chaining exampleMovingsharedpropertiestotheprototype
InheritingtheprototypeonlyAtemporaryconstructor-newF()
Uber-accesstotheparentfromachildobjectIsolatingtheinheritancepartintoafunctionCopyingpropertiesHeads-upwhencopyingbyreferenceObjectsinheritfromobjectsDeepcopyUsingobject()methodUsingamixofprototypalinheritanceandcopyingpropertiesMultipleinheritanceMixins
ParasiticinheritanceBorrowingaconstructorBorrowingaconstructorandcopyingitsprototype
Casestudy-drawingshapesAnalysisImplementationTesting
ExercisesSummary
8.ClassesandModulesDefiningclassesConstructorPrototypemethodsStaticmethodsStaticpropertiesGeneratormethods
SubclassingMixins
ModulesExportlists
Summary
9.PromisesandProxiesAsynchronousprogrammingmodelJavaScriptcallstackMessagequeueEventloopTimersRuntocompletionEventsCallbacks
PromisesCreatingpromisesPromise.all()
MetaprogrammingandproxiesProxyFunctiontraps
Summary10.TheBrowserEnvironmentIncludingJavaScriptinanHTMLpageBOMandDOM-anoverviewBOMThewindowobjectrevisitedUsingwindow.navigatorpropertyYourconsoleisacheatsheetUsingwindow.locationpropertyUsingwindow.historypropertyusingwindow.framespropertyUsingwindow.screenpropertywindow.open()/close()methodwindow.moveTo()andwindow.resizeTo()methodswindow.alert(),window.prompt(),andwindow.confirm()methodsUsingwindow.setTimeout()andwindow.setInterval()methodswindow.documentproperty
DOMCoreDOMandHTMLDOMAccessingDOMnodesThedocumentnodedocumentElementChildnodesAttributesAccessingthecontentinsideatagDOMaccessshortcutsSiblings,body,first,andlastchild
WalktheDOMModifyingDOMnodesModifyingstylesFunwithforms
CreatingnewnodesDOM-onlymethodUsingcloneNode()methodUsing insertBefore() method
RemovingnodesHTML-onlyDOMobjectsPrimitivewaystoaccessthedocumentUsingdocument.write()methodCookies,title,referrer,anddomain
EventsInlineHTMLattributesElementPropertiesDOMeventlistenersCapturingandbubblingStoppropagationPreventdefaultbehaviorCross-browsereventlistenersTypesofevents
XMLHttpRequestSendingtherequestProcessingtheresponseCreatingXMLHttpRequestobjectsinIEpriortoVersion7AisforAsynchronousXisforXMLAnexample
ExercisesSummary
11.CodingandDesignPatternsCoding patternsSeparatingbehaviorContentPresentationBehaviorExampleofseparatingbehavior
AsynchronousJavaScriptloadingNamespacesAnObjectasanamespaceNamespacedconstructors
Anamespace()methodInit-timebranchingLazydefinitionConfigurationobjectPrivatepropertiesandmethodsPrivilegedmethodsPrivatefunctionsaspublicmethodsImmediate functionsModulesChainingJSONHigherorderfunctions
DesignpatternsSingletonpatternSingleton2patternGlobalvariablePropertyoftheconstructorInaprivateproperty
FactorypatternDecoratorpatternDecoratingachristmastree
ObserverpatternSummary
12.TestingandDebuggingUnittestingTestDrivenDevelopmentBehaviorDrivenDevelopmentMocha,ChaiandSinon
JavaScriptdebuggingSyntaxerrorsUsingstrict
RuntimeexceptionsConsole.log and assertsChromeDeveloperTools
Summary13.ReactiveProgrammingandReactReactiveprogrammingWhyshouldyouconsiderreactiveprogramming?
ReactVirtualDOMInstallingandrunningreactComponentsandprops
StateLifecycleevents
SummaryA.ReservedWordsKeywordsES6reservedwordsFuturereservedwords
Previously reserved wordsB.Built-inFunctionsC.Built-inObjectsObjectMembersoftheObjectconstructorTheObject.prototypemembersECMAScript5additionstoobjects
ES6additiontoobjectsPropertyshorthandComputedpropertynamesObject.assign
ArrayTheArray.prototypemembersECMAScript5additionstoArrayES6additiontoarrays
FunctionTheFunction.prototypemembersECMAScript5additionstoaFunctionECMAScript6additionstoaFunction
BooleanNumberMembersoftheNumberconstructorTheNumber.prototypemembers
StringMembersoftheStringconstructorThe String.prototype membersECMAScript5additionstoStringECMAScript6additionstoString
DateMembersoftheDateconstructorTheDate.prototypemembersECMAScript5additionstoDate
MathMembersoftheMathobject
RegExp
TheRegExp.prototypemembersErrorobjectsTheError.prototypemembers
JSONMembersoftheJSONobject
D.RegularExpressionsE.AnswerstoExerciseQuestionsChapter 2, Primitive Data Types, Arrays, Loops, and ConditionsExercises
Chapter3,FunctionsExercises
Chapter4,ObjectsExercises
Chapter5,PrototypeExercises
Chapter6,InheritanceExercises
Chapter7,TheBrowserEnvironmentExercises
Object-Oriented JavaScript - Third Edition
Object-Oriented JavaScript - Third EditionCopyright © 2017 Packt Publishing
Allrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.
Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthors,norPacktPublishing,anditsdealersanddistributorswillbeheldliableforanydamagescausedorallegedtobecauseddirectlyorindirectlybythisbook.
PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproducts mentioned in this book by the appropriate use of capitals. However, Packt Publishingcannotguaranteetheaccuracyofthisinformation.
Firstpublished:July2008
Secondedition:July2013
Thirdedition:January2017
Production reference: 1050117
PublishedbyPacktPublishingLtd.
LiveryPlace
35LiveryStreet
Birmingham
B32PB,UK.
ISBN978-1-78588-056-8
www.packtpub.com
CreditsAuthors
VedAntani
StoyanStefanov
CopyEditor
ZainabBootwala
Reviewer
MohamedSanaulla
ProjectCoordinator
RitikaManoj
CommissioningEditor
WilsonDsouza
Proofreader
SafisEditing
AcquisitionEditor
DenimPinto
Indexer
RekhaNair
ContentDevelopmentEditor
ArunNadar
Graphics
JasonMonteiro
TechnicalEditor
AbhishekSharma
ProductionCoordinator
ArvindkumarGupta
About the AuthorsVedAntani has been building scalable server and mobile platforms using JavaScript, Go, andJavasince2005.HeisanassociatevicepresidentatMyntraandhaspreviouslyworkedatElectronicArtsandOracle.Heisanavidreaderandauthoronseveralsubjects.HehasstudiedcomputerscienceandcurrentlylivesinBangalore,India.Vedispassionateaboutclassicalmusicandlovestospendtimewithhisson.
Writingthisbookrequiredasignificantinvestmentofmytime,andIwouldliketothankmyparentsandfamilyfortheirsupportandencouragementduringthoselongdaysandweekendswhenIwaspracticallyinvisible.
StoyanStefanovisaFacebookengineer,author,andspeaker.Hetalksregularlyaboutwebdevelopmenttopicsatconferences,andhisblog,www.phpied.com.Healsorunsanumberofothersites,includingJSPatterns.com-asitededicatedtoexploringJavaScriptpatterns.PreviouslyatYahoo!,StoyanwasthearchitectofYSlow2.0andcreatoroftheimageoptimizationtool,Smush.it.
A"citizenoftheworld", StoyanwasbornandraisedinBulgaria,butisalsoaCanadiancitizen,currentlyresidinginLos Angeles,California.Inhisofflinemoments,heenjoysplayingtheguitar,takingflyinglessons,andspendingtimeattheSantaMonicabeacheswithhisfamily.
I'dliketodedicatethisbooktomywife,Eva,andmydaughters,ZlatinaandNathalie.Thankyouforyourpatience,support,andencouragement.
About the ReviewerMohamed Sanaulla is a software developer with more than 7 years of experience in developingenterpriseapplicationsandJava-basedback-endsolutionsfore-commerceapplications.
HisinterestsincludeEnterprisesoftwaredevelopment,refactoringandredesigningapplications,designingandimplementingRESTfulwebservices,troubleshootingJavaapplicationsforperformanceissues,andTDD.
HehasstrongexpertiseinJava-basedapplicationdevelopment,ADF(JSF-basedJavaEEwebframework),SQL,PL/SQL,JUnit,designingRESTfulservices,Spring,Struts,Elasticsearch,andMongoDB.HeisalsoaSunCertifiedJavaProgrammerfortheJava6platform.HeisamoderatorforJavaRanch.com.Helikestosharefindingsonhisblog(http://sanaulla.info).
www.PacktPub.comFor support files and downloads related to your book, please visit www.PacktPub.com.
DidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.PacktPub.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusatservice@packtpub.comformoredetails.
Atwww.PacktPub.com,youcanalsoreadacollectionoffreetechnicalarticles,signupforarangeoffreenewslettersandreceiveexclusivediscountsandoffersonPacktbooksandeBooks.
https://www.packtpub.com/mapt
Getthemostin-demandsoftwareskillswithMapt.MaptgivesyoufullaccesstoallPacktbooksandvideocourses,aswellasindustry-leadingtoolstohelpyouplanyourpersonaldevelopmentandadvanceyourcareer.
Why subscribe?FullysearchableacrosseverybookpublishedbyPacktCopyandpaste,print,andbookmarkcontentOndemandandaccessibleviaawebbrowser
Customer FeedbackThank you for purchasing this Packt book. We take our commitment to improving our content andproductstomeetyourneedsseriously-that'swhyyourfeedbackissovaluable.Whateveryourfeelingsaboutyourpurchase,pleaseconsiderleavingareviewonthisbook'sAmazonpage.Notonlywillthishelpus,moreimportantlyitwillalsohelpothersinthecommunitytomakeaninformeddecisionabouttheresourcesthattheyinvestintolearn.Youcanalsoreviewforusonaregularbasisbyjoiningourreviewers'club.Ifyou'reinterestedinjoining,orwouldliketolearnmoreaboutthebenefitsweoffer,pleasecontactus:[email protected].
PrefaceJavaScript has emerged as one of the most robust and versatile programming language around.ModernJavaScriptembracesavastarrayoftime-testedandcuttingedgefeatures.Severalofthesefeaturesareslowlygivingshapetothenextgenerationofwebandserverplatforms.ES6introducesveryimportantlanguageconstructs,suchaspromises,classes,arrowfunctions,andseveral,muchanticipatedfeatures.Thisbooktakesadetailedlookatthelanguageconstructsandtheirpracticaluses.Thisbookdoesn'tassumeanypriorknowledgeofJavaScriptandworksfromthegrounduptogiveyouathoroughunderstandingofthelanguage.Peoplewhoknowthelanguagewillstillfinditusefulandinformative.ForpeoplewhoalreadyknowJavaScriptandarefamiliarwithES5syntax,thisbookwillbeaveryusefulprimerforES6features.
What this book coversChapter 1, Object-Oriented JavaScript, talks briefly about the history, present, and future ofJavaScript,andthenmovesontoexplorethebasicsofobject-orientedprogramming(OOP)ingeneral.Youwillthenlearnhowtosetupyourtrainingenvironment(Firebug)inordertodiveintothelanguageonyourown,usingthebookexamplesasabase.
Chapter2,PrimitiveDataTypes,Arrays,Loops,andConditions,discussesthelanguagebasics-variables,datatypes,primitivedatatypes,arrays,loops,andconditionals.
Chapter3,Functions,coversfunctionsthatJavaScriptuses,andhereyouwilllearntomasterthemall.YouwillalsolearnaboutthescopeofvariablesandJavaScript'sbuilt-infunctions.Aninteresting,butoftenmisunderstood,featureofthelanguage-closures-isdemystifiedattheendofthechapter.
Chapter 4, Objects, talks about objects, how to work with properties and methods, and thevariouswaystocreateyourobjects.Thischapteralsotalksaboutbuilt-in objectssuchasArray,Function,Boolean,Number,andString.
Chapter5,ES6IteratorsandGenerators,introducesthemostanticipatedfeaturesofES6,IteratorsandGenerators.Withthisknowledge,youwillproceedtotakeadetailedlookattheenhancedcollectionsconstructs.
Chapter6,Prototype,isdedicatedtotheall-importantconceptofprototypesinJavaScript.Italsoexplainshowtheprototypechainworks,hasOwnProperty(),andsomegotchasofprototypes.
Chapter7,Inheritance,discusseshowinheritanceworks.Thischapteralsotalksaboutamethodtocreatesubclasseslikeotherclassiclanguages.
Chapter8,ClassesandModules,showsthatES6introducesimportantsyntacticalfeaturesthatmakesiteasiertowriteclassicalobject-oriented programmingconstructs.ES6classsyntaxwrapstheslightlycomplexsyntaxofES5.ES6alsohasfulllanguagesupportformodules.ThischaptergoesintothedetailsoftheclassesandmoduleconstructsintroducedinES6.
Chapter9,PromisesandProxies,explainsthatJavaScripthasalwaysbeenalanguagewithstrongsupportforasynchronousprogramming.UpuntilES5,writingasynchronousprogramsmeantyouneededtorelyoncallbacks-sometimesresultingincallbackhell.ES6promisesareamuch-awaitedfeatureintroducedinthelanguage. PromisesprovideamuchcleanerwaytowriteasynchronousprogramsinES6.Proxiesareusedtodefinecustombehaviortosomeofthefundamentaloperations.ThischapterlooksatpracticalusesofbothpromisesandproxiesinES6.
Chapter 10, The Browser Environment, is dedicated to browsers. This chapter also covers BOM(BrowserObjectModel),DOM(W3C'sDocumentObjectModel),browserevents,andAJAX.
Chapter11,CodingandDesignPatterns,divesintovariousuniqueJavaScriptcodingpatterns,aswellasseverallanguage-independentdesignpatterns,translatedtoJavaScriptfromtheBookofFour,themostinfluentialworkofsoftwaredesignpatterns.ThischapteralsodiscussesJSON.
Chapter12,TestingandDebugging,talksabouthowModernJavaScriptisequippedwithtoolsthatsupportTestDrivenDevelopmentandBehaviorDrivenDevelopment.Jasmineisoneofthemostpopulartoolsavailableatthemoment.ThischapterdiscussesTDDandBDDusingJasmineastheframework.
Chapter13,ReactiveProgrammingandReact,explainsthatwiththeadventofES6,severalradicalideasaretakingshape.Reactiveprogrammingtakesaverydifferentapproachtohowwemanagechangeofstatesusingdataflows.React,however,isaframeworkfocusingontheViewpartofMVC.Thischapterdiscussesthesetwoideas.
AppendixA,ReservedWords,liststhereservedwordsinJavaScript.
Appendix B, Built-in Functions, is a reference of built-in JavaScript functions together withsampleuses.
AppendixC,Built-inObjects,isareferencethatprovidesdetailsandexamplesoftheuseofeverymethodandpropertyofeverybuilt-inobjectinJavaScript.
AppendixD,RegularExpressions,isaregularexpressionspatternreference.
AppendixE,AnswerstoExerciseQuestions,hassolutionsforalltheexercisesmentionedattheendofthechapters.
What you need for this bookYou need a modern browser-Google Chrome or Firefox are recommended, and an optionalNode.jssetup.Mostofthecodeinthisbookcanbeexecutedinhttp://babeljs.io/repl/orhttp://jsbin.com/.ToeditJavaScript,youcanuse anytexteditorofyourchoice.
Who this book is forThis book is for anyone who is starting to learn JavaScript, or who knows JavaScript but isn'tverygoodattheobject-orientedpartofit.ThisbookcanbeausefulprimerforES6ifyouarealreadyfamiliarwiththeES5featuresofthelanguage.
ConventionsIn this book, you will find a number of text styles that distinguish between different kinds ofinformation.Herearesomeexamplesofthesestylesandanexplanationoftheirmeaning.
Codewordsintext,databasetablenames,foldernames,filenames,fileextensions,pathnames,dummyURLs,userinput,andTwitterhandlesareshownasfollows:"TheTriangleconstructortakesthreepointobjectsandassignsthemtothis.points(itsowncollectionofpoints)."
Ablockofcodeissetasfollows:
functionsum(a,b){varc=a+b;returnc;}
Anycommand-lineinput oroutputiswrittenasfollows:
mkdirbabel_testcdbabel_test&&npminitnpminstall--save-devbabel-cli
Newtermsandimportantwordsareshowninbold.Wordsthatyouseeonthescreen,forexample,inmenusordialogboxes,appearinthetextlikethis:"InordertobringuptheconsoleinChromeorSafari,right-clickanywhereonapageandselectInspectElement.TheadditionalwindowthatshowsupistheWebInspectorfeature.SelecttheConsoletab,andyou'rereadytogo".
Note
Warningsorimportantnotesappearinaboxlikethis.
Tip
Tipsandtricksappearlikethis.
Reader feedbackFeedback from our readers is always welcome. Let us know what you think about this book-whatyoulikedordisliked.Readerfeedbackisimportantforusasithelpsusdeveloptitlesthatyouwillreallygetthemostoutof.
Tosendusgeneralfeedback,[email protected],andmentionthebook'stitleinthesubjectofyourmessage.
Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,seeourauthorguideatwww.packtpub.com/authors.
Customer supportNow that you are the proud owner of a Packt book, we have a number of things to help you to getthemostfromyourpurchase.
Errata
Althoughwehavetakeneverycaretoensuretheaccuracyofourcontent,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.
Piracy
PiracyofcopyrightedmaterialontheInternetisanongoingproblemacrossallmedia.AtPackt,wetaketheprotectionofourcopyrightandlicensesveryseriously.IfyoucomeacrossanyillegalcopiesofourworksinanyformontheInternet,pleaseprovideuswiththelocationaddressorwebsitenameimmediatelysothatwecanpursuearemedy.
[email protected] piratedmaterial.
Weappreciateyourhelpinprotectingourauthorsandourabilitytobringyouvaluablecontent.
Questions
Ifyouhaveaproblemwithanyaspectofthisbook,[email protected],andwewilldoourbesttoaddresstheproblem.
Chapter 1. Object-Oriented JavaScriptEver since the early days of the web, there has been a need for more dynamic and responsiveinterfaces.Whileit'sOKtoreadstaticHTMLpagesoftext,andevenbetterwhentheyarebeautifullypresentedwiththehelpofCSS,it'smuchmorefuntoengagewithapplicationsinourbrowsers,suchase-mail,calendars,banking,shopping,drawing,playinggames,andtextediting.AllthatispossiblethankstoJavaScript,theprogramminglanguageoftheweb.JavaScriptstartedwithsimpleone-linersembeddedinHTML,butisnowusedinmuchmoresophisticatedways.Developersleveragethe object-orientednatureofthelanguagetobuildscalablecodearchitecturesmadeupof reusablepieces.
Ifyoulookatthepastandpresentbuzzwordsinwebdevelopment,DHTML,Ajax,Web2.0,HTML5,theyallessentiallymeanHTML,CSS,andJavaScript-HTMLforcontent,CSSforpresentation,andJavaScriptforbehavior.Inotherwords,JavaScriptisthegluethatmakeseverythingworktogethersothatwecanbuildrichwebapplications.
However,that'snotall;JavaScriptcanbeusedformorethanjusttheweb.
JavaScriptprogramsrun insideahostenvironment.Thewebbrowseristhemostcommonenvironment,butit'snottheonlyone.UsingJavaScript,youcancreateallkindsofwidgets,applicationextensions,andotherpiecesofsoftware,asyou'llseeinabit.TakingthetimetolearnJavaScriptisasmartinvestment;youlearnonelanguageandcanthenwriteallkindsofdifferentapplicationsrunningonmultipleplatforms,includingmobileandserver-sideapplications.Thesedays,it'ssafetosaythatJavaScriptiseverywhere.
Thisbookstartsfromzero,anddoesnotassumeanypriorprogrammingknowledgeotherthansomebasicunderstandingofHTML.Althoughthereisonechapterdedicatedtothewebbrowserenvironment,therestofthebookisaboutJavaScriptingeneral,soit'sapplicabletoallenvironments.
Let'sstartwiththefollowing:
AbriefintroductiontothestorybehindJavaScriptThebasicconceptsyou'llencounterindiscussionsonobject-orientedprogramming
A bit of historyInitially, the web was not much more than just a number of scientific publications in the form ofstaticHTMLdocumentsconnectedtogetherwithhyperlinks.Believeitornot,therewasatimewhentherewasnowaytoputanimageinapage.However,thatsoonchanged.Asthewebgrewinpopularityandsize,thewebmasterswhowere creatingHTMLpagesfelttheyneededsomethingmore.Theywantedtocreatericheruserinteractions,mainlydrivenbythedesiretosaveserverroundtripsforsimpletaskssuchasformvalidation.Twooptionscameup-JavaappletsandLiveScript,alanguageconceivedbyBrendanEichatNetscapein1995andlaterincludedintheNetscape2.0browserunderthenameofJavaScript.
Theappletsdidn'tquitecatchon,butJavaScriptdid.TheabilitytouseshortcodesnippetsembeddedinHTMLdocumentsandalterotherwisestaticelementsofawebpagewasembracedbythewebmastercommunity.Soon,thecompetingbrowservendor,Microsoft,shippedInternetExplorer(IE)3.0withJScript,whichwasareverseengineeredversionofJavaScriptplussomeIE-specificfeatures.Eventually,therewasanefforttostandardizethevariousimplementationsofthelanguage,andthisishowECMAScriptwasborn.EuropeanComputerManufacturersAssociation(ECMA)createdthestandardcalledECMA-262,whichdescribesthecorepartsoftheJavaScriptprogramminglanguagewithoutbrowserandwebpage-specificfeatures.
YoucanthinkofJavaScriptasatermthatencompassesthefollowingthreepieces:
ECMAScript:Thecorelanguage-variables,functions,loops,andsoon.Thispartisindependentofthebrowserandthislanguagecanbeusedinmanyotherenvironments.DocumentObjectModel(DOM):ThisprovideswaystoworkwithHTMLandXMLdocuments.Initially,JavaScriptprovidedlimitedaccesstowhat'sscriptableonthepage,mainlyforms,links,andimages.Later,itwasexpandedtomakeallelementsscriptable.ThisledtothecreationoftheDOMstandardbytheWorldWideWebConsortium(W3C)asalanguage-independent(nolongertiedtoJavaScript)waytomanipulatestructureddocuments.Browser Object Model (BOM): This is a set of objects related to the browser environmentandwasneverpartofanystandarduntilHTML5startedstandardizingsomeofthecommonobjectsthatexistacrossbrowsers.
Whilethereisonechapterinthisbookdedicated tothebrowser,theDOM,andtheBOM,mostofthisbookdescribesthecorelanguageandteachesyouskillsyoucanuseinanyenvironmentwhereJavaScriptprogramsrun.
Browserwarsandrenaissance
Forbetterorforworse,JavaScript'sinstantpopularityhappenedduringtheperiodofthebrowserwarsI(approximately1996to2001).ThosewerethetimesduringtheinitialInternetboomwhenthetwomajorbrowservendors,NetscapeandMicrosoft,werecompetingformarketshare.BothwereconstantlyaddingmorebellsandwhistlestotheirbrowsersandtheirversionsofJavaScript,DOM,andBOM,whichnaturallyledtomanyinconsistencies.Whileaddingmorefeatures,thebrowservendorswerefallingbehindonprovidingproperdevelopmentanddebuggingtoolsandadequatedocumentation.Often,developmentwasapain;youwouldwriteascriptwhiletestinginonebrowser,andonceyou'redonewithdevelopment,youtestintheotherbrowser,onlytofindthatyourscriptsimplyfails fornoapparentreason,andthebestyoucangetisacrypticerrormessage,suchasoperationaborted.
Inconsistentimplementations,missingdocumentation,andnoappropriatetoolspaintedJavaScriptin such a light that many programmers simply refused to bother with it.
Ontheotherhand,developerswhodidtrytoexperimentwithJavaScriptgotalittlecarriedaway,addingtoomanyspecialeffectstotheirpageswithoutmuchregardofhowusabletheendresultswere.Developerswereeagertomakeuseofeverynewpossibilitythebrowsersprovided,andendedupenhancingtheirwebpageswiththingssuchasanimationsinthestatusbar,flashingcolors,blinkingtexts,objectsstalkingyourmousecursor,andmanyotherinnovationsthatactuallyhurttheuserexperience.ThesevariouswaystoabuseJavaScriptarenowmostlygone,buttheywereoneofthereasonswhythelanguagehadsomethingofabadreputation.ManyseriousprogrammersdismissedJavaScriptasnothingbutatoyfordesignerstoplayaroundwith,anddismisseditasalanguageunsuitableforseriousapplications.TheJavaScriptbacklashcausedsomewebprojectstocompletelybananyclient-sideprogrammingandtrustonlytheirpredictableandtightlycontrolledserver.Andreally,whywouldyoudoublethetimetodeliverafinishedproductandthenspendadditionaltimedebuggingproblemswiththedifferentbrowsers?
EverythingchangedintheyearsfollowingtheendofthebrowserwarsI.Anumberofeventsreshapedthewebdevelopmentlandscapeinapositiveway.Someofthemaregivenasfollows:
MicrosoftwonthewarwiththeintroductionofIE6,thebestbrowseratthetime,andformanyyearstheystoppeddevelopingInternetExplorer.ThisallowedtimeforotherbrowserstocatchupandevensurpassIE'scapabilities.Themovementforwebstandardswasembracedbydevelopersandbrowservendorsalike.Naturally,developersdidn'tlikehavingtocodeeverythingtwo(ormore)timestoaccountforbrowsers'differences;therefore,theylikedtheideaofhavingagreed-uponstandardsthateveryonewouldfollow.Developersandtechnologiesmaturedandmorepeoplestartedcaringaboutthingssuchasusability,progressiveenhancementtechniques,andaccessibility.ToolssuchasFirebugmadedevelopersmuchmoreproductiveandthedevelopmentlessofapain.
Inthishealthierenvironment,developersstartedfindingoutnewandbetterwaystousetheinstrumentsthatwerealreadyavailable.AfterthepublicreleaseofapplicationssuchasGmailandGoogleMaps,whichwererichonclient-sideprogramming,itbecameclearthatJavaScriptisamature,uniqueincertainways,andpowerfulprototypalobject-orientedlanguage.ThebestexampleofitsrediscoverywasthewideadoptionofthefunctionalityprovidedbytheXMLHttpRequestobject,whichwasonceanIE-onlyinnovation,butwasthenimplementedbymostotherbrowsers.XMLHttpRequestobjectallowsJavaScripttomake HTTPrequestsandgetfreshcontentfromtheserverinordertoupdatesomepartsofapagewithoutafullpagereload.DuetothewideuseoftheXMLHttpRequestobject,anewbreedofdesktop-likewebapplications,dubbedAjaxapplications,wasborn.
Thepresent
AninterestingthingaboutJavaScriptisthatitalwaysrunsinsideahostenvironment.Thewebbrowserisjustoneoftheavailablehosts.JavaScriptcanalsorunontheserver,onthedesktop,andonmobiledevices.Today,youcanuseJavaScripttodoallofthefollowing:
Createrichandpowerfulwebapplications(thekindofapplicationsthatruninsidethewebbrowser).AdditionstoHTML5,suchasapplicationcache,client-sidestorage,anddatabases,makebrowserprogrammingmoreandmorepowerfulforbothonlineandofflineapplications.PowerfuladditionstoChromeWebKitalsoincludesupportforserviceworkersandbrowserpushnotifications.Writeserver-sidecodeusingNode.js,aswellascodethatcanrunusingRhino(aJavaScriptenginewritteninJava).Makemobileapplications;youcancreateappsforiPhone,Android, andotherphonesandtabletsentirelyinJavaScriptusingPhoneGaporTitanium.Additionally,appsforFirefoxOSformobilephonesareentirelyinJavaScript,HTML,andCSS.ReactNativefromFacebookisanexcitingnewwaytodevelopnativeiOS,Android,andWindows(experimental)applicationsusingJavaScript.Createrichmediaapplications,suchasFlashorFlex,usingActionScript,whichisbasedonECMAScript.Writecommand-linetoolsandscriptsthatautomateadministrativetasksonyourdesktopusingWindowsScriptingHost(WSH)orWebKit'sJavaScriptCore,whichisavailableonallMacs.Writeextensionsandpluginsforaplethoraofdesktopapplications,suchasDreamweaver,Photoshop,andmostotherbrowsers.Createcross-operatingsystemdesktopapplicationsusingMozilla'sXULRunnerandElectron.Electronisusedtobuildsomeofthemostpopularappsonthedesktop,suchasSlack,Atom,andVisualStudioCode.Emscripten,ontheotherhand,allowscode writteninC/C++tobecompiledintoanasm.jsformat,whichcanthenberuninsideabrowser.TestingframeworkslikePhantomJSareprogrammedusingJavaScript.This is by no means an exhaustive list. JavaScript started inside web pages, but today it'ssafetosayitispracticallyeverywhere.Inaddition,browservendorsnowusespeedasacompetitiveadvantageandareracingtocreatethefastestJavaScript engines,whichisgreatforbothusersanddevelopers,andopensdoorsforevenmorepowerfulusesofJavaScriptinnewareassuchasimage,audioandvideoprocessing,andgamesdevelopment.
Thefuture
Wecanonlyspeculatewhatthefuturewillbe,butit'squitecertainthatitwillincludeJavaScript.Forquitesometime,JavaScriptmayhavebeenunderestimatedandunderused(ormaybeoverusedinthewrongways),buteveryday,wewitnessnewapplicationsofthelanguageinmuchmoreinterestingandcreativeways.Itallstartedwithsimpleone-liners,oftenembeddedinHTMLtagattributes,suchasonclick.Nowadays,developersshipsophisticated,well-designedandarchitected,andextensibleapplicationsandlibraries,oftensupportingmultipleplatformswithasinglecodebase.JavaScriptisindeedtakenseriously,anddevelopersarestartingtorediscoverandenjoyitsuniquefeaturesmoreandmore.
Oncelistedinthenice-to-havesectionsofjobpostings,today,knowledgeofJavaScriptisoftenadecidingfactorwhenitcomestohiringwebdevelopers.Commonjobinterviewquestionsyoucanheartodayinclude-IsJavaScriptanobject-orientedlanguage?Good.Now,howdoyouimplementinheritanceinJavaScript?Afterreadingthisbook,you'llbepreparedtoaceyourJavaScript job interview and even impress your interviewers with some bits that, maybe, theydidn'tknow.
ECMAScript 5The last most important milestone in ECMAScript revisions was ECMAScript 5 (ES5),officiallyacceptedinDecember2009.ECMAScript5standardisimplementedandsupportedonallmajorbrowsersandserver-sidetechnologies.
ES5wasamajorrevisionbecauseapartfromseveralimportantsyntacticchangesandadditionstothestandardlibraries, ES5alsointroducedseveralnewconstructsinthelanguage.
Forinstance,ES5introducedsomenewobjectsandproperties,andalsotheso-calledstrictmode.Strictmodeisasubsetofthelanguagethatexcludesdeprecatedfeatures.Thestrictmodeisopt-inandnotrequired,meaningthatifyouwantyourcodetoruninthestrictmode,youwilldeclareyourintentionusing(onceperfunction,oronceforthewholeprogram)thefollowingstring:
"usestrict";
ThisisjustaJavaScriptstring,andit'soktohavestringsfloatingaroundunassignedtoanyvariable.Asaresult,olderbrowsersthatdon'tspeakES5willsimplyignoreit,sothisstrictmodeisbackwardscompatibleandwon'tbreakolderbrowsers.
Forbackwardscompatibility,alltheexamplesin thisbookworkinES3,butatthesametime,allthecodeinthebookiswrittensothatitwillrunwithoutwarningsinES5'sstrictmode.Additionally, any ES5-specific parts will be clearly marked. Appendix C, Built-in Objects, liststhenewadditionstoES5indetail.
StrictmodeinES6
WhilestrictmodeisoptionalinES5,allES6modulesandclassesarestrictbydefault.Asyouwillseesoon,mostofthecodewewriteinES6residesinamodule;hence,strictmodeisenforcedbydefault.However,itisimportanttounderstandthatallotherconstructsdonothaveimplicitstrictmodeenforced.Therewereefforts tomakenewerconstructs,suchasarrowandgeneratorfunctions,toalsoenforcestrictmode,butitwaslaterdecidedthatdoingsowouldresultinveryfragmentedlanguagerulesandcode.
ECMAScript 6ECMAScript 6 revision took a long time to finish and was finally accepted on June 17, 2015. ES6featuresareslowlybecomingpartofmajorbrowsersandservertechnologies.ItispossibletousetranspilerstocompileES6toES5andusethecodeonenvironmentsthatdonotyetsupportES6completely(wewilldiscusstranspilersindetaillater).
ES6substantiallyupgradesJavaScriptasalanguageandbringsinveryexcitingsyntacticalchangesandlanguageconstructs.Broadly,therearetwokindsoffundamentalchangesinthisrevisionofECMAScript,whichareasfollows:
Improvedsyntaxforexistingfeaturesandeditionstothestandardlibrary;forexample,classesandpromisesNewlanguagefeatures;forexample,generators
ES6allowsyoutothinkdifferentlyaboutyourcode.Newsyntaxchangescanletyouwritecodethatiscleaner,easiertomaintain,anddoesnotrequirespecialtricks.Thelanguageitselfnowsupportsseveralconstructsthatrequiredthird-partymodulesearlier.LanguagechangesintroducedinES6needaseriousrethinkinthewaywehavebeencodinginJavaScript.
Anoteonthenomenclature-ECMAScript6,ES6,andECMAScript2015arethesame,butusedinterchangeably.
BrowsersupportforES6
ThemajorityofthebrowsersandserverframeworksareontheirwaytowardsimplementingES6features.Youcancheckoutthewhatissupportedandwhatisnotbyclickinghttp://kangax.github.io/compat-table/es6/.
ThoughES6isnotfullysupportedonallbrowsersandserverframeworks,wecanstartusingalmostallfeaturesofES6withthehelpoftranspilers.Transpilersaresource-to-sourcecompilers.ES6transpilersallowyoutowritecodeinES6syntaxandcompile/transformthemintoequivalentES5syntax,whichcanthenberunonbrowsersthatdonotsupporttheentirerangeofES6features.
ThedefactoES6transpileratthemomentisBabel.Inthisbook,wewilluseBabelandwriteandtestourexamples.
Babel
BabelsupportsalmostallES6featuresoutoftheboxorwithcustomplugins.Babelcanbeusedfromawiderangeofbuildsystems,frameworks, andlanguagestotemplateengines,andhasagoodcommandlineandread-eval-printloop(REPL)builtin.
TogetagoodideaabouthowBabeltranspilesES6codetoitsES5equivalentform,headovertoBabelREPL(http://babeljs.io/repl/).
BabelREPLallowsyoutoquicklytestsmallsnippetsofES6.WhenyouopenBabelREPLinthebrowser, you will see some ES6 code defaulted there. On the left pane, remove the code and typeinthefollowingtext:
varname="John",mood="happy";console.log(`Hey${name},areyoufeeling${mood}today?`)
Whenyoutypethisandtaboutoftheleftpane,youwillseeREPLtranspilingthisES6codeintosomethinglikethefollowingcode:
"usestrict";varname="John",mood="happy";console.log("Hey"+name+",areyoufeeling"+mood+"today?");
ThisistheES5equivalentofthecodewewroteearlierintheleftpane.Youcanseethattheresulting code in the right pane is a familiar ES5. As we said, Babel REPL is a good place to tryandexperimentwithvariousES6constructs.However,weneedbabeltoautomaticallytranspileyourES6codeintoES5,andforthat,youcanincludeBabelintoyourexistingbuildsystemsorframeworks.
Let'sbeginbyinstallingBabelasacommand-linetool.Forthis,wewillassumethatyouarefamiliarwithnodeandNodePackageManager (npm).InstallingBabelusingnpmiseasy.Let'sfirstcreateadirectorywherewewillhaveBabelinstalledasamoduleandrestofthesourcecode.OnmyMac,thefollowingcommandswillcreateadirectorycalledbabel_test,initializetheprojectusingnpminit,andinstallBabelcommandlineusingnpm:
mkdirbabel_testcdbabel_test&&npminitnpminstall--save-devbabel-cli
Ifyouarefamiliarwithnpm,youmaygettemptedtoinstallBabelglobally.However,installingBabelasaglobalmoduleisnotgenerallyagoodidea.OnceyouhaveinstalledBabelinyourproject,yourpackage.jsonfilewilllooksomethinglikethefollowingblockofcode:
{
"name":"babel_test","version":"1.0.0","description":"","main":"index.js","scripts":{"test":"echo"Error:notestspecified"&&exit1"},"author":"","license":"ISC","devDependencies":{"babel-cli":"^6.10.1"}}
YoucanseeadevelopmentdependencycreatedforBabelforversion>6.10.1.YoucanuseBabeltotranspileyourcodebyeitherinvokingitfromthecommandlineoraspartofthebuildstep.Foranynon-trivialwork,youwillneedthelaterapproach.ToinvokeBabelaspartoftheprojectbuildstep,youcanaddabuildstepinvokingBabelinsideyourscripttagtoyourpackage.jsonfile,forexample:
"scripts":{"build": "babel src -d lib"
},
Whenyoudonpmbuild,Babelwillbeinvokedonyoursrcdirectoryandthetranspiledcodewillbeplacedinsidelibdirectory.Alternatively,youcanrunBabelmanuallyalsobywritingthefollowingcommand:
$./node_modules/.bin/babelsrc-dlib
WewilltalkaboutvariousBabeloptionsandpluginslaterinthebook.ThissectionwillequipyoutostartexploringES6.
Object-oriented programmingBefore diving into JavaScript, let's take a moment to review what people mean when they sayobject-oriented,andwhatthemainfeaturesofthisprogrammingstyleare.Here'salistofconceptsthataremostoftenusedwhentalkingaboutobject-orientedprogramming(OOP):
Object,method,andpropertyClassEncapsulationAggregationReusability/inheritancePolymorphism
Let'stakeacloserlookintoeachoneoftheseconcepts.Ifyou'renewtotheobject-orientedprogramminglingo,theseconceptsmightsoundtootheoretical,andyoumighthavetroublegraspingorrememberingthemfromonereading.Don'tworry,itdoestakeafewtries,andthesubjectcanbealittledryataconceptuallevel.However,we'lllookatplentyofcodeexamplesfurtheroninthebook,andyou'llseethatthingsaremuchsimplerinpractice.
Objects
Asthenameobject-orientedsuggests,objectsareimportant.Anobjectisarepresentationofathing(someoneorsomething),andthisrepresentationisexpressedwiththehelpofaprogramminglanguage.Thethingcanbeanything,areal-lifeobject,oramoreconvolutedconcept.Takingacommonobject,acat,forexample,youcanseethatithascertaincharacteristics-color,name,weight,andsoonandcanperformsomeactions-meow,sleep,hide,escape,andsoon.ThecharacteristicsoftheobjectarecalledpropertiesinOOP-speak,andtheactionsarecalledmethods.
Theanalogywiththespokenlanguageareasfollows:
Objectsaremostoftennamedusingnouns,suchasbook,person,andsoonMethodsareverbs,forexample,read,run,andsoonValuesofthepropertiesareadjectives
Takethesentence"Theblackcatsleepsonthemat"asanexample."Thecat"(anoun)istheobject,"black"(adjective)isthevalueofthecolorproperty,and"sleep"(averb)isanactionoramethodinOOP.Forthesakeoftheanalogy,wecangoastepfurtherandsaythat"onthemat"specifiessomethingabouttheaction"sleep",soit'sactingasaparameterpassedtothesleepmethod.
Classes
Inreallife,similarobjectscanbegroupedbasedonsomecriteria.Ahummingbirdandaneaglearebothbirds,sotheycanbeclassifiedasbelongingtosomemade-upBirdsclass.InOOP,aclassisablueprintorarecipeforanobject.Anothernameforobjectisinstance,sowecansaythattheeagleisoneconcreteinstanceofthegeneralBirdsclass.Youcancreatedifferentobjectsusingthesameclassbecauseaclassisjustatemplate,whiletheobjectsareconcreteinstancesbasedonthetemplate.
There'sadifferencebetweenJavaScriptandtheclassicOOlanguagessuchasC++andJava.YoushouldbeawarerightfromthestartthatinJavaScript,therearenoclasses;everythingisbasedonobjects.JavaScripthasthenotionofprototypes,whicharealsoobjects(we'lldiscussthemlaterindetail).InaclassicOOlanguage,you'dsaysomethinglike-createanewobjectformecalledBob,whichisofclassPerson.InaprototypalOOlanguage,you'dsay-I'mgoingtotakethisobjectcalledBob'sdadthatIhavelyingaround(onthecouchinfrontoftheTV?)andreuseitasaprototypeforanewobjectthatI'llcallBob.
Encapsulation
Encapsulationisanother OOPrelatedconcept,whichillustratesthefactthatanobjectcontains(encapsulates)thefollowing:
Data(storedinproperties)Themeanstodosomethingwiththedata(usingmethods)
Oneothertermthatgoestogetherwithencapsulationisinformationhiding.Thisisaratherbroadtermandcanmeandifferentthings,butlet'sseewhatpeopleusuallymeanwhentheyuseitinthecontextofOOP.
Imagineanobject,say,anMP3player.You,astheuseroftheobject,aregivensomeinterfacetoworkwith,suchasbuttons,display,andsoon.Youusetheinterfaceinordertogettheobjecttodosomethingusefulforyou,likeplayasong.Howexactlythedeviceisworkingontheinside,youdon'tknow,and,mostoften,don'tcare.Inotherwords,theimplementationoftheinterfaceishiddenfromyou.ThesamethinghappensinOOPwhenyourcodeusesanobjectbycallingitsmethods.Itdoesn'tmatterifyoucodedtheobjectyourselforitcamefromsomethird-partylibrary;yourcodedoesn'tneedtoknowhowthemethodsworkinternally. Incompiledlanguages,youcan'tactuallyreadthecodethatmakesanobjectwork.InJavaScript,becauseit'saninterpretedlanguage,youcanseethesourcecode,buttheconceptisstillthesame-youworkwiththe object's interface without worrying about its implementation.
Anotheraspectofinformationhidingisthevisibilityofmethodsandproperties.Insomelanguages,objectscanhavepublic,private,andprotectedmethodsandproperties.Thiscategorizationdefinesthelevelofaccesstheusersoftheobjecthave.Forexample,onlythemethodsofthesameobjecthaveaccesstotheprivatemethods,whileanyonehasaccesstothepublicones.InJavaScript,allmethodsandpropertiesarepublic,butwe'llseethattherearewaystoprotectthedatainsideanobjectandachieveprivacy.
Aggregation
Combiningseveralobjectsintoanewoneisknownasaggregationorcomposition.It'sapowerfulwaytoseparateaproblemintosmallerandmoremanageableparts(divideandconquer).Whenaproblemscopeissocomplexthatit'simpossibletothinkaboutitatadetailedlevelinitsentirety,youcanseparatetheproblemintoseveralsmallerareas,andpossiblythenseparateeachoftheseintoevensmallerchunks.Thisallowsyoutothinkabouttheproblemonseverallevelsofabstraction.
Take,forexample,apersonalcomputer.It'sacomplexobject.Youcannotthinkaboutallthethingsthatneedtohappenwhenyoustartyourcomputer.But,youcanabstracttheproblemsayingthatyouneedtoinitializealltheseparateobjectsthatyourComputerobjectconsistsoftheMonitorobject,theMouseobject,theKeyboardobject,andsoon.Then,youcandivedeeperintoeachofthesubobjects.Thisway,you'recomposingcomplexobjectsbyassemblingreusableparts.
Touseanotheranalogy,aBookobjectcancontain(aggregate)oneormoreAuthorobjects,aPublisherobject,severalChapterobjects,aTOC(tableofcontents),andsoon.
Inheritance
Inheritanceisanelegantwaytoreuseexistingcode.Forexample,youcanhaveagenericobject,Person,whichhaspropertiessuchasnameanddate_of_birth,andwhichalsoimplementsthewalk,talk,sleep,andeatfunctionality.Then,youfigureoutthatyouneedanotherobjectcalledProgrammer.YoucanreimplementallthemethodsandpropertiesthataPersonobjecthas,butitwillbesmartertojustsaythattheProgrammerobjectinheritsaPersonobject,andsaveyourselfsomework.TheProgrammerobjectonlyneedstoimplementmorespecificfunctionality,suchasthewriteCodemethod,whilereusingallofthePersonobject'sfunctionality.
InclassicalOOP,classesinheritfromotherclasses,butinJavaScript,astherearenoclasses,objectsinheritfromotherobjects.
Whenanobjectinheritsfromanotherobject,itusuallyaddsnewmethodstotheinheritedones,thusextendingtheoldobject.Often,thefollowingphrasescanbeusedinterchangeably-BinheritsfromAandBextendsA.Also,theobjectthatinheritscanpickoneormoremethodsandredefinethem, customizing them for its own needs. This way, the interface stays the same and the methodnameisthesame,butwhencalledonthenewobject,themethodbehavesdifferently.Thiswayofredefininghowaninheritedmethodworksisknownasoverriding.
Polymorphism
Intheprecedingexample,aProgrammerobjectinheritedallofthemethodsoftheparentPersonobject.Thismeansthatbothobjectsprovideatalkmethod,amongothers.Nowimaginethatsomewhereinyourcode,there'savariablecalledBob,anditjustsohappensthatyoudon'tknowifBobisaPersonobjectoraProgrammerobject.YoucanstillcallthetalkmethodontheBobobjectandthecodewillwork.Thisabilitytocallthesamemethodondifferentobjects,andhaveeachofthemrespondintheirownway,iscalledpolymorphism.
OOP summaryHere's a quick table summarizing the concepts discussed so far:
FeatureIllustratesconcept
Bobisaman(anobject). Objects
Bob'sdateofbirthisJune1,1980,gender-male,andhair-black. Properties
Bobcaneat,sleep,drink,dream,talk,andcalculatehisownage. Methods
BobisaninstanceoftheProgrammerclass.Class(inclassicalOOP)
BobisbasedonanotherobjectcalledProgrammer.
Prototype
(inprototypalOOP)
Bobholdsdata,suchasbirth_date,andmethodsthatworkwiththedata,suchascalculateAge(). Encapsulation
Youdon'tneedtoknowhowthecalculationmethodworksinternally.Theobjectmighthavesomeprivatedata,suchasthenumberofdaysinFebruaryinaleapyear.Youdon'tknow,nordoyouwanttoknow.
Informationhiding
BobispartofaWebDevTeamobjecttogetherwithJill,aDesignerobject,andJack,aProjectManagerobject.Aggregationandcomposition
Designer,ProjectManager,andProgrammerareallbasedonandextendaPersonobject. Inheritance
YoucancallthemethodsBob.talk(),Jill.talk(),andJack.talk(),andthey'llallworkfine,albeitproducingdifferentresults.Bobwillprobablytalkmoreaboutperformance,Jillaboutbeauty,andJackaboutdeadlines.EachobjectinheritedthemethodtalkfromPersonandcustomizedit.
Polymorphismandmethodoverriding
Setting up your training environmentThis book takes a do-it-yourself approach when it comes to writing code, because I firmlybelievethatthebestwaytoreallylearnaprogramminglanguageisbywritingcode.Therearenocut-and-paste-readycodedownloadsthatyousimplyputinyourpages.Onthecontrary,you'reexpectedtotypeincode,seehowitworks,andthentweakitandplayaroundwithit.Whentryingoutthecodeexamples,you'reencouragedtoenterthecodeintoaJavaScriptconsole.Let'sseehowyougoaboutdoingthis.
Asadeveloper,youmostlikelyalreadyhaveanumberofwebbrowsersinstalledonyoursystem,suchasFirefox,Safari,Chrome,orInternetExplorer.AllmodernbrowsershaveaJavaScriptconsolefeaturethatyou'llusethroughoutthebooktohelpyoulearnandexperimentwiththelanguage.Morespecifically,thisbookusesWebKit'sconsole,whichisavailableinSafariandChrome,buttheexamplesshouldworkinanyotherconsole.
WebKit'swebinspector
Thisexampleshowshowyoucanusetheconsoletotypeinsomecodethatswapsthelogoonthegoogle.comhomepagewithanimageofyourchoice.Asyoucansee,you cantestyourJavaScriptcodeliveonanypage:
InordertobringuptheconsoleinChromeorSafari,rightclickanywhereonapageandselectInspectElement.TheadditionalwindowthatshowsupistheWebInspectorfeature.SelecttheConsoletab,andyou'rereadytogo.
Youtypecodedirectlyintotheconsole,andwhenyoupressEnter,yourcodeisexecuted.The
returnvalueofthecodeisprintedintheconsole.Thecodeisexecutedinthecontextofthecurrentlyloadedpage,so,forexample,ifyoutypelocation.href,itwillreturntheURLofthecurrentpage.
The console also has an autocomplete feature. It works in a similar way to the normal command-linepromptinyouroperatingsystemorautocompletefeatureofthefull-fledgedIDEs.If,forexample,youtypedocuandhittheTaborrightarrowkey,docuwillbeautocompletedtodocument.Then,ifyoutype.(thedotoperator),youcaniteratethroughalltheavailablepropertiesandmethodsyoucancallonthedocumentobject.
Byusingtheupanddownarrowkeys,youcangothroughthelistofalreadyexecutedcommandsandbringthembackintheconsole.
Theconsolegivesyouonlyonelinetotypein,butyoucanexecuteseveralJavaScriptstatementsbyseparatingthemwithsemicolons.Ifyouneedmorelines,youcanpressShift+Entertogotoanewlinewithoutexecutingtheresultjustyet.
JavaScriptCoreonaMac
OnaMac,youdon'tactuallyneedabrowser;youcanexploreJavaScriptdirectlyfromyourcommandlineTerminalapplication.
Ifyou'veneverusedTerminal,youcansimplysearchforitinSpotlightsearch.Onceyou'velaunchedit,typethefollowingcommand:
aliasjsc='/System/Library/Frameworks/JavaScriptCore.framework/Versions/Current/Resources/jsc'
ThiscommandmakesanaliastothelittlejscapplicationthatstandsforJavaScriptCoreandispartoftheWebKitengine.JavaScriptCoreisshippedtogetherwithMacoperatingsystems.
You can add the alias line shown previously to your ~/.profile file so that jsc is always therewhen you need it.
Now,inordertostarttheinteractiveshell,youwillsimplytypejscfrom anydirectory.Then,youcantypeJavaScriptexpressions,andwhenyouhitEnter,you'llseetheresultoftheexpression.Takealookatthefollowingscreenshot:
Moreconsoles
Allmodernbrowsershaveconsolesbuiltin.YouhaveseentheChrome/Safariconsolepreviously.InanyFirefoxversion,youcaninstalltheFirebugextension,whichcomeswithaconsole.Additionally,innewerFirefoxreleases,there'saconsolebuiltinandaccessibleviatheTools|WebDeveloper|WebConsolemenu.
InternetExplorer,sinceversion8,hasanF12DeveloperToolsfeature,whichhasaconsoleinitsScripttab.
It'salsoagoodideatofamiliarizeyourselfwithNode.js,andyoucanstartbytryingoutitsconsole.InstallNode.jsfromhttp://nodejs.organdtrytheconsoleinyourcommandprompt(terminal):
Asyoucansee,youcanusetheNode.jsconsoletotryoutquickexamples.But,youcanalsowritelongershellscripts(test.jsinthescreenshot)andrunthemwiththescriptname.jsnode.
NodeREPLisapowerfuldevelopmenttool.Whenyoutype'node'onthecommandline,theREPLinvokes.YoucantryoutJavaScriptonthis REPL:
node>console.log("HellowWorld");HellowWorldundefined>a=10,b=10;10>console.log(a*b);100undefined
SummaryIn this chapter, you learned about how JavaScript was born, and where it is today. You were alsointroducedtoobject-orientedprogrammingconceptsandhaveseenhowJavaScriptisnotaclass-basedOOlanguage,butaprototype-basedone.Finally,youlearnedhowtouseyourtrainingenvironment-theJavaScriptconsole.Now,you'rereadytodiveintoJavaScriptandlearnhowtouseitspowerfulOOfeatures.However,let'sstartfromthebeginning.
ThenextchapterwillguideyouthroughthedatatypesinJavaScript(therearejustafew),conditions,loops,andarrays.Ifyouthinkyouknowthesetopics,feelfreetoskipthenextchapter,butnotbeforeyoumakesureyoucancompletethefewshortexercisesattheendofthechapter.
Chapter 2. Primitive Data Types, Arrays,Loops,andConditionsBeforedivingintotheobject-orientedfeaturesof JavaScript,let'sfirsttakealookatsomeofthebasics.Thischapterwalksyouthroughthefollowingtopics:
TheprimitivedatatypesinJavaScript,such asstringsandnumbersArraysCommonoperators,suchas+,-,delete,andtypeofFlowcontrolstatements,suchasloopsandif...elseconditions
VariablesVariables are used to store data; they are placeholders for concrete values. When writingprograms,it'sconvenienttousevariablesinsteadoftheactualdataasit'smucheasiertowritepiinsteadof3.141592653589793;especiallywhenithappensseveraltimesinsideyourprogram.Thedatastoredinavariablecanbechangedafteritinitiallyassigned,hencethenamevariable.Youcanalsousevariablestostoredatathatisunknowntoyouwhileyouwritethecode,suchastheresultofalateroperation.
Usingavariablerequiresthefollowingtwosteps.Youwillneedto:
Declare the variableInitializeit,thatis,giveitavalue
Todeclareavariable,youwillusethevarstatementlikethefollowingpieceofcode:
vara;varthisIsAVariable;var_and_this_too;varmix12three;
Forthenamesofthevariables,youcanuseanycombinationofletters,numbers,theunderscorecharacter,andthedollarsign.However,youcan'tstartwithanumber,whichmeansthatthefollowingdeclarationofcodeisinvalid:
var2three4five;
Toinitializeavariablemeanstogiveitavalueforthefirst(initial)time.Thefollowingarethetwowaystodoso:
Declarethevariablefirst,theninitializeitDeclareandinitializeitwithasinglestatement
Anexampleofthelatterisasfollows:
vara=1;
Nowthevariablenamedacontainsthevalue1.
Youcandeclare,andoptionallyinitialize,severalvariableswithasinglevarstatement;justseparatethedeclarationswithacomma,asshowninthefollowinglineofcode:
varv1,v2,v3='hello',v4=4,v5;
Forreadability,thisisoftenwrittenusingonevariableperline,asfollows:
varv1,
v2,v3='hello',v4=4,v5;
Note
The$characterinvariablenames
Youmayseethedollarsigncharacter($)usedinvariablenames,asin$myvarorlesscommonlymy$var.Thischaracterisallowedtoappearanywhereinavariablename,althoughpreviousversionsoftheECMAstandarddiscourageditsuseinhandwrittenprogramsandsuggesteditshouldonlybeusedingeneratedcode(programs writtenbyotherprograms).ThissuggestionisnotwellrespectedbytheJavaScriptcommunity,and$isinfactcommonlyusedinpracticeasafunctionname.
Variablesarecasesensitive
Variablenamesarecasesensitive.YoucaneasilyverifythisstatementusingyourJavaScriptconsole.TrytypingthefollowingcodebypressingEnteraftereachline:
varcase_matters='lower';varCASE_MATTERS='upper';case_matters;CASE_MATTER;
Tosavekeystrokeswhenyouenterthethirdline,youcantypecaseandpresstheTaborrightarrowkey.Consoleautocompletesthevariablenametocase_matters.Similarly,forthelastline,typeCASEandpresstheTabkey.Theendresultisshowninthefollowingfigure:
Throughouttherestofthisbook,onlythecodefortheexamplesisgiveninsteadofascreenshot,asfollows:
>varcase_matters='lower';>varCASE_MATTERS='upper';
> case_matters;"lower">CASE_MATTERS;"upper"
Thegreater-thansigns(>)showthecodethatyoutype;therestistheresultasprintedinConsole.
Again,rememberthatwhenyouseesuchcodeexamples,you'restronglyencouragedtotypeinthecodeyourself.Then,youcanexperimentbytweakingitalittlehereandtheretogetabetterfeelingofhowexactlyitworks.
Note
YoucanseeintheprecedingscreenshotthatsometimeswhatyoutypeinConsoleresultsinthewordundefined.Youcansimplyignorethis,butifyou'recurious,here'swhathappenswhenevaluating(executing)whatyoutype-theConsoleprintsthereturnedvalue.Someexpressions,suchasvara=1;,don'treturnanythingexplicitly,inwhichcase,theyimplicitlyreturnthespecialvalueundefined(moreoninabit).Whenanexpressionreturnssomevalue(forexample,case_mattersinthepreviousexampleorsomethingsuchas1+1),theresultingvalueisprintedout.Notallconsolesprinttheundefinedvalue;forexample,theFirebugconsole.
OperatorsOperators take one or two values (or variables), perform an operation, and return a value. Let'scheckoutasimpleexampleofusinganoperator,justtoclarifytheterminology:
>1+2;3
Intheprecedingcode:
The+symbolistheoperatorTheoperationisadditionTheinputvaluesare1and2(theyarealsocalledoperands)Theresultvalueis3Thewholethingiscalledanexpression
Insteadofusingthevalues1and2directlyintheexpression,youcanusevariables.Youcanalsouseavariabletostoretheresultoftheoperationasthefollowingexampledemonstrates:
>vara=1;>varb=2;>a+1;2>b+2;4>a+b;3>varc=a+b;>c;3
Thefollowingtableliststhebasicarithmeticoperators:
Operatorsymbol
Operation Example
+ Addition>1+2;3
- Subtraction>99.99-11;88.99
* Multiplication>2*3;6
/ Division>6/4;1.5
%Modulo,theremainderofadivision
>6%3;0>5%3;2
It'ssometimesusefultotestifanumberisevenorodd.Usingthemodulooperator,it'seasytodojustthat.Alloddnumbersreturn1whendividedby2,whileallevennumbersreturn0,forexample:
>4%2;0>5%2;1
++Incrementavalue by 1
Postincrementiswhentheinputvalueisincrementedafterit'sreturned,forexample:
>vara=123;>varb=a++;>b;123>a;124
Theoppositeispre-increment.Theinputvalueisincrementedby1firstandthenreturned,forexample:
>vara=123;>varb=++a;>b;124>a;124
--Decrementavalueby1
Post-decrement:
>vara=123;>varb=a--;>b;123>a;122
Pre-decrement:
>vara=123;>varb=--a;>b;122>a;122
Thevara=1;isalsoanoperation;it'sthesimpleassignmentoperation,and=isthesimpleassignmentoperator.
Thereisalsoafamilyof operatorsthatareacombinationofanassignmentandanarithmeticoperator. These are called compound operators. They can make your code more compact. Let'sseesomeofthemwiththefollowingexamples:
>vara=5;>a+=3;8
Inthisexample,a+=3;isjustashorterwayofdoinga=a+3;.Forexample:
>a-=3;5
Here,a-=3;isthesameasa=a-3;:
>a*=2;10>a/=5;2>a%=2;0
Inadditiontothearithmeticandassignmentoperatorsdiscussedpreviously,thereareothertypesofoperators,asyou'llseelaterinthis,andthefollowingchapters.
Note
Bestpractice
Alwaysendyourexpressionswithasemicolon.JavaScripthasasemicoloninsertionmechanism,whereitcanaddthesemicolonifyouforgetitattheendofaline.However,thiscanalsobeasourceoferrors,soit'sbesttomakesureyoualwaysexplicitlystatewhereyouwanttoterminateyourexpressions.Inotherwords,bothexpressions>1+1and>1+1;willwork;butthroughoutthebook,you'llalwaysseethesecondtype,terminatedwithasemicolon,justtoemphasizethishabit.
Primitive data typesAny value that you use is of a certain type. In JavaScript, the following are just a few primitivedatatypes:
1. Number:Thisincludesfloatingpointnumbersaswellasintegers.Forexample,thesevaluesareallnumbers-1,100,3.14.
2. String:Theseconsistofanynumberofcharacters,forexample,a,one,andone2three.3. Boolean:Thiscanbeeithertrueorfalse.4. Undefined:Whenyoutrytoaccessavariablethatdoesn'texist,yougetthespecialvalueundefined.Thesamehappenswhenyoudeclareavariablewithoutassigningavaluetoityet.JavaScriptinitializesthevariablebehindthesceneswiththevalueundefined.Theundefineddatatype canonlyhaveonevalue-thespecialvalueundefined.
5. Null:Thisisanotherspecialdatatypethatcanhaveonlyonevalue-thenullvalue.Itmeansnovalue,anemptyvalue,ornothing.Thedifferencewithundefinedisthatifavariablehasanullvalue,it'sstilldefined;itjustsohappensthatitsvalueisnothing.You'llseesomeexamplesshortly.
Anyvaluethatdoesn'tbelongtooneofthefiveprimitivetypeslistedhereisanobject.Evennullisconsideredanobject,whichisalittleawkwardhavinganobject(something)thatisactuallynothing.We'lllearnmoreaboutobjectsinChapter4,Objects,butforthetimebeing,justrememberthatinJavaScript,thedatatypesareasfollows:
Primitive(thefivetypeslistedpreviously)Non-primitive(objects)
Findingoutthevaluetype-thetypeofoperator
Ifyouwanttoknowthetypeofavariableoravalue,youcanusethespecialtypeofoperator.Thisoperatorreturnsastringthatrepresentsthedatatype.Thereturnvaluesofusingtypeofareoneofthefollowing:
numberstringbooleanundefinedobjectfunction
Inthenextfewsections,you'llseetypeofinactionusingexamplesofeachofthefiveprimitivedatatypes.
Numbers
Thesimplestnumberisaninteger.Ifyouassign1toavariable,andthenusethetypeofoperator,itreturnsthestringnumber,asfollows:
>varn=1;>typeofn;"number">n=1234;>typeofn;"number"
Intheprecedingexample,youcanseethatthesecondtimeyousetavariable'svalue,youdon'tneedthevarstatement.
Numberscanalsobefloatingpoint(decimals),forexample:
>varn2=1.23;>typeofn;"number"
Youcancalltypeofdirectlyonthevaluewithoutassigningittoavariablefirst,forexample:
>typeof123;"number"
Octalandhexadecimalnumbers
Whenanumberstartswitha0,it'sconsideredan octalnumber.Forexample,theoctal0377isthedecimal255:
>varn3=0377;>typeofn3;"number">n3;255
Thelastlineintheprecedingexampleprintsthedecimalrepresentationoftheoctalvalue.
ES6providesaprefix0o(or0O,butthislooksveryconfusinginmostmonospacefonts)torepresentoctals.Considerthefollowinglineofcodeforexample:
console.log(0o776);//510
Whileyoumaynotbeintimatelyfamiliarwithoctalnumbers,you'veprobablyusedhexadecimalvaluestodefinecolorsinCSSstylesheets.
InCSS,youhaveseveraloptionstodefineacolor,twoofthemareasfollows:
UsingdecimalvaluestospecifytheamountofR(red),G(green),andB(blue),rangingfrom0to255.Forexample,rgb(0,0,0)isblackandrgb(255,0,0)isred (maximumamountofredandnogreenor blue).UsinghexadecimalsandspecifyingtwocharactersforeachR,G,andBvalue.Forexample,#000000isblackand#ff0000isred.Thisisbecauseffisthehexadecimalvaluefor255.
InJavaScript,youcanput0xbeforeahexadecimalvalue,alsocalledhex forshort,forexample:
>varn4=0x00;>typeofn4;"number">n4;0>varn5=0xff;>typeofn5;"number">n5;255
BinaryLiterals
UntillES6,ifyouneededbinaryrepresentationofaninteger,youhadtopassthemtotheparseInt()functionasastringwitharadixof2,asfollows:
console.log(parseInt('111',2));//7
InES6youcanuse0b(or0B)prefixtorepresentbinaryintegers.Forexample:
console.log(0b111);//7
Exponentliterals
1e1(alsowrittenas1e+1or1E1or1E+1)representsthenumber1witha0afterit,orinotherwords,10.Similarly,2e+3representsthenumber2withthree0safterit,or2000,forexample:
>1e1;10>1e+1;
10>2e+3;2000>typeof2e+3;"number"
2e+3meansmovingthedecimalpointthreedigitstotherightofthenumber2.There'salso2e-3,meaningyoumovethedecimalpointthreedigitstotheleftofthenumber2.Lookatthefollowingfigure:
Thefollowingisthecode:
>2e-3;0.002>123.456E-3;0.123456>typeof2e-3;"number"
Infinity
ThereisaspecialvalueinJavaScriptcalledInfinity.ItrepresentsanumbertoobigforJavaScripttohandle.Infinityisindeedanumber,astypingtypeofInfinityintheconsolewillconfirm.Youcanalsoquicklycheckthatanumberwith308zerosisok,but309zerosistoomuch.Tobeprecise,thebiggestnumberJavaScriptcanhandleis1.7976931348623157e+308,whilethesmallestis5e-324,Lookatthefollowingexample:
> Infinity;Infinity>typeofInfinity;"number">1e309;Infinity>1e308;1e+308
Dividingbyzerogivesyouinfinity,forexample:
>vara=6/0;>a;
Infinity
Infinityisthebiggestnumber(orratheralittlebiggerthanthebiggest),buthowaboutthe
smallest?It'sinfinitywithaminussigninfrontofit;-Infinity,forexample:
>vari=-Infinity;>i;-Infinity>typeofi;"number"
Doesthismeanyoucanhavesomethingthat'sexactlytwiceasbigasInfinity,from0uptoinfinityandthenfrom0downtominusinfinity?Well,notreally.WhenyousumInfinityand-Infinity,youdon'tget0,butsomethingthatiscalledNotaNumber(NaN),Forexample:
>Infinity-Infinity;NaN>-Infinity+Infinity;NaN
AnyotherarithmeticoperationwithInfinityasoneoftheoperandsgivesyouInfinity,forexample:
>Infinity-20;Infinity>-Infinity*3;-Infinity>Infinity/2;Infinity>Infinity-99999999999999999;Infinity
Thereisalesserknownglobalmethod,isFinite(),thattellsyouifthevalueisinfinityornot.ES6addsaNumber.isFinite()methodtodojustthat.Whyanothermethod,youmayask.TheglobalvariantofisFinite()triestocastthevaluethroughNumber(value),whileNumber.isFinite()doesn't,henceit'smoreaccurate.
NaN
WhatwasthisNaNinthepreviousexample?Itturnsoutthatdespiteitsname,NotaNumber,NaNisaspecialvaluethatisalsoanumber:
>typeofNaN;"number">vara=NaN;>a;NaN
YougetNaNwhenyoutrytoperformanoperationthatassumesnumbers,buttheoperationfails.Forexample,ifyoutrytomultiply10bythecharacter"f",theresultisNaN,because"f"isobviouslynotavalidoperandforamultiplication:
>vara=10*"f";>a;NaN
NaNiscontagious,soifyouhaveevenoneNaNinyourarithmeticoperation,thewholeresultgoesdownthedrain,forexample:
>1+2+NaN;NaN
Number.isNaN
ES5hasaglobalmethod-isNaN().ItdeterminesifavalueisNaNornot.ES6providesaverysimilarmethod-Number.isNaN()(Noticethatthismethodisnotglobal).
ThedifferencebetweentheglobalisNaN()andNumber.isNaN()isthatglobalisNaN()castsnon-numericvaluesbeforeevaluatingthemtobeNaN.Let'slookatthefollowingexample.WeareusingtheES6Number.isNaN()methodtotestifsomethingisaNaNornot:
console.log(Number.isNaN('test'));//false:StringsarenotNaNconsole.log(Number.isNaN(123));//false:integersarenotNaNconsole.log(Number.isNaN(NaN));//true:NaNsareNaNsconsole.log(Number.isNaN(123/'abc'));//true:123/'abc'resultsinan
NaN
WesawthatES5'sglobalisNaN()methodfirstcastsnon-numericvaluesandthendoesthecomparison;thefollowingresultwillbedifferentfromitsES6counterpart:
console.log(isNaN('test'));//true
Ingeneral,comparedtoitsglobalvariant,Number.isNaN()ismorecorrect.However,neitherofthemcanbeusedtofigureoutifsomethingisnotanumber-theyjustanswerifthevalueisaNaNornot.Practically,youareinterestedinknowingifavalueidentifiesasanumberornot.Mozillasuggeststhefollowingpolyfillmethodtodojustthat:
functionisNumber(value){returntypeofvalue==='number'&&!Number.isNaN(value);}
Number.isInteger
ThisisanewmethodinES6.Itreturnstrueifthenumberisfiniteanddoesnotcontainanydecimalpoints(isaninteger):
console.log(Number.isInteger('test'));//falseconsole.log(Number.isInteger(Infinity));//falseconsole.log(Number.isInteger(NaN));//falseconsole.log(Number.isInteger(123));//trueconsole.log(Number.isInteger(1.23));//false
Strings
Astringisasequenceof charactersusedtorepresenttext.InJavaScript,anyvalueplacedbetweensingleordoublequotesisconsideredastring.Thismeansthat1isanumber,but"1"isastring.Whenusedwithstrings,typeofreturnsthestring"string",forexample:
> var s = "some characters";>typeofs;"string">vars='somecharactersandnumbers1235.87';>typeofs;"string"
Here'sanexampleofanumberusedinthestringcontext:
>vars='1';>typeofs;"string"
Ifyouputnothinginquotes,it'sstillastring(anemptystring),forexample:
>vars="";typeofs;"string"
Asyoualreadyknow,whenyouusetheplussignwithtwonumbers,thisisthearithmeticadditionoperation.However,ifyouusetheplussignwithstrings,thisisastringconcatenationoperation,anditreturnsthetwostringsgluedtogether:
>vars1="web";>vars2="site";
> var s = s1 + s2;>s;"website">typeofs;"string"
Thedualpurposeofthe+operatorisasourceoferrors.Therefore,ifyouintendtoconcatenatestrings,it'salwaysbesttomakesurethatalloftheoperandsarestrings.Thesameappliesforaddition;ifyouintendto addnumbersthenmakesuretheoperandsarenumbers.You'lllearnvariouswaystodosofurtherinthechapterandthebook.
Stringconversions
When you use a number-like string, for example, "1", as an operand in an arithmetic operation,the string is converted to a number behind the scenes. This works for all arithmetic operationsexceptaddition,becauseofitsambiguity.Considerthefollowingexample:
>vars='1';>s=3*s;
>typeofs;"number">s;3>vars='1';>s++;>typeofs;"number">s;2
Alazywaytoconvertanynumber-likestringtoanumberistomultiplyitby1(anotherwayistouseafunctioncalledparseInt(),asyou'llseeinthenextchapter):
>vars="100";typeofs;"string">s=s*1;100>typeofs;"number"
Iftheconversionfails,you'llgetNaN:
>varmovie='101dalmatians';>movie*1;NaN
Youcanconvertastringtoanumberbymultiplyingitby1.Theopposite-convertinganythingtoastring-canbedonebyconcatenatingitwithanemptystring,asfollows:
>varn=1;>typeofn;"number">n=""+n;"1">typeofn;"string"
Specialstrings
Therearealsostringswithspecialmeanings,aslistedinthefollowingtable:
String Meaning Example
\\
'
"
The\istheescapecharacter.Whenyouwanttohavequotesinsideyourstring,youcanescapethemsothatJavaScriptdoesn'tthinktheymeantheendofthestring.
Ifyouwanttohaveanactualbackslashinthestring,escapeit
>vars='Idon'tknow';:ThisisanerrorbecauseJavaScriptthinksthestringisIdonandtherestisinvalidcode.Thefollowingcodesarevalid:
> var s = 'I don't know';>vars="Idon'tknow";>vars="Idon'tknow";>vars='"Hello",hesaid.';>vars=""Hello",hesaid.";
withanotherbackslash. Escapingtheescape:>vars="1\\2";s;"1\2"
\n Endofline.
>vars='\n1\n2\n3\n';>s;"123"
\r Carriagereturn.
Consider the following statements:
>vars='1\r2';>vars='1\n\r2';>vars='1\r\n2';
Theresultofalloftheseisasfollows:
>s;"12"
\t Tab.>vars="1\t2";>s;"12"
\uThe\ufollowedbyacharactercodeallowsyoutouseUnicode.
Here'smynameinBulgarianwrittenwithCyrilliccharacters:
>"\u0421\u0442\u043E\u044F\u043D";"Стoян"
Therearealsoadditionalcharactersthatarerarelyused:\b(backspace),\v(verticaltab),and\f(formfeed).
Stringtemplateliterals
ES6introducedtemplateliterals.Ifyouarefamiliarwithotherprogramminglanguages,PerlandPythonhavesupportedtemplateliteralsforawhilenow.Templateliteralsallowexpressionstobeembeddedwithinregularstrings.ES6hastwokindsofliterals:templateliteralsandtaggedliterals.
Templateliteralsaresingleormultiplelinestringswithembeddedexpressions.Forexample,youmusthavedonesomethingsimilartothis:
varlog_level="debug";varlog_message="meltdown";console.log("Loglevel:"+log_level+"-message:"+log_message);//Loglevel:debug-message:meltdown
Youcanaccomplishthesameusingtemplateliterals,asfollows:
console.log(`Loglevel:${log_level}-message:${log_message}`)
Templateliteralsareenclosedbytheback-tick(``)(graveaccent)characterinsteadoftheusualdoubleorsinglequotes.Templateliteralplaceholdersareindicatedbythedollarsignandcurlybraces(${expression}).Bydefault,theyareconcatenatedtoformasinglestring.Thefollowingexampleshowsatemplateliteralwithaslightlycomplexexpression:
vara=10;varb=10;console.log(`Sumis${a+b}andMultiplicationwouldbe${a*b}.`);//Sumis20andMultiplicationwouldbe100.
Howaboutembeddingafunctioncall?
vara=10;varb=10;functionsum(x,y){returnx+y}
function multi(x,y){returnx*y}console.log(`Sumis${sum(a,b)}andMultiplicationwouldbe${multi(a,b)}.`);
Templateliteralsalsosimplifymultilinestringsyntax.Insteadofwritingthefollowinglineofcode:
console.log("Thisislineone\n"+"andthisislinetwo");
Youcanhaveamuchcleanersyntaxusingtemplateliterals,whichisasfollows:
console.log(`Thisislineoneandthisislinetwo`);
ES6hasanotherinterestingliteraltypecalledTaggedTemplateLiterals.Taggedtemplatesallowyoutomodifytheoutputoftemplateliteralsusingafunction.Ifyouprefixanexpressiontoatemplateliteral,thatprefixisconsideredtobeafunctiontobeinvoked.Thefunctionneedstobe defined before we can use the tagged template literal. For example, the following expression:
transform`Nameis${lastname},${firstname}${lastname}`
Theprecedingexpressionisconvertedintoafunctioncall:
transform([["Nameis",",",""],firstname,lastname)
Thetagfunction,'transform',getstwoparameters-templatestringslikeNameisandsubstitutionsdefinedby${}.Thesubstitutionsareonlyknownatruntime.Let'sexpandthetransform
function:
functiontransform(strings,...substitutes){console.log(strings[0]);//"Nameis"console.log(substitutes[0]);//Bond}varfirstname="James";varlastname="Bond"transform`Nameis${lastname},${firstname}${lastname}`
When template strings (Name is) are passed to the tag function, there are two forms of eachtemplate string, as follows:
TherawformwherethebackslashesarenotinterpretedThecookedformwherethebackslasheshas specialmeaning
Youcanaccesstherawstringformusingrawproperty,asthefollowingexampleshows:
functionrawTag(strings,...substitutes){console.log(strings.raw[0])}rawTag`Thisisarawtextand\narenottreateddifferently`//Thisisarawtextand\narenottreateddifferently
Booleans
ThereareonlytwovaluesthatbelongtotheBooleandatatype-thetrueandfalsevaluesusedwithoutquotes:
> var b = true;>typeofb;"boolean">varb=false;>typeofb;"boolean"
Ifyouquotetrueorfalse,theybecomestrings,asshowninthefollowingexample:
> var b = "true";>typeofb;"string"
Logical operators
Therearethreeoperators,calledlogicaloperators,thatworkwithBooleanvalues.Theseareasfollows:
!-logicalNOT(negation)&&-logicalAND||-logicalOR
Youknowthatwhensomethingisnottrue,itmustbefalse.Here'showthisisexpressedusingJavaScriptandthelogical!operator:
>varb=!true;>b;false
IfyouusethelogicalNOTtwice,youwillgettheoriginalvalue,whichisasfollows:
>varb=!!true;>b;true
Ifyouusealogicaloperatoronanon-Booleanvalue,thevalueisconvertedtoBooleanbehindthescenes,asfollows:
>varb="one";>!b;false
Intheprecedingcase,thestringvalue"one"isconvertedtoaBoolean,true,andthennegated.Theresultofnegatingtrueisfalse.Inthefollowingexample,there'sadoublenegation,sothe
resultistrue:
>varb="one";>!!b;true
You can convert any value to its Boolean equivalent using a double negation. Understanding howanyvalueconvertstoaBooleanisimportant.Mostvaluesconverttotruewiththeexceptionofthefollowing,whichconverttofalse:
Theemptystring""nullundefinedThenumber0ThenumberNaNTheBooleanfalse
Thesesixvaluesarereferredtoasfalsy,whileallothersaretruthy,(including,forexample,thestrings"0","",and"false").
Let'sseesomeexamples oftheothertwooperators-thelogicalAND(&&)andthelogicalOR(||).Whenyouuse&&,theresultistrueonlyifalloftheoperandsaretrue.Whenyouuse||,theresultistrueifatleastoneoftheoperandsistrue:
>varb1=true,b2=false;> b1 || b2;
true>b1&&b2;false
Here'salistofthepossibleoperationsandtheirresults:
Operation Result
true&&true true
true&&false false
false&&true false
false&&false false
true||true true
true||false true
false||true true
false||false false
Youcanuseseverallogicaloperationsoneaftertheother,asfollows:
>true&&true&&false&&true;false>false||true||false;true
Youcanalsomix&&and ||inthesameexpression.Insuchcases,youshoulduseparenthesestoclarifyhowyouintendtheoperationtowork.Considerthefollowingexample:
>false&&false||true&&true;true>false&&(false||true)&&true;false
Operatorprecedence
Youmightwonderwhythepreviousexpression(false&&false||true&&true)returnedtrue.Theanswerliesintheoperatorprecedence,asyouknowfrommathematics:
>1+2*3;7
Thisisbecausemultiplicationhasahigherprecedenceoveraddition,so2*3isevaluatedfirst,asifyoutyped:
>1+(2*3);7
Similarlyforlogicaloperations,!hasthehighestprecedenceandisexecutedfirst,assumingtherearenoparenthesesthatdemandotherwise.Then,intheorderofprecedence,comes&&andfinally,||.Inotherwords,thefollowingtwocodesnippetsarethesame.Thefirstoneisasfollows:
>false&&false||true&&true;true
Andthesecondoneisasfollows:
>(false&&false)||(true&&true);true
Note
Bestpractice
Useparenthesesinsteadofrelyingonoperatorprecedence.Thismakesyourcodeeasiertoreadandunderstand.
The ECMAScript standard defines the precedence of operators. While it may be a goodmemorizationexercise,thisbookdoesn'tofferit.Firstofall,you'llforgetit,andsecond,evenifyoumanagetorememberit,youshouldn'trelyonit.Thepersonreadingandmaintainingyourcodewilllikelybeconfused.
Lazyevaluation
Ifyouhaveseverallogicaloperationsoneaftertheother,buttheresultbecomesclearatsomepointbeforetheend,thefinaloperationswillnot beperformedbecausetheydon'taffecttheendresult.Considerthefollowinglineofcodeasanexample:
>true||false||true||false||true;true
AstheseareallORoperationsandhavethesameprecedence,theresultwillbetrueifatleast,oneoftheoperandsistrue.Afterthefirstoperandisevaluated,itbecomesclearthattheresultwillbetrue,nomatterwhatvaluesfollow.So,theJavaScriptenginedecidestobelazy(OK,efficient)andavoidsunnecessaryworkbyevaluatingcodethatdoesn'taffecttheendresult.Youcanverifythisshort-circuitingbehaviorbyexperimentingintheconsole,asshowninthefollowingcodeblock:
>varb=5;> true || (b = 6);
true>b;5>true&&(b=6);6>b;6
Thisexamplealsoshowsanotherinterestingbehavior-ifJavaScriptencountersanon-Booleanexpressionasanoperandinalogicaloperation,thenon-Booleanisreturnedasaresult:
>true||"something";true>true&&"something";"something">true&&"something"&&true;true
Thisbehaviorisnotsomethingyoushouldrelyonbecauseitmakesthecodehardertounderstand.It's common to use this behavior to define variables when you're not sure whether they werepreviouslydefined.Inthenextexample,ifthemynumbervariableisdefined,itsvalueiskept;otherwise,it'sinitializedwiththevalue10:
>varmynumber=mynumber||10;>mynumber;10
Thisissimpleandlooks elegant,butbeawarethatit'snotcompletelyfoolproof.Ifmynumberisdefinedandinitializedto0,ortoanyofthesixfalsyvalues,thiscodemightnotbehaveasyouexpect,asshowninthefollowingpieceofcode:
>varmynumber=0;>varmynumber=mynumber||10;>mynumber;
10
Comparison
There'sanothersetofoperatorsthatallreturnaBooleanvalueasaresultoftheoperation.Thesearethecomparisonoperators.Thefollowingtableliststhemtogetherwithexampleuses:
Operatorsymbol
Description Example
==Equalitycomparison:Thisreturnstruewhenbothoperandsareequal.Theoperandsareconvertedtothesametypebeforebeingcompared.They'realsocalledloosecomparison.
>1==1;true>1==2;false>1=='1';true
===Equalityandtype comparison:Thisreturnstrueifbothoperandsareequalandofthesametype.It'sbetterandsafertocomparethiswaybecausethere'snobehind-the-scenestypeconversions.Itisalsocalledstrictcomparison.
>1==='1';false>1===1;true
!=Non-equalitycomparison:Thisreturnstrueiftheoperandsarenotequaltoeachother(afteratypeconversion).
> 1 !=1;false>1!='1';false>1!='2';true
!==Non-equalitycomparisonwithouttype conversion:Returnstrueiftheoperandsarenotequaloriftheyareofdifferenttypes.
>1!==1;false>1!=='1';true
>1>1;
> Thisreturnstrueiftheleftoperandisgreaterthantherightone.
false>33>22;true
>= Thisreturnstrueiftheleftoperandisgreaterthanorequaltotherightone.>1>=1;true
< Thisreturnstrueiftheleftoperandislessthantherightone.
>1<1;false>1<2;true
<= Thisreturnstrueiftheleftoperandislessthanorequaltotherightone.
>1<=1;true>1<=2;true
NotethatNaNisnotequaltoanything,notevenitself.Takealookatthefollowinglineofcode:
>NaN==NaN;false
Undefinedandnull
Ifyoutrytouseanon-existingvariable,you'llgetthefollowingerror:
> foo;ReferenceError:fooisnotdefined
Usingthetypeofoperatoronanon-existingvariableisnotanerror.Youwillgetthe"undefined"stringback,asfollows:
>typeoffoo;"undefined"
Ifyoudeclareavariablewithoutgivingitavalue,thisis,ofcourse,notanerror.But,thetypeofstillreturns"undefined":
>varsomevar;>somevar;>typeofsomevar;"undefined"
This is because, when you declare a variable without initializing it, JavaScript automaticallyinitializesitwiththeundefinedvalue,asshowninthefollowinglinesofcode:
>varsomevar;>somevar===undefined;true
Thenullvalue,ontheotherhand,isnotassignedbyJavaScriptbehindthescenes;it'sassignedbyyourcode,whichisasfollows:
>varsomevar=null;null>somevar;null>typeofsomevar;"object"
Althoughthedifferencebetweennullandundefinedissmall,itcanbecriticalattimes.Forexample,ifyouattemptanarithmeticoperation,youwillgetdifferentresults:
> var i = 1 + undefined;>i;NaN>vari=1+null;>i;1
Thisisbecauseofthedifferentwaysnullandundefinedareconvertedtotheotherprimitive
types.Thefollowingexamplesshowthepossibleconversions:
Conversiontoanumber:
> 1 * undefined;
ConversiontoNaN:
>1*null;0
ConversiontoaBoolean:
>!!undefined;false>!!null;false
Conversiontoastring:
>"value:"+null;"value:null">"value:"+undefined;"value:undefined"
Symbols
ES6introducedanewprimitivetype-symbols.Severallanguageshaveasimilarnotion.Symbolslookverysimilartoregularstrings,buttheyareverydifferent.Let'sseehowthesesymbolsarecreated:
varatom=Symbol()
Noticethatwedon'tusenewoperatorwhilecreatingsymbols.Youwillgetanerrorwhenyoudouseit:
varatom=newSymbol()//Symbolisnotaconstructor
YoucandescribeSymbolaswell:
varatom=Symbol('atomicsymbol')
Describingsymbolscomesinveryhandywhiledebugginglargeprogramswheretherearelotsofsymbolsscatteredacross.
ThemostimportantpropertyofSymbol(andhencethereasonoftheirexistence)isthattheyareuniqueandimmutable:
console.log(Symbol()===Symbol())//falseconsole.log(Symbol('atom')===Symbol('atom'))//false
Fornow,wewillhavetopausethisdiscussiononsymbols.Symbolsareusedaspropertykeysandplaceswhereyouneeduniqueidentifiers.Wewilldiscusssymbolsinalaterpartofthisbook.
Primitive data types recapLet's quickly summarize some of the main points discussed so far:
TherearefiveprimitivedatatypesinJavaScript:NumberStringBooleanUndefinedNull
Everythingthatisnotaprimitivedatatypeisanobject.
Theprimitivenumberdatatypecanstorepositiveandnegativeintegersorfloats,hexadecimalnumbers,octalnumbers,exponents,andthespecialnumbers-NaN,Infinity,and-Infinity.Thestringdatatypecontainscharactersinquotes.Templateliteralsallowembeddingofexpressionsinsideastring.TheonlyvaluesoftheBooleandatatypearetrueandfalse.Theonlyvalueofthenulldatatypeisthenullvalue.Theonlyvalueoftheundefineddatatypeistheundefinedvalue.AllvaluesbecometruewhenconvertedtoaBoolean,withtheexceptionofthefollowingsixfalsyvalues:
""
null
undefined
0
NaN
false
ArraysNow that you know about the basic primitive data types in JavaScript, it's time to move to a morepowerfuldatastructure-thearray.
So,whatisanarray?It'ssimplyalist(asequence)ofvalues.Insteadofusingonevariabletostoreonevalue,youcanuseonearrayvariabletostoreanynumberofvaluesaselementsofthearray.
Todeclareavariablethatcontainsanemptyarray,youcanusesquarebracketswithnothingbetweenthem,asshowninthefollowinglineofcode:
>vara=[];
Todefineanarraythathasthreeelements,youcanwritethefollowinglineofcode:
>vara=[1,2,3];
Whenyousimplytypethenameofthearrayintheconsole,youcangetthecontentsofyourarray:
>a;[1,2,3]
Nowthequestionishowtoaccessthevaluesstoredinthesearrayelements.Theelementscontainedinanarrayareindexedwithconsecutivenumbers,startingfromzero.Thefirstelementhasindex(orposition)0,thesecondhasindex1, andsoon.Here'sthethree-elementarrayfromthepreviousexample:
Index Value
0 1
1 2
2 3
Toaccessanarrayelement,youcanspecifytheindexofthatelementinsidesquarebrackets.So,a[0]givesyouthefirstelementofthearraya,a[1]givesyouthesecond,andsoon,asshowninthefollowingexample:
>a[0];1>a[1];2
Adding/updatingarrayelements
Usingtheindex,youcanalsoupdatethevaluesoftheelementsofthearray.Thenextexampleupdatesthethirdelement(index2)andprintsthecontentsofthenewarray,asfollows:
>a[2]='three';"three">a;[1,2,"three"]
Youcanaddmoreelementsbyaddressinganindexthatdidn'texistbefore,asshowninthefollowing lines of code:
>a[3]='four';"four">a;[1,2,"three","four"]
Ifyouaddanewelementbutleaveagapinthearray,thoseelementsinbetweendon'texistandreturntheundefinedvalueifaccessed.Checkoutthefollowingexample:
>vara=[1,2,3];>a[6]='n`xew';"new">a;[1,2,3,undefinedx3,"new"]
Deletingelements
Todeleteanelement,youcanusethedeleteoperator.However,afterthedeletion,thelengthofthearraydoesnotchange.Inasense,youmaygetaholeinthearray:
> var a = [1, 2, 3];>deletea[1];true>a;[1,undefined,3]>typeofa[1];"undefined"
Arraysofarrays
Arrayscancontainalltypesofvalues,includingotherarrays:
> var a = [1, "two", false, null, undefined];>a;[1,"two",false,null,undefined]>a[5]=[1,2,3];[1,2,3]>a;[1,"two",false,null,undefined,Array[3]]
TheArray[3]intheresultisclickableintheconsoleanditexpandsthearrayvalues.Let'slookatanexamplewhereyouhaveanarrayoftwoelements,bothofthembeingotherarrays:
>vara=[[1,2,3],[4,5,6]];>a;[Array[3],Array[3]]
Thefirstelementofthearrayis[0],andit'salso anarray:
>a[0];[1,2,3]
To access an element in the nested array, you can refer to the element index in another set ofsquarebrackets,asfollows:
>a[0][0];1>a[1][2];6
Notethatyoucanusethearraynotationtoaccess individualcharactersinsideastring,asshowninthefollowingcodeblock:
>vars='one';>s[0];"o"
> s[1];"n">s[2];"e"
Note
Arrayaccesstostringswassupportedbymanybrowsersforawhile(notolderIEs),butitwasofficiallyrecognizedonlyaslateasECMAScript5.
Therearemorewaystohavefunwitharrays(andwegettothoseinChapter4,Objects),butlet'sstopherefornow,rememberingthefollowingpoints:
AnarrayisadatastoreAnarraycontainsindexedelementsIndexesstartfromzeroandincrementbyoneforeachelementinthearrayToaccessanelementofanarray,youcanuseitsindexinsquarebracketsAnarraycancontainanytypeofdata,includingotherarrays
Conditions and loopsConditions provide a simple but powerful way to control the flow of code execution. Loopsallowyoutoperformrepetitiveoperationswithlesscode.Let'stakealookat:
ifconditionsswitchstatementswhile,do...while,for,andfor...inloops
Note
TheexamplesinthefollowingsectionsrequireyoutoswitchtothemultilineFirebugconsole.Or,ifyouusetheWebKitconsole,pressShift+EnterinsteadofEntertoaddanewline.
Codeblocks
Intheprecedingexamples,yousawtheuseofcodeblocks.Let'stakeamomenttoclarifywhatablockofcodeis,becauseyouwilluseblocksextensivelywhenconstructingconditionsandloops.
Ablockofcodeconsistsofzeroormoreexpressionsenclosedincurlybrackets,whichisshowninthefollowinglinesofcode:
{vara=1;varb=3;}
Youcannestblockswithineachotherindefinitely,asshowninthefollowingexample:
{vara=1;varb=3;varc,d;{c=a+b;{d=a-b;}}}
Note
Bestpracticetips
Useend-of-linesemicolons,asdiscussedpreviouslyinthechapter.Althoughthesemicolonisoptionalwhenyouhaveonlyoneexpressionperline,it'sgoodtodevelopthehabitofusingthem.Forbestreadability,theindividualexpressionsinsideablockshouldbeplacedoneperlineandseparatedbysemicolons.
Indentanycodeplacedwithincurlybrackets.Someprogrammerslikeonetabindentation,someusefourspaces,andsomeusetwospaces.Itreallydoesn'tmatter,aslongasyou'reconsistent.Intheprecedingexample,theouterblockisindentedwithtwospaces,thecodeinthefirstnestedblockisindentedwithfourspaces,andtheinnermostblockisindentedwithsixspaces.
Use curly brackets. When a block consists of only one expression, the curly brackets are optional,butforreadabilityandmaintainability,youshouldgetintothehabitofalwaysusingthem,evenwhenthey'reoptional.
Theifcondition
Here'sasimpleexampleofanifcondition:
varresult='',a=3;if(a>2){result='aisgreaterthan2';}
Thepartsoftheifconditionareasfollows:
TheifstatementAconditioninparentheses-isagreaterthan2?Ablockofcodewrappedin{}thatexecutesiftheconditionissatisfied
Thecondition(thepartinparentheses)alwaysreturnsaBooleanvalue,andmayalsocontainthefollowing:
Alogicaloperation-!,&&,or||Acomparison,suchas===,!=,>,andsoonAnyvalueorvariablethatcanbeconvertedtoaBooleanAcombinationoftheabove
Theelseclause
Therecanalsobeanoptionalelsepartoftheifcondition.Theelsestatementisfollowedbyablockofcodethatrunsiftheconditionevaluatestofalse:
if(a>2){result='aisgreaterthan2';}else{result='aisNOTgreaterthan2';}
Inbetweentheifandtheelsestatements,therecanalsobeanunlimitednumberofelse...ifconditions.Here'sanexample:
if(a>2||a<-2){result='aisnotbetween-2and2';}elseif(a===0&&b===0){result='bothaandbarezeros';}elseif(a===b){result='aandbareequal';}else{result='Igiveup';}
Youcanalsonestconditionsbyputtingnewconditionswithinanyoftheblocks,asshowninthefollowingpieceofcode:
if(a===1){if(b===2){
result='ais1andbis2';}else{result='ais1butbisdefinitelynot2';}}else{result='aisnot1,noideaaboutb';}
Checkingifavariableexists
Let'sapplythenewknowledgeaboutconditionsforsomethingpractical.It'softennecessarytocheckwhetheravariableexists.Thelaziestwaytodothisistosimplyputthevariableintheconditionpartoftheifstatement,forexample,if(somevar){...}.But,thisisnotnecessarilythebestmethod.Let'stakealookatanexamplethattestswhetheravariablecalledsomevarexists,andifso,setstheresultvariabletoyes:
>varresult='';>if(somevar){result='yes';}ReferenceError:somevarisnotdefined>result;""
Thiscodeobviouslyworksbecausetheendresultwasnotyes.Butfirstly,thecodegeneratedanerror-somevarisnotdefined,andyoudon'twant yourcodetobehavelikethat.Secondly,justbecauseif(somevar)returnsfalse,itdoesn'tmeanthatsomevarisnotdefined.Itcouldbethatsomevarisdefinedandinitializedbutcontainsafalsyvaluelikefalseor0.
Abetterwaytocheckifavariableisdefinedistousetypeof:
>varresult="";>if(typeofsomevar!=="undefined"){result="yes";
}>result;""
Thetypeofoperatoralwaysreturnsastring,andyoucancomparethisstringwiththestring"undefined".Notethatthesomevarvariablemayhavebeendeclaredbutnotassignedavalueyetandyou'llstillgetthesameresult.So,whentestingwithtypeoflikethis,you'rereallytestingwhetherthevariablehas anyvalueotherthantheundefinedvalue:
>varsomevar;>if(typeofsomevar!=="undefined"){result="yes";
}>result;""
>somevar=undefined;>if(typeofsomevar!=="undefined"){result="yes";}>result;""
Ifavariableisdefinedandinitializedwithanyvalueotherthanundefined,itstypereturnedbytypeofisnolonger"undefined",asshowninthefollowingpieceofcode:
>somevar=123;>if(typeofsomevar!=="undefined"){result='yes';}>result;"yes"
Alternativeifsyntax
Whenyouhaveasimplecondition,youcanconsiderusinganalternativeifsyntax.Takealookatthis:
vara=1;varresult='';
if (a === 1) {result="aisone";}else{result="aisnotone";}
Youcanalsowritethisas:
>vara=1;>varresult=(a===1)?"aisone":"aisnotone";
Youshouldonlyusethissyntaxforsimpleconditions.Becarefulnottoabuseit,asitcaneasilymakeyourcodeunreadable.Here'sanexample.
Let'ssayyouwanttomakesureanumberiswithinacertainrange,saybetween50and100:
>vara=123;>a=a>100?100:a<50?50:a;>a;100
Itmaynotbeclearhowthiscodeworksexactlybecauseofthemultiple?.Addingparenthesesmakesitalittleclearer,asshowninthefollowingcodeblock:
>vara=123;>a=(a>100?100:a<50)?50:a;>a;
50>vara=123;>a=a>100?100:(a<50?50:a);>a;100
?:iscalledaternaryoperatorbecauseittakesthreeoperands.
Switch
Ifyoufindyourselfusinganifconditionandhavingtoomanyelse...ifparts,youcanconsiderchangingtheiftoaswitch,asfollows:
vara='1',result='';switch(a){case1:result='Number1';break;case'1':result='String1';break;default:result='Idon'tknow';break;}
Theresultafterexecutingthisis"String1".Let'sseewhatthepartsofaswitchare:
Theswitchstatement.Anexpressioninparentheses.Theexpressionmostoftencontainsavariable,butcanbeanythingthatreturnsavalue.Anumberofcaseblocksenclosedincurlybrackets.Eachcasestatementisfollowedbyanexpression.Theresultoftheexpressioniscomparedtotheexpressionfoundaftertheswitchstatement.Iftheresultofthecomparisonistrue,thecodethatfollowsthecolonafterthecaseisexecuted.Thereisanoptionalbreakstatementtosignaltheendofthecaseblock.Ifthisbreakstatementisreached,theswitchstatementisalldone.Otherwise,ifthebreakismissing,theprogramexecutionentersthenextcaseblock.There'sanoptionaldefaultcasemarkedwiththedefaultstatementandfollowedbyablockofcode.Thedefaultcaseisexecutedifnoneofthepreviouscasesevaluatedtotrue.
Inotherwords,thestep-by-stepproceduretoexecuteaswitchstatementisasfollows:
1. Evaluate the switch expression found in parentheses; remember it.2. Move to the first case and compare its value with the one fromStep 1.3. IfthecomparisoninStep2returnstrue,executethecodeinthecaseblock.4. Afterthecaseblockisexecuted,ifthere'sabreakstatementattheendofit,exitswitch.
5. Ifthere'snobreakorStep2returnedfalse,moveontothenextcaseblock.6. Repeatsteps2to5.7. Ifyouarestillhere(noexitinStep4),executethecodefollowingthedefaultstatement.
Tip
Indentthecodethatfollowsthecaselines.Youcanalsoindentcasefromtheswitch,butthatdoesn'tgiveyoumuchin termsofreadability.
Don'tforgettobreak
Sometimes,youmaywanttoomitthebreakintentionally,butthat'srare.It'scalledafall-throughandshouldalwaysbedocumentedbecauseitmaylooklikeanaccidentalomission.Ontheotherhand,sometimesyoumaywanttoomitthewholecodeblockfollowingacaseandhavetwocasessharingthesamecode.Thisisfine,butitdoesn'tchangetherulethatifthere'scodethatfollowsacasestatement,thiscodeshouldendwithabreak.Intermsofindentation,aligningthebreakwiththecaseorwiththecodeinsidethecaseisapersonalpreference;again,beingconsistentiswhatmatters.
Usethedefaultcase.Thishelpsyoumakesurethatyoualwayshaveameaningfulresultaftertheswitchstatement,evenifnoneofthecasesmatchesthevaluebeingswitched.
Loops
Theif...elseandswitchstatementsallowyourcodetotakedifferentpaths,asifyou'reatacrossroad,anddecidewhichwaytogodependingonacondition.Loops,ontheotherhand,allowyourcodetotakeafewroundaboutsbeforemergingbackintothemainroad.Howmanyrepetitions?Thatdependsontheresultofevaluatingaconditionbefore(orafter)eachiteration.
Let'ssayyouare(yourprogramexecutionis)travelingfromAtoB.Atsomepoint,youwillreachaplacewhereyouhavetoevaluateacondition,C.TheresultofevaluatingCtellsyouwhetheryoushouldgointoaloop,L.YoumakeoneiterationandarriveatCagain.Then,youevaluatetheconditiononceagaintoseeifanotheriterationisneeded.Eventually,youmoveonyourwaytoB:
Aninfiniteloopiswhentheconditionisalwaystrue,andyourcodegetsstuckintheloopforever.Thisis,ofcourse,alogicalerror,andyoushouldlookoutforsuchscenarios.
InJavaScript,thefollowingarethefourtypesofloops:
whileloopsdo-whileloopsforloopsfor-inloops
While loops
Thewhileloopsarethesimplesttypeofiteration.Theylooklikethefollowing:
vari=0;while(i<10){i++;
}
Thewhilestatementisfollowedbyaconditioninparenthesesandacodeblockincurlybrackets.Aslongastheconditionevaluatestotrue,thecodeblockisexecutedoverandoveragain.
Do-whileloops
Thedo...whileloopsareaslightvariationofwhileloops.Anexampleisshownasfollows:
vari=0;do{
i++;}while(i<10);
Here,thedostatementisfollowedbyacodeblockandaconditionaftertheblock.Thismeansthatthecodeblockisalwaysexecuted,atleastonce,beforetheconditionisevaluated.
Ifyouinitializeito11insteadof0inthelasttwoexamples,thecodeblockinthefirstexample(thewhileloop)willnotbeexecuted,andiwillstillbe11attheend,whileinthesecond(thedo...whileloop),thecodeblockwillbeexecutedonceandiwillbecome12.
Forloops
Theforloopisthemostwidelyusedtypeofloop,andyoushouldmakesureyou'recomfortablewiththisone.Itrequiresjustalittlebitmoreintermsofsyntax:
InadditiontotheCconditionandtheLcodeblock,youhavethefollowing:
Initialization:Thisisthecodethatisexecutedbeforeyouevenentertheloop(markedwith0inthediagram)Increment:Thisisthecodethatisexecutedaftereveryiteration(markedwith++inthe
diagram)
Thefollowingisthemostwidelyusedforlooppattern:
Intheinitializationpart,youcandefineavariable(orsettheinitialvalueofanexistingvariable),mostoftencallediIntheconditionpart,youcancompareitoaboundaryvalue,suchasi<100Intheincrementpart,youcanincreaseiby1,suchasi++
Here'sanexample:
varpunishment='';for(vari=0;i<100;i++){punishment+='Iwillneverdothisagain,';}
Allthreeparts(initialization,condition,andincrement)cancontainmultipleexpressionsseparatedbycommas.Sayyouwanttorewritetheexampleanddefinethevariablepunishmentinsidetheinitializationpartoftheloop:
for(vari=0,punishment='';i<100;i++){punishment+='Iwillneverdothisagain,';}
Canyoumovethebodyoftheloopinsidetheincrementpart?Yes,youcan,especiallyasit'saone-liner.Thisgivesyoualoopthatlooksalittleawkward,asithasnobody.Notethatthisisjustanintellectualexercise;it'snotrecommendedthatyouwriteawkward-lookingcode:
for(vari=0,punishment='';i<100;i++,punishment+='Iwillneverdothisagain,'){
//nothinghere
}
Thesethreepartsarealloptional.Here'sanotherwayofrewritingthesameexample:
vari=0,punishment='';for(;;){punishment+='Iwillneverdothisagain,';if(++i==100){break;}}
Althoughthelastrewriteworksexactlythesamewayastheoriginal,it'slongerandhardertoread.It'salsopossibletoachievethesameresult usingawhileloop.But,theforloopsmakethe
codetighterandmorerobustbecausethemeresyntaxoftheforloopmakesyouthinkaboutthethreeparts(initialization,condition,andincrement),andthushelpsyoureconfirmyourlogicandavoidsituationssuchasbeingstuckinaninfiniteloop.
The for loops can be nested within each other. Here's an example of a loop that is nested insideanother loop and assembles a string containing ten rows and ten columns of asterisks. Think of ibeing the row and j being the column of an image:
varres='\n';for(vari=0;i<10;i++){for(varj=0;j<10;j++){res+='*';}res+='\n';}
Theresultisastring,asshownhere:
"****************************************************************************************************"
Here'sanotherexamplethatusesnestedloopsandamodulooperationtodrawasnowflake-likeresult:
varres='\n',i,j;for(i=1;i<=7;i++){for(j=1;j<=15;j++){res+=(i*j)%8?'':'*';}res+='\n';}
Theresultisasfollows:
"****************
*"
For...inloops
Thefor...inloopisusedtoiterateovertheelementsofanarray,oranobject,asyou'llseelater.Thisisitsonlyuse;itcannotbeusedasageneral-purposerepetitionmechanismtoreplacefororwhile.Let'sseeanexampleofusingafor-intoloopthroughtheelementsofanarray.But,bearinmindthatthisisforinformationalpurposesonly,asfor...inismostlysuitableforobjectsandtheregularforloopshouldbeusedforarrays.
Inthisexample,youcaniterateoveralloftheelementsofanarrayandprintouttheindex(thekey)andthevalueofeachelement,forexample:
//exampleforinformationonly//for-inloopsareusedforobjects//regularforisbettersuitedforarrays
vara=['a','b','c','x','y','z'];
varresult='\n';
for(variina){result += 'index: ' + i + ', value: ' + a[i] + '\n';
}Theresultis:"index:0,value:aindex:1,value:bindex:2,value:cindex:3,value:xindex:4,value:yindex:5,value:z"
CommentsOne last thing for this chapter-comments. Inside your JavaScript program, you can put comments.TheseareignoredbytheJavaScriptengineanddon'thaveanyeffectonhowtheprogramworks.Buttheycanbeinvaluablewhenyourevisityourcodeafterafewmonths,ortransferthecodetosomeoneelseformaintenance.
Thefollowingtwotypes ofcommentsareallowed:
Single line comments start with // and end at the end of the line.Multiline comments start with /* and end with */ on the same line or any subsequent line.Note that any code in between the comment start and the comment end is ignored.
Someexamplesareasfollows:
//beginningofline
vara=1;//anywhereontheline
/*multi-linecommentonasingleline*/
/*commentthatspansseverallines*/
There are even utilities, such as JSDoc and YUIDoc, that can parse your code and extractmeaningfuldocumentationbasedonyourcomments.
Exercises1. Whatistheresultofexecutingeachoftheselinesintheconsole?Why?
>vara;typeofa;>vars='1s';s++;>!!"false";>!!undefined;
> typeof -Infinity;>10%"0";>undefined==null;>false==="";>typeof"2E+2";>a=3e+3;a++;
2. Whatisthevalueofvafterthefollowing?
>varv=v||10;
Experimentbyfirstsettingvto100,0,ornull.3. Writeasmallprogramthatprintsoutthemultiplicationtable.Hint:usealoopnestedinsideanotherloop.
SummaryIn this chapter, you learned a lot about the basic building blocks of a JavaScript program. Nowyouknowthefollowingprimitivedatatypes:
NumberStringBooleanUndefinedNull
Youalsoknowquiteafewoperators,whichareasfollows:
Arithmeticoperators:+,-,*,/,and%Incrementoperators:++and-Assignmentoperators:=,+=,-=,*=,/=,and%=Specialoperators:typeofanddeleteLogicaloperators:&&,||,and!Comparisonoperators:==,===,!=,!==,<,>,>=,and<=Theternaryoperator:?
Thenyoulearnedhowtousearraystostoreandaccessdata,andfinallyyousawdifferentwaystocontroltheflowofyourprogramusingconditions(if...elseorswitch)andloops(while,do...while,for,andfor...in).
Thisisquiteabitofinformation;giveyourselfawell-deservedpatonthebackbeforedivingintothenextchapter.Morefuniscomingup!
Chapter 3. FunctionsMastering functions is an important skill when you learn any programming language, and evenmoresowhenitcomestoJavaScript.ThisisbecauseJavaScripthasmanyusesforfunctions,andmuchofthelanguage'sflexibilityandexpressivenesscomesfromthem.Wheremostprogramminglanguageshaveaspecialsyntaxforsomeobject-orientedfeatures,JavaScriptjustusesfunctions.Thischapterwillcoverthefollowingtopics:
HowtodefineanduseafunctionPassingargumentstoafunctionPredefinedfunctionsthatareavailabletoyouforfreeThescopeofvariablesinJavaScriptTheconceptthatfunctionsarejustdata,albeitaspecialtypeofdata
Understandingthesetopicswillprovideasolidbasethatwillallowyoutodiveintothesecondpartofthechapter,whichshowssomeinterestingapplicationsoffunctions,asfollows:
UsinganonymousfunctionsCallbacksImmediate(self-invoking)functionsInner functions (functions defined inside other functions)FunctionsthatreturnfunctionsFunctionsthatredefinethemselvesClosures
What is a function?Functions allow you to group together a code, give it a name, and reuse it later, addressing it bythenameyougaveit.Let'sconsiderthefollowingcodeasanexample:
functionsum(a,b){varc=a+b;returnc;}
Thepartsthatmakeupafunctionareshownasfollows:
Thefunctionkeyword.Thenameofthefunction;inthiscase,sum.Thefunctionparameters;inthiscase,aandb.Afunctioncantakeanynumberofparameters,ornoparameters,separatedbycommas.Acodeblock,alsocalledthebodyofthefunction.Thereturnstatement.Afunctionalwaysreturnsavalue.Ifitdoesn'treturnavalueexplicitly,itimplicitlyreturnsthevalueundefined.
Notethatafunctioncanonlyreturnasinglevalue.Ifyouneedtoreturnmorevalues,youcansimplyreturnanarraythatcontainsallofthevaluesyouneedaselementsofthisarray.
Theprecedingsyntaxiscalledafunctiondeclaration.It'sjustoneofthewaystocreateafunctioninJavaScript,andmorewaysarecomingup.
Callingafunction
Inordertomakeuseofafunction,youwillneedtocallit.Youcancallafunctionsimplyusingitsname,optionally,followedbyanynumberofvaluesinparentheses.Toinvokeafunctionisanotherwayofsayingtocall.
Let'scallthesum()function,passingtwoargumentsandassigningthevaluethatthefunctionreturnstothevariableresult:
>varresult=sum(1,2);>result;3
Parameters
Whendefiningafunction,youcanspecifywhatparametersthefunctionexpectstoreceivewhenit'scalled.Afunctionmaynotrequireanyparameters,butifitdoes,andyouforgettopassthem,JavaScriptwillassigntheundefinedvaluetotheonesyouskipped.Inthenextexample,thefunctioncallreturnsNaNbecauseittriestosum1andundefined:
>sum(1);NaN
Technicallyspeaking,thereisadifferencebetweenparametersandarguments,althoughthetwoareoftenusedinterchangeably.Parametersaredefinedtogetherwiththefunction,whileargumentsarepassedtothefunctionwhenit'scalled.Considerthefollowingexample:
>functionsum(a,b){returna+b;}>sum(1,2);
Here,aandbareparameters,while1and2arearguments.
JavaScriptisnotpickyatallwhenitcomestoacceptingarguments.Ifyoupassmorethanthefunctionexpects,theextraoneswillbesilentlyignored,asshowninthefollowingexample:
> sum(1, 2, 3, 4, 5);3
What'smore,youcancreatefunctionsthatareflexibleaboutthenumberofparameterstheyaccept.Thisispossiblethankstothespecialvalueargumentsthatarecreatedautomaticallyinsideeachfunction.Here'safunctionthatsimplyreturnswhateverargumentsarepassedtoit:
>functionargs(){returnarguments;}>args();[]>args(1,2,3,4,true,'ninja');[1,2,3,4,true,"ninja"]
Usingarguments,youcanimprovethesum()functiontoacceptanynumberofargumentsandaddthemallup,asshowninthefollowingexample:
functionsumOnSteroids(){vari,res=0,number_of_params=arguments.length;for(i=0;i<number_of_params;i++){res+=arguments[i];}
returnres;}
Ifyoutestthisfunctionbycallingitwithadifferentnumberofarguments,orevennoneatall,youcanverifythatitworksasexpected,asyoucanseeinthefollowingexample:
>sumOnSteroids(1,1,1);3>sumOnSteroids(1,2,3,4);10>sumOnSteroids(1,2,3,4,4,3,2,1);20>sumOnSteroids(5);5
> sumOnSteroids();0
Thearguments.lengthexpressionreturnsthenumberofargumentspassedwhenthefunctionwascalled.Don'tworryifthesyntaxisunfamiliar,we'llexamineitindetailinthenextchapter.You'llalsoseethatargumentsisnotanarray(althoughitsurelookslikeone),butanarray-likeobject.
ES6introducesseveralimportantimprovementsaroundfunctionparameters.ES6functionparameterscannowhavedefaultvalues,restparameters,andallowsdestructuring.Thenextsectiondiscusseseachoftheseconceptsindetail.
Default parametersFunction parameters can be assigned default values. While calling the function, if a parameter isomitted,thedefaultvalueassignedtotheparameterisused:
functionrender(fog_level=0,spark_level=100){console.log(`FogLevel:${fog_level}andspark_level:${spark_level}`)}render(10);//FogLevel:10andspark_level:100
Inthisexample,weareomittingthespark_levelparameter,andhencethedefaultvalueassignedtotheparameterisused.Itisimportanttonotethatundefinedisconsideredasanabsenceofparametervalue;considerthefollowinglineofcode,forexample:
render(undefined,10);//FogLevel:0andspark_level:10
Whileprovidingdefaultvaluesofparameters,itispossibletorefertootherparametersaswell:
functiont(fog_level=1,spark_level=fog_level){console.log(`FogLevel:${fog_level}andspark_level:${spark_level}`)//FogLevel:10andspark_level:10}functions(fog_level=10,spark_level=fog_level*10){console.log(`FogLevel:${fog_level}andspark_level:
${spark_level}`)//FogLevel:10andspark_level:100}t(10);s(10);
Defaultparametershavetheirownscope;thisscopeissandwichedbetweentheouterfunctionscopeandtheinnerscopeofthefunction.Iftheparameterisshadowedbyavariableininnerscope,surprisingly,theinnervariableisnotavailable.Thefollowingexamplewillhelpexplainthis:
varscope="outer_scope";functionscoper(val=scope){varscope="inner_scope";console.log(val);//outer_scope}scoper();
Youmayexpectvaltogetshadowedbytheinnerdefinitionofthescopevariable,butasthedefaultparametershavetheirownscope,thevalueassignedtovalisunaffectedbytheinnerscope.
Rest parametersES6 introduces rest parameters. Rest parameters allow us to send an arbitrary number ofparameterstoafunctionintheformofanarray.Restparametercanonlybethelastoneinthelistofparameters,andtherecanonlybeonerestparameter.Puttingarestoperator(...)beforethelastformalparameterindicatesthatparameterisarestparameter.Thefollowingexampleshowsaddingarestoperatorbeforethelastformalparameter:
functionsayThings(tone,...quotes){console.log(Array.isArray(quotes)); //true
console.log(`In${tone}voice,Isay${quotes}`)}sayThings("MorganFreeman","Somethingserious","ImplodingUniverse","Amen");//InMorganFreemanvoice,IsaySomethingserious,ImplodingUniverse,Amen
Thefirstparameterpassedtothefunctionisreceivedintone,whiletherestoftheparametersarereceivedasanarray.Variablearguments(var-args)havebeenpartofseveralotherlanguagesandawelcomeeditiontoES6.Restparameterscanreplacetheslightlycontroversialargumentsvariable.Themajordifferencebetweenrestparametersandtheargumentsvariableisthattherestparametersarerealarrays.Allarraymethodsareavailabletorestparameters.
Spread operatorsA spread operator looks exactly like a rest operator but performs the exact opposite function.Spreadoperatorsareusedwhileprovidingargumentswhilecallingafunctionordefininganarray.Thespreadoperatortakesanarrayandsplitsitselementintoindividualvariables.Thefollowingexampleillustrateshowthespreadoperatorprovidesamuchclearersyntaxwhilecallingfunctionsthattakeanarrayasanargument:
functionsumAll(a,b,c){returna+b+c}varnumbers=[6,7,8]//ES5wayofpassingarrayasanargumentofafunction
console.log(sumAll.apply(null,numbers)); //21//ES6Spreadoperatorconsole.log(sumAll(...numbers))//21
InES5,itiscommontousetheapply()function whenpassinganarrayasanargumenttoafunction.Intheprecedingexample,wehaveanarrayweneedtopasstoafunctionwherethefunctionacceptsthreevariables.TheES5methodofpassinganarraytothisfunctionusestheapply()function,wherethesecondargumentallowsanarraytobepassedtothefunctionbeingcalled.ES6spreadoperatorsgiveamuchcleanerandprecisewaytodealwiththissituation.WhilecallingsumAll(),weusethespreadoperator(...)andpassthenumbersarraytothefunctioncall.Thearrayisthensplitintoindividualvariables-a,b,andc.
Spread operators improve the capabilities of arrays in JavaScript. If you want to create an arraythatismadeupofanotherarray,theexistingarraysyntaxdoesnotsupportthis.Youhavetousepush,splice,andconcattoachievethis.However,usingspreadoperators,thisbecomestrivial:
varmidweek=['Wed','Thu'];varweekend=['Sat','Sun'];varweek=['Mon','Tue',...midweek,'Fri',...weekend];//["Mon","Tue","Wed","Thu","Fri","Sat","Sun"]console.log(week);
Intheprecedingexample,weareconstructingaweekarrayusingtwoarrays,midweekandweekend,usingthespreadoperator.
Predefinedfunctions
ThereareanumberoffunctionsthatarebuiltintotheJavaScriptengineandareavailableforyoutouse.Let'stakealookatthem.Whiledoingso,you'llhaveachancetoexperimentwithfunctions,theirargumentsandreturnvalues,andbecomecomfortableworkingwithfunctions.Thefollowingisalistofthebuilt-infunctions:
parseInt()parseFloat()isNaN()isFinite()encodeURI()decodeURI()encodeURIComponent()decodeURIComponent()eval()
Note
Theblackboxfunction
Often,whenyouinvokefunctions,yourprogramdoesn'tneedtoknowhowthesefunctionsworkinternally.Youcanthinkofafunctionasablackbox,giveitsomevalues(asinputarguments),andthentaketheoutputresultitreturns.Thisistrueforanyfunction-onethat'sbuiltintotheJavaScriptengine,onethatyoucreate,oronethataco-workerorsomeoneelsecreated.
parseInt()
TheparseInt()functiontakesanytypeofinput(mostoftenastring)and triestomakeanintegeroutofit.Ifitfails,itreturnsNaN,asshowninthefollowingcode:
>parseInt('123');123>parseInt('abc123');NaN>parseInt('1abc23');1>parseInt('123abc');123
The function accepts an optional second parameter, which is the radix, telling the function whattypeofnumbertoexpect-decimal,hexadecimal,binary,andsoon.Forexample,tryingtoextractadecimalnumberoutoftheFFstringmakesnosense,sotheresultisNaN,butifyoutryFFasahexadecimal,thenyouget255,asshowninthefollowingpieceofcode:
>parseInt('FF',10);NaN
>parseInt('FF',16);255
Anotherexamplewouldbeparsingastringwithabase10(decimal)andbase8(octal):
> parseInt('0377', 10);377>parseInt('0377',8);255
IfyouomitthesecondargumentwhencallingparseInt(),thefunctionwillassume10(adecimal),withthefollowingexceptions:
Ifyoupassastringbeginningwith0x,thentheradixisassumedtobe16(ahexadecimalnumberisassumed).Ifthestringyoupassstartswith0,thefunctionassumesradix8(anoctalnumberisassumed).Considerthefollowingexamples:
>parseInt('377');377>console.log(0o377);255>parseInt('0x377');887
Thesafestthingtodoistoalwaysspecifytheradix.Ifyouomittheradix,yourcodewillprobablystillworkin99percentofcases(becausemostoftenyouparsedecimals);however,everyonceinawhile,itmightcauseyouabitofhairlosswhiledebuggingsomeedgecases.Forexample,imagineyouhaveaformfieldthatacceptscalendardaysormonthsandtheusertypes06or08.
Note
ECMAScript5removestheoctalliteralvaluesandavoidstheconfusionwithparseInt()andunspecifiedradix.
parseFloat()
TheparseFloat()functionissimilartotheparseInt()function,butitalsolooksfordecimalswhentryingtofigureoutanumberfromyourinput.Thisfunctiontakesonlyoneparameter,whichisasfollows:
>parseFloat('123');123>parseFloat('1.23');1.23>parseFloat('1.23abc.00');1.23>parseFloat('a.bc1.23');
NaN
AswithparseInt(),parseFloat()givesupatthefirstoccurrenceofanunexpectedcharacter,eventhoughtherestofthestringmighthaveusablenumbersinit:
>parseFloat('a123.34');NaN>parseFloat('12a3.34');12
TheparseFloat()functionunderstandsexponentsintheinput(unlikeparseInt()):
>parseFloat('123e-2');1.23>parseFloat('1e10');10000000000>parseInt('1e10');1
isNaN()
UsingisNaN(),youcancheckifaninputvalueisavalidnumberthatcan safelybeusedinarithmeticoperations.ThisfunctionisalsoaconvenientwaytocheckwhetherparseInt(),parseFloat(),oranyarithmeticoperationsucceeded:
>isNaN(NaN);true>isNaN(123);false>isNaN(1.23);false>isNaN(parseInt('abc123'));true
Thefunctionwillalsotrytoconverttheinputtoanumber:
>isNaN('1.23');false>isNaN('a1.23');true
TheisNaN()functionisusefulbecausethespecialvalueNaNisnotequaltoanything,includingitself.Inotherwords,NaN===NaNisfalse.So,NaNcannotbeusedtocheckifavalueisavalidnumber.
isFinite()
TheisFinite()functioncheckswhethertheinputisanumberthatisneitherInfinitynorNaN:
>isFinite(Infinity);false
>isFinite(-Infinity);false>isFinite(12);true>isFinite(1e308);true>isFinite(1e309);false
Ifyouarewonderingabouttheresultsreturnedbythelasttwocalls,rememberfromthepreviouschapterthatthebiggestnumberinJavaScriptis1.7976931348623157e+308,so1e309iseffectivelyinfinity.
Encode/decodeURIs
InaUniformResourceLocator(URL)oraUniformResourceIdentifier(URI),somecharactershavespecialmeanings.Ifyouwanttoescapethosecharacters,youcanusetheencodeURI()orencodeURIComponent()functions.ThefirstonewillreturnausableURL,whilethesecondoneassumesyou'reonlypassingapartoftheURL,suchasaquerystringforexample,andwillencodeallapplicablecharacters,asfollows:
>varurl='http://www.packtpub.com/script.php?q=thisandthat';>encodeURI(url);"http://www.packtpub.com/script.php?q=this%20and%20that">encodeURIComponent(url);"http%3A%2F%2Fwww.packtpub.com%2Fscript.php%3Fq%3Dthis%20and%20that"
TheoppositesofencodeURI()andencodeURIComponent()aredecodeURI()anddecodeURIComponent(),respectively.
Sometimes,inlegacycode,youmightseethefunctionsescape()andunescape()usedtoencodeanddecodeURLs,butthesefunctionshavebeendeprecated;theyencodedifferentlyandshouldnotbeused.
eval()
Theeval()functiontakesastringinputandexecutesitasaJavaScriptcode,asfollows:
>eval('varii=2;');>ii;2
So,eval('varii=2;')isthesameasvarii=2;
Theeval()functioncanbeusefulsometimes,butitshouldbeavoidedifthereareotheroptions.Mostofthetime,therearealternatives,andinmostcases,thealternativesaremoreelegantandeasiertowriteandmaintain.EvalisevilisamantrayoucanoftenhearfromseasonedJavaScriptprogrammers.Thedrawbacksofusingeval()areasfollows:
Security:JavaScriptispowerful,whichalsomeansitcancausedamage.Ifyoudon'ttrustthesourceoftheinputyoupasstoeval(),justdon'tuseit.Performance:It'sslowertoevaluatelivecodethantohavethecodedirectlyinthescript.
Abonus-thealert()function
Let'stakealookatanothercommonfunction-alert().It'snotpartofthecoreJavaScript(it'snowheretobefoundintheECMAspecification),butit'sprovidedbythehostenvironment-thebrowser.Itshowsastringoftextinamessagebox.Itcanalsobeusedasaprimitivedebuggingtool,althoughthedebuggersinmodernbrowsersaremuchbettersuitedforthispurpose.
Here'sascreenshotshowingtheresultofexecutingthealert("HiThere")code:
Beforeusingthisfunction,bearinmindthatitblocksthebrowserthread,meaningthatnoothercodewillbeexecuteduntiltheuserclosesthealert.IfyouhaveabusyAjax-typeapplication,it'sgenerallynotagoodideatousealert().
Scope of variablesIt's important to note, especially if you have come to JavaScript from another language, thatvariablesinJavaScriptarenotdefinedinablockscope,butinafunctionscope.Thismeansthatifavariableisdefinedinsideafunction,it'snotvisibleoutsideofthefunction.However,ifit'sdefinedinsideaniforaforcodeblock,it'svisibleoutsidetheblock.Thetermglobalvariablesdescribesvariablesyoudefineoutsideofanyfunction(intheglobalprogramcode),asopposedtolocalvariables,whicharedefinedinsideafunction.Thecodeinsideafunctionhasaccesstoallglobalvariablesaswellastoitsownlocalones.
Inthenextexample:
Thef()functionhasaccesstotheglobalvariableOutsidethef()function,thelocalvariable doesn'texist
varglobal=1;functionf(){varlocal=2;global++;returnglobal;}
Let'stestthis:
>f();2>f();3>local;ReferenceError:localisnotdefined
It'salsoimportanttonotethatifyoudon'tusevartodeclareavariable,thisvariableisautomaticallyassigneda globalscope.Let'sseeanexample:
Whathappened?Thef()functioncontainsthelocalvariable.Beforecallingthefunction,thevariabledoesn'texist.Whenyoucallthefunctionforthefirsttime,thelocalvariableiscreatedwithaglobalscope.Then,ifyouaccessthelocalvariableoutsidethefunction,itwillbeavailable.
Note
Bestpracticetips
Minimizethenumberofglobalvariablesinordertoavoidnamingcollisions.Imaginetwopeopleworkingontwodifferentfunctionsinthesamescript,andtheybothdecidetousethesamenamefortheirglobalvariable.Thiscouldeasilyleadtounexpectedresultsandhard-to-findbugs.Alwaysdeclareyourvariableswiththevarstatement.Considerasinglevarpattern.Defineallvariablesneededinyourfunctionattheverytopofthefunctionsoyouhaveasingleplacetolookforvariablesand,hopefully,preventaccidentalglobals.
Variablehoisting
Here'saninterestingexamplethatshowsanimportantaspectoflocalversusglobalscoping:
var a = 123;
functionf(){alert(a);vara=1;alert(a);}
f();
Youmightexpectthatthefirstalert()functionwilldisplay123(thevalueoftheglobalvariablea)andthesecondwilldisplay1(thelocalvariablea).But,thisisnotthecase.Thefirstalertwillshowundefined.Thisisbecause,insidethefunction,thelocalscopeismoreimportantthantheglobalscope.So,alocalvariableoverwrites anyglobalvariablewiththesamename.Atthetimeofthefirstalert(),theavariablewasnotyetdefined(hencetheundefinedvalue),butitstillexistedinthelocalspaceduetothespecialbehaviorcalledhoisting.
WhenyourJavaScriptprogramexecutionentersanewfunction,allthevariablesdeclaredanywhereinthefunctionaremoved,elevated,orhoistedtothetopofthefunction.Thisisanimportantconcepttokeepinmind.Further,onlythedeclarationishoisted,meaningonlythepresenceofthevariableismovedtothetop.Anyassignmentsstaywheretheyare.Intheprecedingexample,thedeclarationofthelocalvariableawashoistedtothetop.Onlythedeclarationwashoisted, butnottheassignmentto1.It'sasifthefunctionwaswritteninthefollowingway:
vara=123;
functionf(){vara;//sameas:vara=undefined;alert(a);//undefineda=1;alert(a);//1}
Youcanalsoadoptthesinglevarpatternmentionedpreviouslyinthebestpracticesection.Inthiscase,you'llbedoingasortofmanualvariablehoistingtopreventconfusionwiththeJavaScripthoistingbehavior.
Block scopeES6 provides additional scope while declaring variables. We looked at function scope and how itaffectsvariablesdeclaredwiththevarkeyword.IfyouarecodinginES6,blockscopewillmostlyreplaceyourneedtousevariablesdeclaredusingvar.Although,ifyouarestillwithES5,wewantyoutomakesurethatyoulookathoistingbehaviorcarefully.
ES6introducestheletandconstkeywordsthat allowustodeclarevariables.
Variablesdeclaredwithletareblock-scoped.Theyexistonlywithinthe currentblock.Variablesdeclaredwithvararefunctionscoped,aswesawearlier.Thefollowingexampleillustratestheblockscope:
vara=1;{leta=2;console.log(a);//2}console.log(a);//1
Thescopebetweenanopeningbrace'{'andaclosingbrace'}'isablock.IfyouarecomingfromabackgroundinJavaorC/C++,theconceptofablockscopewillbeveryfamiliartoyou.Inthoselanguages,programmersintroducedblocksjusttodefineascope.InJavaScript,however,therewasaneedtoidiomaticallyintroduceblocksastheydidn'thaveascopeassociatedtoit.However,ES6allowsyoutocreateblock-scopedvariablesusingtheletkeyword.Asyoucanseeintheprecedingexample,variableacreatedinsidetheblockisavailablewithintheblock.Whiledeclaringblock-scopedvariables,itisgenerallyrecommendedtoaddtheletdeclarationatthetopoftheblock.Let'slookatanotherexampletoclearlydistinguishfunctionandblockscope:
functionswap(a,b){//<--functionscopestartshereif(a>0&&b>0){//<--blockscopestartsherelettmp=a;a=b;b=tmp;}//<--blockscopeendshereconsole.log(a,b);
console.log(tmp); // tmp is not defined as it is availableonlyintheblockscopereturn[a,b];}swap(1,2);
Asyoucansee,tmpisdeclaredwithletandisavailableonlyintheblockinwhichitwasdefined.Forallpracticalpurposes,youshouldmaximizeyouruseofblock-scopedvariables.Unlessthereissomethingveryspecificyouaretryingtodothatmakesitnecessaryforyoutouse
vardeclarations,makesureyoupreferblockscopedvariables.However,incorrectlyusingtheletkeywordcancauseacoupleofproblems.First,youcannotredeclarethesamevariablewithinthesamefunctionorblockscopeusingtheletkeyword:
functionblocker(x){if(x){letf;letf;//duplicatedeclaration"f"}}
InES6,variablesdeclaredbytheletkeywordarehoistedtoblockscope.However,referencingthevariablebeforeitsdeclarationisanerror.
AnotherkeywordintroducedinES6isconst.Avariabledeclaredwiththeconstkeywordcreatesaread-onlyreferencetoavalue.Thisdoesnotmeanthatthevalueheldbythereferenceisimmutable. However, the variable identifier cannot be reassigned. Constants are block-scopedjustlikevariablescreatedusingtheletkeyword.Also,youhavetoassignavaluetothevariablewhiledeclaringthem.
Althoughitsoundslikeitdoes,consthasnothingtodowithimmutablevalues.Constantscreateimmutablebinding.Thisisanimportantdistinctionandneedstobeunderstoodcorrectly.Let'sconsiderthefollowingexample:
constcar={}car.tyres=4
Thisisavalidcode;hereweareassigningvalue {}toaconstantcar.Onceassigned,thisreferencecannotbechanged.InES6,youshoulddothefollowing:
Useconstwherepossible.Usethemforallvariableswhosevaluesdon'tchange:
Uselet
Avoidvar.
Functions are dataFunctions in JavaScript are actually data. This is an important concept that we'll need later on.Thismeansthatyoucancreateafunctionandassignittoavariable,asfollows:
varf=function(){return1;};
Thiswayofdefiningafunctionissometimesreferredtoasfunctionliteralnotation.
Thefunction(){return1;}partisafunctionexpression.Afunctionexpressioncanoptionallyhaveaname,inwhichcaseitbecomesanamedfunctionexpression(NFE).So,thisisalsoallowed,althoughrarelyseeninpractice(andcausesIEtomistakenlycreatetwovariablesintheenclosingscope-fandmyFunc):
varf=functionmyFunc(){return1;};
Asyoucansee,there'snodifferencebetweenanamedfunctionexpressionandafunctiondeclaration. But they are, in fact, different. The only way to distinguish between the two is to lookatthecontextinwhichtheyareused.Functiondeclarationsmayonlyappearinprogramcode(inabodyofanotherfunctionorinthemainprogram).You'llseemanymoreexamplesoffunctionslateroninthebookthatwillclarifytheseconcepts.
Whenyouusethetypeofoperatoronavariablethatholdsafunctionvalue,itreturnsthestring"function"asshowninthefollowingexample:
>functiondefine(){return1;}
>varexpress=function(){return1;};
>typeofdefine;"function"
>typeofexpress;"function"
So,JavaScriptfunctionsaredata,butaspecialkindofdatawiththefollowingtwoimportantfeatures:
TheycontaincodeTheyareexecutable(theycanbeinvoked)
Asyouhaveseenbefore,thewaytoexecuteafunctionisbyaddingparenthesesafteritsname.Asthenextexampledemonstrates,thisworksregardlessofhowthefunctionwasdefined.Intheexample,youcanalsoseehowafunctionistreatedasaregularvalue;itcanbecopiedtoadifferentvariable,asfollows:
>varsum=function(a,b){returna+b;};
>varadd=sum;>typeofadd;function>add(1,2);3
Asfunctionsaredataassignedtovariables,thesamerulesfornamingfunctionsapplyasfornamingvariables-afunctionnamecannotstartwithanumberanditcancontainanycombinationofletters,numbers,theunderscorecharacter,and thedollarsign.
Anonymousfunctions
Asyounowknow,thereexistsafunctionexpressionsyntaxwhereyoucanhaveafunctiondefinedlikethefollowing:
varf=function(a){returna;};
Thisisalsooftencalledananonymousfunction(asitdoesn'thaveaname),especiallywhensuchafunctionexpressionisusedevenwithoutassigningittoavariable.Inthiscase,therecanbetwoelegantusesforsuchanonymousfunctions,whichareasfollows:
Youcanpassananonymousfunctionasaparametertoanotherfunction.Thereceivingfunctioncandosomethingusefulwiththefunctionthatyoupass.Youcandefineananonymousfunctionandexecuteitrightaway.
Let'sseethesetwoapplicationsofanonymousfunctionsinmoredetail.
Callbackfunctions
Asafunctionisjustlikeanyotherdataassignedtoavariable,itcanbedefined,copied,andalsopassedasanargumenttootherfunctions.
Here'sanexampleofafunctionthatacceptstwofunctionsasparameters,executesthem,andreturnsthesumofwhateachofthemreturns:
functioninvokeAdd(a,b){returna()+b();}
Now, let's define two simple additional functions using a function declaration pattern that onlyreturnshardcodedvalues:
functionone(){return1;}functiontwo(){return2;}
Nowyoucanpassthosefunctionstotheoriginalfunction,invokeAdd(),andgetthefollowingresult:
>invokeAdd(one,two);3
Anotherexampleofpassingafunctionasaparameteristouseanonymousfunctions(functionexpressions).Insteadofdefiningone()andtwo(),youcansimplydothefollowing:
>invokeAdd(function(){return1;},function(){return2;});3
Or,youcanmakeitmorereadable,asshowninthefollowingcode:
>invokeAdd(function(){return1;},function(){return2;});3
Or,youcandothefollowing:
>invokeAdd(function(){return1;},function(){
return2;});3
Whenyoupassafunction,A,toanotherfunction,B,andthenBexecutesA,it'softensaidthatAisacallbackfunction.IfAdoesn'thaveaname,thenyoucansaythatit'sananonymouscallbackfunction.
Whenarecallbackfunctionsuseful?Let'sseesomeexamplesthatdemonstratethebenefitsofcallbackfunctions,namely:
Theyletyoupassfunctionswithouttheneedtonamethem,whichmeanstherearefewervariablesfloatingaroundYoucandelegatetheresponsibilityofcallingafunctiontoanotherfunction,whichmeansthere is less code to writeTheycanhelpwithperformancebydeferringtheexecutionorbyunblockingcalls
Callbackexamples
Take a look at this common scenario-you have a function that returns a value, which you then passtoanotherfunction.Inourexample,thefirstfunction,multiplyByTwo(),acceptsthreeparameters,loopsthroughthem,multipliesthembytwo,andreturnsanarraycontainingtheresult.Thesecondfunction,addOne(),takesavalue,addsonetoit,andreturnsit,asfollows:
functionmultiplyByTwo(a,b,c){vari,ar=[];for(i=0;i<3;i++){ar[i]=arguments[i]*2;}returnar;}
functionaddOne(a){returna+1;}
Let'stestthesefunctions:
>multiplyByTwo(1,2,3);[2,4,6]>addOne(100);101
Now,let'ssayyouwanttohaveanarray,myarr,thatcontainsthreeelements,andeachoftheelementsistobepassedthroughbothfunctions.First,let'sstartwithacalltomultiplyByTwo():
>varmyarr=[];>myarr=multiplyByTwo(10,20,30);[20,40,60]
Now,loopthrougheachelement,passingittoaddOne():
>for(vari=0;i<3;i++){myarr[i]=addOne(myarr[i]);}>myarr;[21,41,61]
Asyoucansee,everythingworksfine,butthere'sroomforimprovement.Forexample,thereweretwoloops.Loopscanbeexpensiveiftheygothroughalotofrepetitions.Youcanachievethesameresultwithonlyoneloop.Here'showtomodifymultiplyByTwo()sothatitacceptsacallbackfunctionandinvokesthatcallbackoneveryiteration:
functionmultiplyByTwo(a,b,c,callback){vari,ar=[];for(i=0;i<3;i++){ar[i]=callback(arguments[i]*2);}returnar;}
Usingthemodifiedfunction,alltheworkisdonewithjustonefunctioncall,whichpassesthestartvaluesandthecallbackfunction,asfollows:
>myarr=multiplyByTwo(1,2,3,addOne);[3,5,7]
InsteadofdefiningaddOne(),youcanuseananonymousfunction,thereforesavinganextraglobalvariable:
>multiplyByTwo(1,2,3,function(a){returna+1;});[3,5,7]
Anonymousfunctionsareeasytochangeshouldtheneedarise:
>multiplyByTwo(1,2,3,function(a){returna+2;});[4,6,8]
Immediatefunctions
Sofar,wehavediscussedusinganonymousfunctionsascallbacks.Let'sseeanotherapplicationofananonymousfunction-callingafunctionimmediatelyafterit'sdefined.Here'sanexample:
(function(){alert('boo');}
)();
Thesyntaxmaylookalittlescaryatfirst,butallyoudoissimplyplaceafunctionexpressioninsideparenthesesfollowedbyanothersetofparentheses.Thesecondsetsaysexecutenowandisalsotheplacetoputanyargumentsthatyouranonymousfunctionmightaccept,forexample:
(function(name){alert('Hello'+name+'!');})('dude');
Alternatively,youcanmovetheclosingofthefirstsetofparenthesestotheend.Bothofthesework:
(function(){// ...
}());
//vs.
(function(){//...})();
Onegoodapplicationofimmediate(self-invoking)anonymousfunctionsiswhenyouwanttohavesomeworkdonewithoutcreatingextraglobalvariables.Adrawback,ofcourse,isthatyoucannotexecutethesamefunctiontwice.Thismakesimmediatefunctionsbestsuitedforone-offorinitialization tasks.
Animmediatefunctioncanalsooptionallyreturnavalueifyouneedone.It'snotuncommontoseecodethatlookslikethefollowing:
varresult=(function(){//somethingcomplexwith//temporarylocalvariables...//...//returnsomething;}());
Inthiscase,youdon'tneedtowrapthefunctionexpressioninparentheses;youonlyneedtheparenthesesthatinvokethefunction.So,thefollowingpieceofcodealsoworks:
varresult=function(){//somethingcomplexwith//temporarylocalvariables//returnsomething;}();
Thissyntaxworks,butmaylookslightlyconfusing;withoutreadingtheendofthefunction,youdon'tknowifresultisafunctionorthereturnvalueoftheimmediatefunction.
Inner(private)functions
Bearinginmindthatafunctionisjustlikeanyothervalue,there'snothingthatstopsyoufromdefiningafunctioninsideanotherfunction,here's theexample:
functionouter(param){functioninner(theinput){returntheinput*2;}
return 'The result is ' + inner(param);}
Usingafunctionexpression,thiscanalsobewrittenasfollows:
varouter=function(param){varinner=function(theinput){returntheinput*2;};return'Theresultis'+inner(param);};
Whenyoucalltheglobalouter()function,itwillinternallycallthelocalinner()function.Asinner()islocal,it'snotaccessibleoutsideouter(),soyoucansayit'saprivatefunction:
>outer(2);"Theresultis4">outer(8);"Theresultis16">inner(2);ReferenceError:innerisnotdefined
Thebenefitsofusingprivatefunctionsareasfollows:
Youcankeeptheglobalnamespaceclean,whichislesslikelytocausenamingcollisionsPrivacy-youcanexposeonlythosefunctionstotheoutsideworldthatyoudecide,andkeepthefunctionalitythatisnotmeanttobeconsumedbytherestoftheapplicationtoyourself
Functionsthatreturnfunctions
Asmentionedearlier,afunctionalwaysreturnsa value,andifitdoesn'tdoitexplicitlywithreturn,thenitdoessoimplicitlybyreturningundefined.Afunctioncanreturnonlyonevalue,andthisvaluecanjustaseasilybeanotherfunction,forexample:
functiona(){alert('A!');returnfunction(){alert('B!');
};}
Inthisexample,thea()functiondoesitsjob(saysA!)andreturnsanotherfunctionthatdoessomethingelse(saysB!).Youcanassignthereturnvaluetoavariableandthenusethisvariableasanormalfunction,asfollows:
>varnewFunc=a();>newFunc();
Here,thefirstlinewillalertA!andthesecondwillalertB!.
Ifyouwanttoexecutethereturnedfunctionimmediatelywithoutassigningittoanewvariable,youcansimplyuseanothersetofparentheses.Theendresultwillbethesame:
>a()();
Function,rewritethyself!
Asafunctioncanreturnafunction,youcanusethenewfunctiontoreplacetheoldone.Continuingwiththepreviousexample,youcantakethevaluereturnedbythecalltoa()tooverwritetheactuala() function:
> a = a();
TheprecedinglineofcodealertsA!,butthenexttimeyoucalla()italertsB!.Thisisusefulwhenafunctionhassomeinitialone-offworktodo.Thefunctionoverwritesitselfafterthefirstcallinordertoavoiddoingunnecessaryrepetitiveworkeverytimeit'scalled.
Intheprecedingexample,thefunctionwasredefinedfromtheoutsideandthereturnedvaluewasassignedbacktothefunction.But,thefunctioncanactuallyrewriteitselffromtheinside,asshowninthefollowingexample:
functiona(){alert('A!');a=function(){alert('B!');};}
Ifyoucallthisfunctionforthefirsttime,itwilldothefollowing:
AlertA!(considerthisasbeingtheone-offpreparatorywork)Redefinetheglobalvariableaandassigninganewfunctiontoit
Everysubsequenttimethatthefunctioniscalled,itwillalertB!.
Here'sanotherexamplethatcombinesseveralofthetechniquesdiscussedinthelastfewsectionsofthischapter:
vara=(function(){
functionsomeSetup(){varsetup='done';}
functionactualWork(){alert('Worky-worky');
}
someSetup();returnactualWork;
}());
Fromthisexample,youcannotethefollowingthings:
Youhaveprivatefunctions;someSetup()andactualWork().Youhaveanimmediatefunction:ananonymousfunctionthatcallsitselfusingtheparenthesesfollowingitsdefinition.Thefunctionexecutesforthefirsttime,callssomeSetup(),andthenreturnsareferencetotheactualWorkvariable,whichisafunction.Noticethattherearenoparenthesesinthereturnstatementbecauseyou'rereturningafunctionreference,nottheresultofinvokingthisfunction.Asthewholethingstartswithvara=,thevaluereturnedbytheself-invokedfunctionisassignedtoa.
Ifyouwanttotestyourunderstandingofthetopicsjustdiscussed,answerthefollowingquestions.Whatwilltheprecedingcodealertbeinthefollowingcases:
Itisinitiallyloaded?Youcalla()afterwards?
Thesetechniquescouldbereallyusefulwhenworkinginthebrowserenvironment.Differentbrowserscanhavedifferentwaysofachievingthesameresult.Ifyouknowthatthebrowserfeatureswon'tchangebetweenfunctioncalls,youcanhaveafunctiondeterminethebestwaytodotheworkinthecurrentbrowser,thenredefineitselfsothatthebrowsercapabilitydetectionisdoneonlyonce.You'llseeconcreteexamplesofthisscenariolaterinthisbook.
ClosuresThe rest of the chapter is about closures (what better way to close a chapter?). Closures can be alittlehardtograspinitially,sodon'tfeeldiscouragedifyoudon'tgetitduringthefirstread.Youshouldgothroughtherestofthechapterandexperimentwiththeexamplesonyourown,butifyoufeelyoudon'tfullyunderstandtheconcept,youcancomebacktoitlaterwhenthetopicsdiscussedpreviouslyinthischapterhavehadachancetosinkin.
Beforemovingontoclosures,let'sfirstreviewandexpandontheconceptofscopeinJavaScript.
Scopechain
Asyouknow,inJavaScript,thereisnocurlybracesscope,butthereisafunctionscope.Avariabledefinedinafunctionisnotvisibleoutsidethefunction,butavariabledefinedinacodeblock(forexampleaniforaforloop)isvisibleoutsidetheblock,forexample:
>vara=1;>functionf(){varb=1;returna;
}>f();1>b;ReferenceError:bisnotdefined
Theavariableisintheglobalspace,whilebisinthescopeofthefunctionf().So,wehavethefollowing:
Insidef(),bothaandbarevisibleOutsidef(),aisvisible,butbisnot
If you define an inner()function nested inside outer(), it will have access to variables in itsown scope, plus the scope of its parents. This is known as a scope chain, and the chain can be aslong(deep)asyouneedittobe:
varglobal=1;functionouter(){varouter_local=2;functioninner(){varinner_local=3;returninner_local+outer_local+global;}returninner();}
Let'stestiftheinner()functionhasaccesstoallvariables:
>outer();6
Breakingthechainwithaclosure
Let'sintroduceclosureswithanillustrationandlookatthefollowingcodeandseewhat'shappeningthere:
vara="globalvariable";varF=function(){varb="localvariable";varN=function(){
var c = "inner local";};};
First,thereistheglobalscopeG.Thinkofitastheuniverse,asifitcontainseverything:
Itcancontainglobalvariablessuchasa1anda2andglobalfunctionssuchasF:
Functionshavetheirownprivatespaceandcanuseittostoreothervariables,suchasb,andinnerfunctions,suchasN(forinner).Atsomepoint,youwillendupwithapicturelikethefollowing:
Ifyou'reatpointa,you'reinsidetheglobalspace.Ifyou'reatpointb,whichisinsidethespaceoftheFfunction,thenyouhaveaccesstotheglobalspaceandtotheFspace.Ifyou'reatpointc,whichisinsidetheNfunction,thenyoucanaccesstheglobalspace,theFspace,andtheNspace.Youcannotreachfromatob,becausebisinvisibleoutsideF.But,youcangetfromctobifyouwantorfromNtob.TheinterestingpartisthattheclosureeffecthappenswhensomehowNbreaksoutofFandendsupintheglobalspace.
Whathappensthen?Nisinthesameglobalspaceasa.And,asfunctionsremembertheenvironmentinwhichtheyweredefined,NwillstillhaveaccesstotheFspace,andhence,canaccessb.Thisisinteresting,becauseNiswhereaisandyetNdoeshaveaccesstob,butadoesn't.
Additionally,howdoesNbreakthechain?Bymakingitselfglobal(omittingvar)orbyhavingFdeliver(orreturn)ittotheglobalspace.Let'sseehowthisisdoneinpractice.
Closure #1
Takealookatthefollowingfunction,whichisthesameasbefore,onlyFreturnsNandalsoNreturnsb,towhichithasaccessviathescopechain:
vara="globalvariable";varF=function(){varb="localvariable";varN=function(){varc="innerlocal";
return b;};returnN;};
TheFfunctioncontainsthebvariable,whichislocal,andthereforeinaccessiblefromtheglobalspace:
>b;ReferenceError:bisnotdefined
The N function has access to its private space, to the F() function's space, and to the global space.So, it can see b. As F() is callable from the global space (it's a global function), you can call itand assign the returned value to another global variable. The result - a new global function thathasaccesstotheF()function'sprivatespace:
>varinner=F();>inner();"localvariable"
Closure#2
Thefinalresultofthenextexamplewillbethesameasthepreviousexample,butthewaytoachieveitisalittledifferent.F()doesn'treturnafunction,butinsteaditcreatesanewglobalfunction,inner(),insideitsbody.
Let'sstartbydeclaringa placeholderfortheglobalfunction-to-be.Thisisoptional,butit'salwaysgoodtodeclareyourvariables.Then,youcandefinetheF()functionasfollows:
varinner;//placeholdervarF=function(){varb="localvariable";varN=function(){returnb;};inner=N;};
Now,let'sseewhathappensifyouinvokeF():
>F();
Anewfunction,N(),isdefinedinsideF()andassignedtotheglobalinnerfunction.Duringdefinitiontime,N()wasinsideF(),soithadaccesstotheF()function'sscope.Theinner()functionwillkeepitsaccesstotheF()function'sscope,eventhoughit'spartoftheglobalspace,forexample:
>inner();"localvariable".
Adefinitionandclosure#3
Everyfunctioncanbeconsideredaclosure.Thisisbecauseeveryfunctionmaintainsasecretlinktotheenvironment(thescope)inwhichitwascreated.But,mostofthetime,thisscopeisdestroyedunlesssomethinginterestinghappens(asshownintheprecedingcode)thatcausesittobemaintained.
Basedonwhatyou'veseensofar,youcansaythataclosureiscreatedwhenafunctionkeepsalinktoitsparentscopeevenaftertheparenthasreturned.And,everyfunctionisaclosurebecause,attheveryleast,everyfunctionmaintainsaccesstotheglobalscope,whichisneverdestroyed.
Let'sseeonemoreexampleofaclosure,thistimeusingthefunctionparameters.Functionparameters behave like local variables to this function, but they are implicitly created; you don'tneedtousevarforthem.Youcancreateafunctionthatreturnsanotherfunction,whichinturnreturnsitsparent'sparameter,asfollows:
functionF(param){varN=function(){returnparam;};param++;returnN;}
Youcanusethefunctionasfollows:
>varinner=F(123);
>inner();124
Noticehowparam++wasincrementedafterthefunctionwasdefinedandyet,whencalled,inner()returnedtheupdatedvalue.Thisdemonstratesthatthefunctionmaintainsareferencetothescopewhereitwasdefined,andnottothevariablesandtheirvaluesfoundinthescopeduringthefunctionexecution.
Closuresinaloop
Let'stakealookatacanonicalrookiemistakewhenitcomestoclosures.Itcaneasilyleadtohard-to-spotbugs,becauseonthesurface,everythinglooksnormal.
Let'sloopthreetimes,eachtimecreatinganewfunctionthatreturnstheloopsequencenumber.Thenewfunctionswillbeaddedtoanarrayandthearrayisreturnedattheend.Here'sthefunction:
functionF(){vararr=[],i;for(i=0;i<3;i++){arr[i]=function(){returni;};}returnarr;}
Let'srunthefunction,assigningtheresulttothearrarray:
>vararr=F();
Nowyouhaveanarrayofthreefunctions.Let'sinvokethembyaddingparenthesesaftereacharrayelement.Theexpectedbehavioristoseetheloopsequenceprintedoutas0,1,and2.Let'stry:
>arr[0]();3>arr[1]();3>arr[2]();3
Hmm,notquiteasexpected.Whathappenedhere?Allthreefunctionspointtothesamelocalvariable:i.Why?Thefunctionsdon'tremembervalues,theyonlykeepalink(reference)totheenvironmentwheretheywerecreated.Inthiscase,theivariablehappenstoliveintheenvironmentwherethethreefunctionsweredefined.So,allfunctions,whentheyneedtoaccessthevalue,reachbacktotheenvironmentandfindthemostcurrentvalueofi.Aftertheloop,theivariable'svalueis3.So,allthreefunctionspointtothesamevalue.
Whythreeandnottwoisanothergoodquestiontothinkaboutforbetterunderstandingtheforloop.
So,howdoyouimplementthecorrectbehavior?Theansweristouseanotherclosure,asshowninthefollowingpieceofcode:
functionF(){vararr=[],i;for(i=0;i<3;i++){arr[i]=(function(x){returnfunction(){returnx;};}(i));}returnarr;}
Thisgivesyoutheexpectedresultasfollows:
>vararr=F();>arr[0]();0>arr[1]();1>arr[2]();2
Here,insteadofjustcreatingafunctionthatreturnsi,youpasstheivariable'scurrentvaluetoanotherimmediatefunction.Inthisfunction,ibecomesthelocalvaluex,andxhasadifferentvalueeverytime.
Alternatively,youcanuseanormal(asopposedtoanimmediate)innerfunctiontoachievethesameresult.Thekeyistousethemiddlefunctiontolocalizethevalueofiateveryiteration,asfollows:
functionF(){
functionbinder(x){returnfunction(){returnx;};}
vararr=[],i;for(i=0;i<3;i++){arr[i]=binder(i);}returnarr;}
Getterandsetter
Let'sseetwomoreexamplesofusingclosures.Thefirstoneinvolvesthecreationofthegetterandsetterfunctions.Imaginethatyouhaveavariablethatshouldcontainaspecifictypeofvalueoraspecificrangeofvalues.Youdon'twanttoexposethisvariablebecauseyoudon'twantjustanypartofthecodetobeabletoalteritsvalue.Youcanprotectthisvariableinsideafunctionandprovidetwoadditionalfunctions-onetogetthevalueandonetosetit.Theonethatsetsitcouldcontainsomelogictovalidateavaluebeforeassigningittotheprotectedvariable.Let'smakethevalidationpartsimple(forthesakeofkeepingtheexampleshort)andonlyacceptnumbervalues.
Youcanplaceboththegetterandthesetterfunctionsinsidethesamefunctionthatcontainsthesecretvariablesothattheysharethesamescope:
vargetValue,setValue;
(function () {
varsecret=0;
getValue=function(){returnsecret;};
setValue=function(v){if(typeofv==="number"){secret=v;}};
}());
Inthiscase,thefunctionthatcontainseverythingisanimmediatefunction.ItdefinessetValue()andgetValue()asglobalfunctions,whilethesecretvariableremainslocalandinaccessibledirectly,asshowninthefollowingexample:
>getValue();0>setValue(123);>getValue();123>setValue(false);>getValue();123
Iterator
Thelastclosureexample(alsothelastexampleinthischapter)showstheuseofaclosuretoaccomplishaniteratorfunctionality.
Youalreadyknowhowtoloopthroughasimplearray,buttheremightbecaseswhereyouhaveamorecomplicateddatastructurewithdifferentrulesastowhatthesequenceofvalueshas.Youcanwrapthecomplicatedwho'snextlogicintoaneasy-to-usenext()function.Then,youcansimplycallnext()everytimeyouneedtheconsecutivevalue.
Forthisexample,let'sjustuseasimplearrayandnotacomplexdatastructure.Here'saninitializationfunctionthattakesaninputarrayandalsodefinesasecretpointer,i,thatwillalwayspointtothenextelementinthearray:
functionsetup(x){vari=0;returnfunction(){returnx[i++];};}
Calling the setup() function with a data array will create the next() function for you, asfollows:
>varnext=setup(['a','b','c']);
Fromthereit'seasyandfun;callingthesamefunctionoverandoveragaingivesyouthenextelement,whichisasfollows:
>next();"a"
> next();"b">next();"c"
IIFE versus blocksAs ES5 did not provide block scope, a popular pattern to achieve block scope was to useimmediatelyinvokedfunctionexpressions(IIFE),forexample:
(function(){varblock_scoped=0;}());console.log(block_scoped);//referenceerror
WithES6'ssupportforblockscopes,youcansimplyusealetorconstdeclaration.
Arrow functionsJavaScript uses almost all variations of arrows. With ES6, it introduces a new syntax for writingfunctions.WehavealwayswrittenfunctionexpressionsinJavaScript.ItisidiomatictowritecodelikethisinJavaScript(thisexampleisinjQuery):
$("#submit-btn").click(function(event){validateForm();submitMessage();});
ThisisatypicaljQueryeventhandler.Theeventhandlerclick()functionacceptsafunctionasaparameterandwewillsimplycreateaninlineanonymousfunctionexpressionandpassittotheclickfunction.ThisstyleofwritinganonymousfunctionexpressionsisknownasLambdafunctions.Severalotherlanguagessupportthisfeature.Thoughlambdasaremoreorlessstandardinnewlanguages,JavaScriptwasresponsibleforpopularizingtheirusage.However,thelambdasyntaxinJavaScripthasnotbeenveryconcise.ES6arrowfunctionsfillthatgapandprovideaconcisesyntaxtowritefunctions.
Arrowfunctionprovideamoreconcisesyntaxthanthetraditionalfunctionexpressions;forexample,considerthefollowingpieceofcode:
constnum=[1,2,3]const squares = num.map(function(n){
returnn*n;});console.log(squares);//[1,4,9]
Arrowfunctionssyntaxcansimplifythefunctiontothefollowinglineofcode:
constsquares_6=num.map(n=>n*n)
Asyoucansee,thereisnofunctionorreturnkeywordanywhere.Ifyourfunctionhasonlyoneargument,youwillendupwritingthefunctionasidentifer=>expression.
Whenyouneedmultiplearguments,youneedtowraptheargumentlistinbraces:
Noparameters:()=>{...}Oneparameter:a=>{...}Morethanoneparameters:(a,b)=>{...}
Arrowfunctionscanhaveboththestatementblockbodiesaswellasexpressionbodies:
n=>{returnn+n}//statementblockn=>n+n//expression
Bothareequivalentbutthesecondvariationisconciseandpreferred.Arrowfunctionsare
alwaysanonymous.Oneimportantaspectofarrowfunctionsthatwewilldiscussalittlelateristhatarrowfunctionsdonotbindtheirownvaluesofthethiskeyword-thevalueislexicallyderivedfromthesurroundingscope.Aswehavenotyetlookedatthethiskeywordindetail,wewilldeferthediscussiontoalaterpartofthisbook.
Exercises1. Writeafunctionthatconvertsahexadecimalcolor,forexampleblue(#0000FF),intoitsRGBrepresentation,rgb(0,0,255).NameyourfunctiongetRGB()andtestitwiththefollowingcode(hint:treatthestringasanarrayofcharacters):
>vara=getRGB("#00FF00");>a;"rgb(0,255,0)"
2. Whatdoeachofthesefollowinglinesprintintheconsole?
>parseInt(1e1);> parseInt('1e1');
>parseFloat('1e1');>isFinite(0/10);>isFinite(20/0);>isNaN(parseInt(NaN));
3. Whatdoesthisfollowingcodealert?
vara=1;functionf(){functionn(){alert(a);}vara=2;n();}f();
4. Allthesefollowing examplesalert"Boo!".Canyouexplainwhy?Example1:
varf=alert;eval('f("Boo!")');
Example2:
vare;varf=alert;eval('e=f')('Boo!');
Example3:
(function(){returnalert;})()('Boo!');
SummaryYou have now completed the introduction to the fundamental concepts related to functions inJavaScript.Thishaslaidthegroundworkthatwillallowyoutoquicklygrasptheconceptsofobject-orientedJavaScriptandthepatternsusedinmodernJavaScriptprogramming.Sofar,we'vebeenavoidingtheOOfeatures,butasyouhavereachedthispointinthebook,it'sonlygoingtogetmoreinterestingfromhereonin.Let'stakeamomenttoreviewthetopicsdiscussedinthischapter:
Thebasicsofhowtodefineandinvoke(call)afunctionusingeitherafunctiondeclarationsyntaxorafunction expressionFunctionparametersandtheirflexibilityBuilt-infunctions-parseInt(),parseFloat(),isNaN(),isFinite(),andeval()-andthefourfunctionstoencode/decodeaURLThe scope of variables in JavaScript-no curly braces scope, variables have only functionscopeandthescopechainFunctionsasdata-afunctionislikeanyotherpieceofdatathatyouassigntoavariableandalotofinterestingapplicationsfollowfromthis,suchas:
PrivatefunctionsandprivatevariablesAnonymousfunctionsCallbacksImmediatefunctionsFunctionsoverwritingthemselves
ClosuresArrowfunctions
Chapter 4. ObjectsNow that you've mastered JavaScript's primitive data types, arrays, and functions, it's time to staytruetothepromiseofthebooktitleandtalkaboutobjects.
JavaScripthasaneccentrictakeontheclassicalObject-orientedprogramming.Object-orientedprogrammingisoneofthemostpopularprogrammingparadigmsandhasbeenamainstayinmostofprogramminglanguageslikeJavaandC++.TherearewelldefinedideasproposedbyclassicalOOPthatmostoftheselanguagesadopt.JavaScript,however,hasadifferenttakeonit.WewilllookJavaScript'swayofsupportingOOP.
Inthischapter,youwilllearnthefollowingtopics:
HowtocreateanduseobjectsWhataretheconstructorfunctionsWhattypesofbuilt-inJavaScriptobjectsexistandwhattheycandoforyou
From arrays to objectsAs you already know fromChapter 2, Primitive Data Types, Arrays, Loops, and Conditions, anarrayisjustalistofvalues.Eachvaluehasanindex(anumerickey)thatstartsfromzeroandincrementsbyoneforeachvalue.Considerthefollowingexample:
>varmyarr=['red','blue','yellow','purple'];>myarr;["red","blue","yellow","purple"].>myarr[0];"red">myarr[3];"purple"
Ifyouputtheindexesinonecolumnandthevaluesinanother,you'llendupwithatableofkey/valuepairsshownasfollows:
Key Value
0 red
1 blue
2 yellow
3 purple
Anobjectissimilartoanarray,butthedifferenceisthatyoudefinethekeysyourself.You'renotlimitedtousingonlynumericindexes,andyoucanusefriendlierkeyssuchasfirst_name,age,andsoon.
Let'stakealookatasimpleobjectandexamineitsparts:
varhero={breed:'Turtle',occupation:'Ninja'};
Youcanseethat:
ThenameofthevariablethatreferstotheobjectisheroInsteadof[and],whichyouusetodefineanarray,youuse{and}forobjectsYouseparatetheelements(calledproperties)containedintheobjectwithcommasThekey/valuepairsaredividedbycolons,asinkey:value
Thekeys(namesoftheproperties)canoptionallybeplacedinquotationmarks.Forexample,thesekeysareallthesame:
varhero={occupation:1};varhero={"occupation":1};varhero={'occupation':1};
It'srecommendedthatyoudon'tquotethenamesoftheproperties(it'slesstyping),buttherearecaseswhenyoumustusequotes.Someofthecasesarestatedhere:
IfthepropertynameisoneofthereservedwordsinJavaScript(seeAppendixA,ReservedWords)Ifitcontainsspacesorspecialcharacters(anythingotherthanletters,numbers,andthe_and$characters)Ifitstartswithanumber
Inotherwords,ifthenameyouhavechosenforapropertyisnotavalidnameforavariableinJavaScript,thenyouneedtowrapitinquotes.
Havealookatthisbizarre-lookingobject:
var o = {$omething:1,'yesorno':'yes','!@#$%^&*':true};
Thisisavalidobject.Thequotesarerequiredforthesecondandthethirdproperties;otherwise,you'llgetanerror.
Laterinthischapter,you'llseeotherwaystodefineobjectsandarrays,inadditionto[]and{}.However,first,let'sintroducethisbitofterminology-defininganarraywith[]iscalledarrayliteralnotation,anddefininganobjectusingcurlybraces{}iscalledobjectliteralnotation.
Elements,properties,methods,andmembers
Whentalkingaboutarrays,yousaythattheycontainelements.Whentalkingaboutobjects,yousaythattheycontainproperties.Thereisn'tanysignificantdifferenceinJavaScript;it'sjusttheterminologythatpeopleareusedto,probablyfromotherprogramminglanguages.
Apropertyofanobjectcanpointtoafunction,becausefunctionsarejustdata.Propertiesthatpointtofunctionsarealsocalledmethods.Inthefollowingexample,talkisamethod:
vardog={name:'Benji',talk:function(){alert('Woof,woof!');}};
Asyouhaveseeninthepreviouschapter,it'salsopossibletostorefunctionsasarrayelementsandinvokethem,butyou'llnotseemuchcodelikethisinpractice:
>vara=[];>a[0]=function(what){alert(what);};>a[0]('Boo!');
Youcanalsoseepeopleusingthewordmemberstorefertothepropertiesofanobject,mostoftenwhenitdoesn'tmatterifthepropertyisafunctionornot.
Hashesandassociativearrays
Insomeprogramminglanguages,thereisadistinctionbetween:
A regular array, also called an indexed or enumerated array (the keys are numbers)Anassociativearray,alsocalledahashoradictionary(thekeysarestrings)
JavaScriptusesarraystorepresentindexedarraysandobjectstorepresentassociativearrays.IfyouwantahashinJavaScript,youuseanobject.
Accessinganobject'sproperties
Therearetwowaystoaccessthepropertyofanobject:
Using the square bracket notation, for example, hero['occupation']Using the dot notation, for example, hero.occupation
Thedotnotationiseasiertoreadandwrite,butitcannotalwaysbeused.Thesamerulesapplyforquotingpropertynames.Ifthenameofthepropertyisnotavalidvariablename,youcannotusethedotnotation.
Let'staketheheroobjectagain:
varhero={breed:'Turtle',occupation:'Ninja'};
Followingisanexampleforaccessingapropertywiththedotnotation:
>hero.breed;"Turtle"
Let'sseeanexampleforaccessingapropertywiththebracketnotation:
>hero['occupation'];"Ninja"
Considerthefollowingexampleforaccessinganon-existingpropertyreturnsundefined:
>'Haircoloris'+hero.hair_color;"Haircolorisundefined"
Objectscancontainanydata,includingotherobjects:
varbook={name:'Catch-22',published:1961,author:{firstname:'Joseph',lastname:'Heller'}};
Togettothefirstnamepropertyoftheobjectcontainedintheauthorpropertyofthebookobject,youcanusethefollowinglinesofcode:
>book.author.firstname;"Joseph"
Letseeanexampleusingthesquarebracketsnotation:
>book['author']['lastname'];"Heller"
Itworksevenifyoumixboth:
>book.author['lastname'];"Heller">book['author'].lastname;"Heller"
Anothercasewhereyouneedsquarebracketsiswhenthenameofthepropertyyouneedtoaccessisnotknownbeforehand.Duringruntime,it'sdynamicallystoredinavariable:
>varkey='firstname';>book.author[key];"Joseph"
Callinganobject'smethods
Youknowamethodisjustapropertythathappenstobeafunction,soyouaccessmethodsinthesamewayinwhichyouwouldaccessproperties-usingthedotnotationorusingsquarebrackets.Calling(invoking)amethodisthesameascallinganyotherfunction-youjustaddparenthesesafterthemethodname,whicheffectivelysaysExecute!:
>varhero={breed:'Turtle',occupation:'Ninja',say:function(){return'Iam'+hero.occupation;}};>hero.say();"IamNinja"
Ifthereareanyparametersthatyouwanttopasstoamethod,youwouldproceedasyouwouldwithnormalfunctions:
>hero.say('a','b','c');
Asyoucanusethearray-likesquarebracketstoaccessaproperty,itmeansyoucanalsousebracketstoaccessandinvokemethods:
>hero['say']();
This is not a common practice, unless the method name is not known at the time of writing code,butitisinsteaddefinedatruntime:
varmethod='say';hero[method]();
Note
Noquotesunlessyouhavetousethedotnotation toaccessmethodsandproperties,anddon'tquotepropertiesinyourobjectliterals.
Alteringproperties/methods
JavaScriptallowsyoutoalterthepropertiesandmethodsofexistingobjectsatanytime.Thisincludesaddingnewpropertiesordeletingthem.Youcanstartwithablankobjectandaddpropertieslater.Let'sseehowyoucangoaboutdoingthis.
Anobjectwithoutpropertiesisshownasfollows:
>varhero={};
Note
A"blank"object
Inthissection,youstartedwitha"blank"object,varhero={}.Blankisinquotesbecausethisobjectisnotreallyemptyanduseless.Althoughatthisstageithasnopropertiesofitsown,ithasalreadyinheritedsome.
You'lllearnmoreaboutownversusinheritedpropertieslater.So,anobjectinES3isneverreallyblankorempty.InES5though,thereisawaytocreateacompletelyblankobjectthatdoesn'tinheritanything,butlet'snotgetaheadtoomuch.
1. Followingisthecodetoaccessannon-existingproperty:
>typeofhero.breed;"undefined"
2. Addingtwopropertiesandamethod:
>hero.breed='turtle';>hero.name='Leonardo';>hero.sayName=function(){returnhero.name;};
3. Callingthemethod:
>hero.sayName();"Leonardo"
4. Deletingaproperty:
>deletehero.name;true
5. Ifyoucallthemethodagain,itwillnolongerfindthedeletednameproperty:
>hero.sayName();"undefined"
Note
Malleableobjects
Youcanalwayschangeanyobjectatanytime,suchasaddingandremovingpropertiesandchanging their values. However, there are exceptions to this rule. A few properties of some built-inobjectsarenotchangeable(forexample,Math.PI,asyou'llseelater).Also,ES5allowsyoutopreventchangestoobjects.You'lllearnmoreaboutitinAppendixC,Built-inObjects.
Usingthethisvalue
Inthepreviousexample, thesayName()methodusedhero.nametoaccessthenamepropertyoftheheroobject.Whenyou'reinsideamethodthough,thereisanotherwaytoaccesstheobjectthemethodbelongsto.Thismethodisusingthespecialvaluethis:
>varhero={name:'Rafaelo',sayName:function(){returnthis.name;}};>hero.sayName();"Rafaelo"
So,whenyousaythis,you'reactuallysaying-thisobjectorthecurrentobject.
Constructorfunctions
Thereisanotherwaytocreateobjects-usingconstructorfunctions.Let'slookatanexample:
function Hero() {this.occupation='Ninja';}
Inordertocreateanobjectusingthisfunction,youcanusethenewoperatorasfollows:
>varhero=newHero();>hero.occupation;"Ninja"
Abenefitofusingconstructorfunctionsisthattheyacceptparameters,whichcanbeusedwhencreating new objects. Let's modify the constructor to accept one parameter and assign it to thenameproperty:
functionHero(name){this.name=name;this.occupation='Ninja';this.whoAreYou=function(){return"I'm"+this.name+"andI'ma"+this.occupation;};}
Now, you can create different objects using the same constructor:
>varh1=newHero('Michelangelo');>varh2=newHero('Donatello');>h1.whoAreYou();"I'mMichelangeloandI'maNinja">h2.whoAreYou();"I'mDonatelloandI'maNinja"
Note
Byconvention,youshouldcapitalizethefirstletterofyourconstructorfunctionssothatyouhaveavisualcluethattheyarenotintendedtobecalledasregularfunctions.
Ifyoucallafunctionthatisdesignedtobeaconstructorbutyouomitthenewoperator,itisnotanerror.However,itdoesn'tgiveyoutheexpectedresult:
>varh=Hero('Leonardo');>typeofh;"undefined"
Whathappenedhere?Thereisnonewoperator,soanewobjectwasnotcreated.Thefunctionwascalledlikeanyotherfunction,sothevariablehcontainsthevaluethatthefunctionreturns.Thefunctiondoesnotreturnanything(there'snoreturnfunction),soitactuallyreturnsundefined,whichgetsassignedtothevariableh.
Inthiscase,whatdoesthisreferto?Itreferstotheglobalobject.
Theglobalobject
Youhavealreadylearnedabitaboutglobalvariables(andhowyoushouldavoidthem).YoualsoknowthatJavaScriptprogramsruninsideahostenvironment(thebrowser,forexample).Nowthatyouknowaboutobjects,it'stimeforthewholetruth,thehostenvironmentprovidesaglobalobject,andallglobalvariablesareaccessibleaspropertiesoftheglobalobject.
Ifyourhostenvironmentisthewebbrowser,theglobalobjectiscalledwindow.Anotherwaytoaccesstheglobalobject(andthisisalsotrueinmostotherenvironments)istousethiskeywordoutsideaconstructorfunction,forexampleintheglobalprogramcodeoutsideanyfunction.
As an illustration, you can declare a global variable outside any function as follows:
>vara=1;
Then,youcanaccessthisglobalvariableinvariousways:
AsavariableaAsapropertyoftheglobalobject,forexample,window['a']orwindow.aAsapropertyoftheglobalobjectreferredtoasthis:
>vara=1;>window.a;1>this.a;1
Let'sgobacktothecasewhereyoudefineaconstructorfunctionandcallitwithoutthenewoperator.Insuchcases,thisreferstotheglobalobjectandallthepropertiessettothisbecomepropertiesofwindow.
Declaringaconstructorfunctionandcallingitwithoutnewreturns"undefined":
>functionHero(name){this.name=name;}>varh=Hero('Leonardo');
> typeof h;"undefined">typeofh.name;TypeError:Cannotreadproperty'name'ofundefined
AsyouhadthiskeywordinsidethefunctionHero,aglobalvariable(apropertyoftheglobalobject)callednamewascreated:
>name;"Leonardo"
> window.name;
"Leonardo"
Ifyoucallthesameconstructorfunctionusingnew,thenanewobjectisreturned,andthisreferstoit:
>varh2=newHero('Michelangelo');>typeofh2;"object">h2.name;"Michelangelo"
Thebuilt-inglobalfunctionsyouhaveseeninChapter3,Functions,canalsobeinvokedasmethods of the window object. So, the following two calls have the same result:
>parseInt('101dalmatians');101>window.parseInt('101dalmatians')101
Theconstructorproperty
Whenanobjectiscreated,aspecialpropertyisassignedtoitbehindthescenes-theconstructorproperty.Itcontainsareferencetotheconstructorfunctionusedtocreatethisobject.
Continuingfromthepreviousexample:
>h2.constructor;functionHero(name){this.name=name;
}
Astheconstructorpropertycontainsareferencetoafunction,youmightaswellcallthisfunctiontoproduceanewobject.Thefollowingcodeislikesaying,"Idon'tcarehowobjecth2wascreated,butIwantanotheronejustlikeit":
>varh3=newh2.constructor('Rafaello');>h3.name;"Rafaello"
Ifanobjectwascreatedusingtheobjectliteralnotation,itsconstructoris thebuilt-inObject()constructorfunction(thereismoreaboutthislaterinthischapter):
>varo={};>o.constructor;functionObject(){[nativecode]}>typeofo.constructor;"function"
Theinstanceofoperator
Withtheinstanceofoperator,youcantestwhetheranobjectwascreatedwithaspecificconstructorfunction:
>functionHero(){}>varh=newHero();>varo={};>hinstanceofHero;true>hinstanceofObject;true>oinstanceofObject;true
Notethatyoudon'tputparenthesesafterthefunctionname(youdon'tusehinstanceofHero()).Thisisbecauseyou'renotinvokingthisfunction,butjustreferringtoitbyname,aswithanyothervariable.
Functionsthatreturnobjects
Inadditiontousingconstructorfunctionsandthenewoperatortocreateobjects,youcanalsouseanormalfunctiontocreateobjectswithoutthenewoperator.Youcanhaveafunctionthatdoesabitofpreparatoryworkandhasanobjectasareturnvalue.
Forexample,here'sasimplefactory()functionthatproducesobjects:
functionfactory(name){return{name:name};}
Considerthefollowingexampleusingthefactory()function:
>varo=factory('one');>o.name;"one">o.constructor;functionObject(){[nativecode]}
Infact,youcanalsouseconstructorfunctionsandreturnobjectsdifferentfromthiskeyword.Thismeansyoucanmodifythedefaultbehavioroftheconstructorfunction.Let'sseehow.
Here'sthenormalconstructorscenario:
> function C() {this.a=1;}>varc=newC();>c.a;1
However,now,lookatthisscenario:
>functionC2(){this.a=1;return{b:2};}
> var c2 = new C2();>typeofc2.a;"undefined">c2.b;2
Whathappenedhere?Insteadofreturningthethisobject,whichcontainsthepropertya,theconstructorreturnedanotherobjectthatcontainsthepropertyb.Thisispossibleonlyifthereturn
valueisanobject.Otherwise,ifyoutrytoreturnanythingthatisnotanobject,theconstructorwillproceedwithitsusualbehaviorandreturnthis.
Ifyouthinkabouthowobjectsarecreatedinsideconstructorfunctions,youcanimaginethatavariable called this is defined at the top of the function and then returned at the end. Consider thefollowing code:
functionC(){//varthis={};//pseudocode,youcan'tdothisthis.a=1;//returnthis;}
Passingobjects
Whenyouassignanobjecttoadifferentvariable orpassittoafunction,youonlypassareferencetothatobject.Consequently,ifyoumakeachangetothereference,you'reactuallymodifyingtheoriginalobject.
Here'sanexampleofhowyoucanassignanobjecttoanothervariableandthenmakeachangetothecopy.Asaresult,the originalobjectisalsochanged:
>varoriginal={howmany:1};>varmycopy=original;>mycopy.howmany;1>mycopy.howmany=100;100>original.howmany;100
Thesamethingapplieswhenpassingobjectstofunctions:
>varoriginal={howmany:100};>varnullify=function(o){o.howmany=0;};>nullify(original);>original.howmany;0
Comparingobjects
Whenyoucompareobjects,you'llgettrueonlyifyoucomparetworeferencestothesameobject.Ifyoucomparetwodistinctobjectsthathappentohavetheexactsamemethodsandproperties,theresultwouldbefalse.
Let'screatetwoobjectsthatlookthesame:
>varfido={breed:'dog'};>varbenji={breed:'dog'};
Comparingthemreturnsfalse:
>benji===fido;false>benji==fido;
false
Youcancreateanewvariable,mydog,andassignoneoftheobjectstoit.Thisway,thevariablemydogactuallypointstothesameobject:
>varmydog=benji;
In this case, benji is mydog because they are the same object (changing the mydog variable'sproperties will change the benji variable's properties). The comparison returns true:
>mydog===benji;true
Asfidoisadifferentobject,itdoesnotcomparetomydog:
>mydog===fido;false
ObjectsintheWebKitconsole
Beforedivingintothebuilt-inobjectsinJavaScript,let'squicklysayafewwordsaboutworkingwithobjectsintheWebKitconsole.
Afterplayingaroundwiththeexamplesinthischapter,youmighthavealreadynoticedhowobjectsaredisplayedintheconsole.Ifyoucreateanobjectandtypeitsname,you'llgetanarrowpointingtothewordobject.
Theobjectisclickableandexpandstoshowyoualistofallofthepropertiesoftheobject.Ifaproperty is also an object, there is an arrow next to it too, so you can expand this as well. This ishandyasitgivesyouaninsightintoexactlywhatthisobjectcontains.Considerthefollowingexample:
Note
Youcanignore__proto__fornow;there'smore aboutitinthenextchapter.
Loggingusingtheconsole.logmethod
Theconsolealsooffersyouanobjectcalledconsoleandafewmethods,suchasconsole.log()andconsole.error(),whichyoucanusetodisplayanyvalueyouwantintheconsole.
Theconsole.log()methodisconvenientwhenyouwanttoquicklytestsomething,aswellaswhenyouwanttodumpsomeintermediatedebugginginformationinyourrealscripts.Here'showyoucanexperimentwithloops,forexample:
> for (var i = 0; i < 5; i++) {console.log(i);}01234
ES6objectliterals
ES6introducesamuchsuccinctsyntaxwhileusingobjectliterals.ES6offersseveralshorthandsforpropertyinitializationandfunctiondefinitions.ES6shorthandscloselyresembleafamiliarJSONsyntax.Considerthefollowingcodefragment:
leta=1letb=2letval={a:a,b:b}console.log(val)//{"a":1,"b":2}
This is a typical way to assign property values. If the name of the variable and the property key isthesame,ES6allowsyoutouseshorthandsyntax.Theprecedingcodecanbewrittenasfollows:
leta=1letb=2letval={a,b}console.log(val)//{"a":1,"b":2}
Similarsyntaxisavailableformethoddefinitionsaswell.Aswehavediscussed,methodsaresimplypropertiesofanobjectwhosevaluesarefunctions.Considerthefollowingexample:
varobj={prop:1,modifier:function(){
console.log(this.prop);}}
ThereisacompactwaytodefinemethodsinES6.Yousimplydropthefunctionkeywordand:.TheequivalentcodeinES6wouldlooklikethefollowing:
varobj={prop:1,modifier(){console.log(this.prop);}}
ES6allowsyoutocomputethekeyofaproperty.UntilES6,youcouldonlyusefixedpropertynames.Hereisanexample:
varobj={prop:1,modifier:function(){console.log(this.prop);}}obj.prop=2;obj.modifier();//2
Asyoucansee,wearelimitedtousingfixedkey names:propandmodifierinthiscase.However,ES6allowsyoutousecomputedpropertykeys.Itispossibletocreatepropertykeysdynamicallyusingvaluesreturnedbyafunctionaswell:
letvehicle="car"functionvehicleType(){return"truck"}letcar={[vehicle+"_model"]:"Ford"}lettruck={[vehicleType()+"_model"]:"Mercedez"}console.log(car)//{"car_model":"Ford"}console.log(truck)//{"truck_model":"Mercedez"}
Weareusingthevalueofvariablevehicletoconcatenatewithafixedstringtoderivethepropertykeywhilecreatingthecarobject.Inthesecondsnippet,wearecreatingapropertybyconcatenatingafixedstringwiththevaluereturnedbyafunction.Thiswayofcomputingpropertykeysprovidesgreatflexibilitywhilecreatingobjects,andalotofboilerplateandrepetitivecodecanbeeliminated.
Thissyntaxisapplicabletomethoddefinitionaswell:
letobject_type="Vehicle"letobj={["get"+object_type](){return"Ford"}}
Object properties and attributesEach object has a few properties. Each property, in turn, has a key and attributes. A property'sstateisstoredintheseattributes.Allpropertieshavethefollowingattributes:
Enumerable(boolean):Thisindicatesifyoucanenumeratethepropertiesoftheobject.Systempropertiesarenon-enumerablewhileuserpropertiesareenumerable.Unlessthereisastrongreason,thispropertyshouldremainuntouched.Configurable(boolean):Ifthisattributeisfalse,thepropertycannotbedeletedoredited(itcannotchangeanyofitsattribute).
YoucanusetheObject.getOwnPropertyDescriptor()methodtoretrieveanobject'sownproperties:
letobj={age:25}console.log(Object.getOwnPropertyDescriptor(obj,'age'));//{"value":25,"writable":true,"enumerable":true,"configurable":true}
Meanwhile,thepropertycanbedefinedusingtheObject.defineProperty()method:
letobj={age:25}Object.defineProperty(obj,'age',{configurable:false})console.log(Object.getOwnPropertyDescriptor(obj,'age'));//{"value":25,"writable":true,"enumerable":true,"configurable":false}
Thoughyouwouldneverusethesemethods,itisimportanttounderstandobjectpropertiesandattributes.Inthenextsection,wewilldiscusshowsomeoftheobjectmethodsareusedincontextofsomeoftheseproperties.
ES6 object methodsES6 introduces a few static helper methods for objects. Object.assign is a helper method thatreplaces popular mixins to perform a shallow copy of an object.
CopypropertiesusingObject.assign
Thismethodisusedtocopypropertiesofthetargetobjectintothesourceobject.Inotherwords,thismethodmergesthesourceobjectwiththetargetobjectandmodifiesthetargetobject:
leta={}Object.assign(a,{age:25})console.log(a)//{"age":25}
ThefirstparametertoObject.assignisthetargetonwhichsourcepropertiesarecopied.Thesametargetobjectisreturnedtothecaller.Existingpropertiesareoverwritten,whilepropertiesthataren'tpartofthesourceobjectareignored:
leta={age:23,gender:"male"}Object.assign(a,{age:25})//ageoverwritten,butgenderignoredconsole.log(a)//{"age":25,"gender":"male"}
Object.assigncantakemultiplesourceobjects.YoucanwriteObject.assign(target,source1,source2).Hereisanexample:
console.log(Object.assign({a:1,b:2},{a:2},{c:4},{b:3}))//Object{//"a":2,//"b":3,//"c":4//
Inthissnippet,weareassigningpropertiesfrommultiplesourceobjects.Also,noticehowObject.assign()returnsthetargetobject,whichweinturnuseinsideconsole.log().
Onepointtonoteisthatonlyenumerableown(non-inherited)propertiescanbecopiedusingObject.assign().Propertiesfromtheprototypechain(willbediscussedlaterinthischapterwhenwetalkaboutInheritance)arenotconsidered.Ourearlierdiscussionofenumerablepropertieswillhelpyouunderstandthisdistinction.
Inthefollowingexample,wewillcreateanon-enumerablepropertyusingdefineProperty()andvalidatethefactthatObject.assign()ignoresthatproperty:
leta={age:23,gender:"male"}Object.defineProperty(a,'superpowers',{enumberable:false,value:
'ES6'})console.log(
Thepropertydefinedassuperpowershastheenumerableattributesettofalse.Whilecopyingproperties,thispropertyisignored.
ComparevalueswithObject.is
ES6providesaslightlyprecisewayofcomparingvalues.Wehavediscussedthestrictequalityoperator===.However,forNaNand-0and+0,thestrictequalityoperatorbehavesinconsistently.Hereisanexample:
console.log(NaN===NaN)//falseconsole.log(-0===+0)//true//ES6Object.isconsole.log(Object.is(NaN,NaN))//true
console.log(Object.is(-0,+0)) //false
Apartfromthesetwocases,Object.is()cansafelybereplacedwiththe===operator.
DestructuringYou will be working with objects and arrays all the time when you code. JavaScript object andarraynotationsresembletheJSONformat.Youwilldefineobjectsandarrays,andthenretrieveelementsfromthem.ES6givesaconvenientsyntaxthatsignificantlyimprovesthewayweaccessproperties/membersfromobjectsandarrays.Let'sconsideratypicalcodeyouwouldoftenwrite:
varconfig={server: 'localhost',
port:'8080'}varserver=config.server;varport=config.port;
Here,weextractedvaluesofserverandportfromtheconfigobjectandassignedthemtolocalvariables.Prettystraightforward!However,whenthisobjecthasabunchofproperties,someofthemnested,thissimpleoperationcangetverytedioustowrite.
ES6destructuringsyntaxallowsanobjectliteralontheleft-handsideofanassignmentstatement.Inthefollowingexample,wewilldefineanobjectconfigwithafewproperties.Later,wewillusedestructuringtoassigntheobjectconfigtoassignvaluestoindividualpropertiesontheleft-handsideoftheassignmentstatement:
letconfig={server:'localhost',port:'8080',timeout:900,}let{server,port}=configconsole.log(server,port)//"localhost""8080"
Asyoucanseeserverandportarelocalvariablesthatgotassignedpropertiesfromtheconfigobjectbecausethenameofthepropertieswerethesameasthatofthelocalvariables.Youcanalsopickparticularpropertieswhileyouassignthemtolocalvariables.Hereisanexample:
let{timeout:t}=configconsole.log(t)//900
Here,weareonlypickingtimeoutfromtheconfigobjectandassignittoalocalvariablet.
Youcanalsousethedestructuringsyntaxtoassignvaluestoalreadydeclaredvariables.Inthiscase,youhavetoputparenthesesaroundtheassignment:
letconfig={server: 'localhost',
port:'8080',timeout:900,
}letserver='127.0.0.1';letport='80';({server,port}=config)//assignmentsurroundedby()console.log(server,port)//"localhost""8080"
Asthedestructuringexpressionevaluatestotheright-handsideoftheexpression,it'spossibletouseitanywhereyouwouldexpectavalue.Forexample,inafunctioncall,asshownhere:
letconfig={server:'localhost',port:'8080',timeout:900,}
let server='127.0.0.1';letport='80';lettimeout='100';
functionstartServer(configValue){console.log(configValue)}startServer({server,port,timeout}=config)
Ifyouspecifyalocalvariablewithapropertynamethatdoesnotexistintheobject,thelocalvariablegetsanundefinedvalue.However,whileusingvariablesinthe destructuringassignment,youcanoptionallyspecifydefaultvalues:
letconfig={server:'localhost',port:'8080'}let{server,port,timeout=0}=configconsole.log(timeout)
Inthisexample,foranon-existentpropertytimeout,weprovidedadefaultvaluetopreventgetting undefined values assigned to local variables.
Destructuringworksonarraysaswell,andthesyntaxisalsoverysimilar tothatoftheobjects.Wejustneedtoreplaceobjectliteralsyntaxwitharray:literals:
constarr=['a','b']const[x,y]=arrconsole.log(x,y)/"a""b"
Asyoucansee,thisistheexactsamesyntaxwesawearlier.Wedefinedanarrayarrandlaterusedthedestructuringsyntaxtoassignelementsofthatarraytotwolocalvariables,xandy.Here,theassignmenthappensbasedontheorderofelementsinthearray.Asyouonlycareaboutthepositionofelements,youcanskipsomeofthemifyouwantto.Hereisanexample:
constdays=['Thursday','Friday','Saturday','Sunday']
const[,,sat,sun]=daysconsole.log(sat,sun)//"Saturday""Sunday"
Here,weknowthatweneedelementsfrompositions2and3(anarray'sindexstartsfrom0),andhence,weignoreelementsatpositions0and1.Arraydestructuringcaneliminatetheuseofatempvariablewhileswappingvaluesoftwovariables.Considerthefollowing:
leta=1,b=2;[b,a]=[a,b]console.log(a,b)//21
Youcanusetherestoperator(...)toextractremainingelementsandassignthemtoanarray.Therestoperatorcanonlybeusedasthelastoperatorduringdestructuring:
const[x,...y]=['a','b','c'];//x='a';y=['b','c']
Built-in objectsEarlier in this chapter, you came across the Object() constructor function. It's returned when youcreate objects with the object literal notation and access their constructor property. Object()is one of the built-in constructors; there are a few others, and in the rest of this chapter you'll seeallofthem.
Thebuilt-inobjectscanbedividedintothreegroups:
Datawrapperobjects:TheseareObject,Array,Function,Boolean,Number,andString.TheseobjectscorrespondtothedifferentdatatypesinJavaScript.Thereisadatawrapperobjectforeverydifferentvaluereturnedbytypeof(discussedinChapter2,PrimitiveDataTypes,Arrays,Loops,andConditions),withtheexceptionofundefinedandnull.Utilityobjects:TheseareMath,Date,andRegExp,andcancomeinhandy.Errorobjects:TheseincludethegenericErrorobjectaswellasothermorespecificobjectsthatcanhelpyourprogramrecoveritsworkingstatewhensomethingunexpectedhappens.
Onlyahandfulofmethodsofthebuilt-inobjectswillbediscussedinthischapter.Forafullreference,seeAppendixC,Built-inObjects.
Ifyou'reconfusedaboutwhatabuilt-inobjectisandwhatabuilt-inconstructoris,well,theyarethesamething.Inamoment,you'llseehowfunctionsand,therefore,constructorfunctions,arealsoobjects.
Object
ObjectistheparentofallJavaScriptobjects,whichmeansthateveryobjectyoucreateinheritsfromit.Tocreateanewemptyobject,youcanusetheliteralnotationortheObject()constructorfunction.Thefollowingtwolinesare equivalent:
>varo={};>varo=newObject();
Asmentionedbefore,anempty(orblank)objectisnotcompletelyuseless,becauseitalreadycontainsseveralinheritedmethodsandproperties.Inthisbook,emptymeansanobjectlike{}thathasnopropertiesofitsown,otherthantheonesitautomaticallygets.Let'slookatafewofthepropertiesthatevenblankobjectsalreadyhave:
Theo.constructorpropertyreturnsareferencetotheconstructorfunctionTheo.toString()isamethodthatreturnsastringrepresentationoftheobjectTheo.valueOf()returnsasingle-valuerepresentationoftheobject;often,thisistheobjectitself
Let'sseethesemethodsinaction.First,createanobject:
>varo=newObject();
CallingtoString()returnsastringrepresentationoftheobject:
>o.toString();"[objectObject]"
ThetoString()methodwillbecalledinternallybyJavaScriptwhenanobjectisusedinastringcontext.Forexample,alert()worksonlywithstrings,soifyoucallthealert()functionpassinganobject,thetoString()methodwillbecalledbehindthescenes.Thesetwolinesproducethesameresult:
>alert(o);>alert(o.toString());
Anothertypeofstringcontextisthestringconcatenation.Ifyoutrytoconcatenateanobjectwithastring,theobject'stoString()methodiscalledfirst:
>"Anobject:"+o;"Anobject:[objectObject]"
ThevalueOf()methodisanothermethodthatallobjectsprovide.Forthesimpleobjects(whoseconstructorisObject()),thevalueOf()methodreturnstheobjectitself:
>o.valueOf()===o;true
Tosummarize:
Youcancreateobjectseitherwithvaro={};(objectliteralnotation,thepreferredmethod)orwithvaro=newObject();Anyobject,nomatterhowcomplex,inheritsfromtheObjectobjectandtherefore,offersmethodssuchastoString()andpropertiessuchasaconstructor
Array
Array()isabuilt-infunctionthatyoucanuseasaconstructortocreatearrays:
>vara=newArray();
Thisisequivalenttothearrayliteralnotation:
>vara=[];
No matter how the array is created, you can add elements to it as usual:
>a[0]=1;>a[1]=2;>a;[1,2]
WhenusingtheArray()constructor,youcanalsopassvaluesthatwillbeassignedtothenewarray'selements:
>vara=newArray(1,2,3,'four');>a;[1,2,3,"four"]
Anexceptiontothisiswhenyoupassasinglenumbertotheconstructor.Inthiscase,thenumberisconsideredtobethelengthofthearray:
>vara2=newArray(5);>a2;[undefinedx5]
Asarraysarecreatedwithaconstructor,doesthismeanthatarraysareinfactobjects?Yes,andyoucanverifythisusingthetypeofoperator:
>typeof[1,2,3];"object"
Asarraysareobjects,thismeansthattheyinherit thepropertiesandmethodsoftheparentobject:
>vara=[1,2,3,'four'];>a.toString();"1,2,3,four">a.valueOf();[1,2,3,"four"]>a.constructor;functionArray(){[nativecode]}
Arraysareobjects,butofaspecialtypebecause:
Thenamesoftheirpropertiesareautomaticallyassignedusingnumbersstartingfrom0.
Theyhavealengthpropertythatcontainsthenumberofelementsinthearray.Theyhavemorebuilt-inmethodsinadditiontothoseinheritedfromtheparentobject.
Let'sexaminethedifferencesbetweenanarrayandanobject,startingbycreatingtheemptyarraya and the empty object o:
>vara=[],o={};
Arrayobjectshavealengthpropertyautomaticallydefinedforthem,whilenormalobjectsdonot:
>a.length;0>typeofo.length;"undefined"
It'soktoaddbothnumericandnon-numericpropertiestobotharraysandobjects:
> a[0] = 1;>o[0]=1;>a.prop=2;>o.prop=2;
Thelengthpropertyisalwaysuptodatewiththenumberofnumericproperties,whileitignoresthenon-numericones:
>a.length;1
The length property can also be set by you. Setting it to a greater value than the current numberof items in the array makes room for additional elements. If you try to access these non-existingelements,you'llgetthevalueundefined:
>a.length=5;5>a;[1,undefinedx4]
Settingthelengthpropertytoalowervalueremovesthetrailingelements:
>a.length=2;2>a;[1,undefinedx1]
Afewarraymethods
Inadditiontothemethodsinheritedfromtheparentobject,arrayobjectsalsohavespecializedmethodsforworkingwitharrays,suchassort(),join(),andslice(),amongothers(see
AppendixC,Built-inObjects,forthecompletelist).
Let'stakeanarrayandexperimentwithsomeofthesemethods:
>vara=[3,5,1,7,'test'];
Thepush()methodappendsanewelementtotheendofthearray.Thepop()methodremovesthelastelement.Thea.push('new')methodworkslikea[a.length]='new',anda.pop()islikea.length-.
The push() method returns the length of the changed array, whereas pop() returns the removedelement:
>a.push('new');6>a;[3,5,1,7,"test","new"]>a.pop();"new">a;[3,5,1,7,"test"]
Thesort()methodsortsthearrayandreturnsit.Inthenextexample,aftersort,bothaandbpointtothesamearray:
>varb=a.sort();>b;[1,3,5,7,"test"]>a===b;true
Thejoin()methodreturnsastringcontainingthevaluesofalltheelementsinthearraygluedtogetherusingthestringparameterpassedtojoin():
>a.join('isnot');"1isnot3isnot5isnot7isnottest"
Theslice()methodreturnsapieceofthearraywithoutmodifyingthesourcearray.Thefirstparametertoslice()is thestartindex(zero-based),andthesecondistheendindex(bothindicesarezero-based).Startindexisincluded,whiletheendindexisnot.Takealookatthefollowingexample:
>b=a.slice(1,3);[3,5]>b=a.slice(0,1);[1]
> b = a.slice(0, 2);[1,3]
Afteralltheslicing,thesourcearrayisstillthesame:
>a;[1,3,5,7,"test"]
Thesplice()methodmodifiesthesourcearray. Itremovesaslice,returnsit,andoptionallyfillsthegapwithnewelements.Thefirsttwoparametersdefinethestartindexandlength(numberofelements)oftheslicetoberemoved;theotherparameterspassthenewvalues:
>b=a.splice(1,2,100,101,102);[3,5]>a;[1,100,101,102,7,"test"]
Fillingthegapwithnewelementsisoptional,soyoucanskipit:
>a.splice(1,3);[100,101,102]>a;[1,7,"test"]
ES6 array methodsArrays get a bunch of useful methods. Libraries such as lodash and underscore provided featuresmissinginthelanguagesofar.Withthenewhelpermethods,arraycreationandmanipulationismuchmorefunctionalandeasytocode.
Array.from
Convertingarray-likevaluestoarrayshasalwaysbeenabitofachallengeinJavaScript.Peoplehaveemployedseveralhacksandwrittenlibrariestojustletyouhandlearrayseffectively.
ES6introducesaveryhelpfulmethodtoconvertarray-likeobjectsanditerablevaluesintoarrays.Array-likevaluesareobjectsthathavealengthpropertyandindexedelements.Everyfunctionhasanimplicitargumentsvariablethatcontainsalistofallargumentspassedtothefunction.Thisvariableisanarray-likeobject.BeforeES6,theonlywaywecouldconverttheargumentsobjecttoanarraywastoiteratethroughitandcopythevaluesovertoanewarray:
functiontoArray(args){varresult=[];for(vari=0,len=args.length;i<len;i++){result.push(args[i]);}returnresult;}functiondoSomething(){varargs=toArray(arguments);console.log(args)}doSomething("hellow","world")//Array[//"hellow",//"world"
//]
Here,wearecreatinganewarraytocopyoverallelementsoftheargumentsobject.Thisiswastefulandneedsalotofunnecessarycoding.Array.from()isaconcisewaytoconvertarray-likeobjectsintoarrays.WecanconvertthisexampletoamoresuccinctoneusingArray.from():
functiondoSomething(){console.log(Array.from(arguments))}doSomething("hellow","world")//Array[//"hellow",//"world"//]
YoucanprovideyourownmappingschemewhilecallingArray.from()byprovidingamappingfunction.Thisfunctionisinvokedonalltheelementsoftheobjectandconvertsit.Thisisausefulconstructformanycommonusecases,forexample:
functiondoSomething(){console.log(Array.from(arguments,function(elem){returnelem+"mapped";}));
}
Inthisexample,wearedeconstructingtheargumentsobjectusingArray.fromandforeachelementinargumentsobject,wearecallingafunction.
CreatingarraysusingArray.of
CreatinganarrayusingtheArray()constructorcausesabitofaproblem.Theconstructorbehavesdifferentlybasedonthenumberandtypeofarguments.WhenyoupassasinglenumericvaluetotheArray()constructor,anarrayofundefinedelementsiscreated,withthevalueofthelengthassignedtothevalueoftheargument:
letarr=newArray(2)console.log(arr)//[undefined,undefined]console.log(arr.length)//2
Ontheotherhand,ifyou passonlyonenon-numericvalue,itbecomesthe onlyiteminthearray:
letarr=newArray("2")console.log(arr)//["2"]console.log(arr.length)//1
Thisisnotall.Ifyoupassmultiplevalues,theybecomeelementsofthearray:
letarr=newArray(1,"2",{obj:"3"})console.log(arr.length)//3
So,clearly,thereneedstobeabetterwaytocreatearraystoavoidsuchconfusion.ES6introducestheArray.ofmethodthatworksliketheArray()constructor, butguaranteesonestandardbehavior.Array.ofcreatesanarrayfromitsarguments,regardlessoftheirnumberandtype:
letarr=Array.of(1,"2",{obj:"3"})console.log(arr.length)//3
Array.prototypemethods
ES6introducesseveralinterestingmethodsaspartofarrayinstances.Thesemethodshelpwitharrayiterationandsearchingelementsinthearray,bothofwhichareveryfrequentandusefuloperations.
Herearethemethodsusedforiteratingoverarrays:
Array.prototype.entries()
Array.prototype.values()
Array.prorotype.keys()
Allthreemethodsreturnaniterator.ThisiteratorcanbeusedtocreatearraysusingArray.from()andcanbeusedinforloopsforiteration:
letarr=['a','b','c']for(constindexofarr.keys()){console.log(index)//012}for(constvalueofarr.values()){console.log(value)//abc}for(const[index,value]ofarr.entries()){console.log(index,value)}//0"a"//1"b"//2"c"
Similarly,therearenewmethodsforsearchingwithinarrays.Lookingfor anelementinanarrayusuallyinvolvediteratingthroughtheentirelistandcomparingthemwithavalue,astherewerenobuilt-inmethodsforthis.ThoughindexOf()andlastIndexOf()helpedfindasinglevalue,therewasnowaytofindelementsbasedoncomplexconditions.WithES6,thefollowingbuild-inmethodshelpwiththiskeyword.
Array.prototype.find
Array.prototype.findIndex
Boththesemethodsaccepttwoarguments-firstisthecallbackfunction(whichcontainsthepredicatecondition)and thesecondisanoptionalthiskeyword.Thecallbackacceptsthreearguments:thearrayelement,indexofthatelement,andthearray.Thecallbackreturnstrueiftheelementmatchesthepredicate:
letnumbers=[1,2,3,4,5,6,7,8,9,10];console.log(numbers.find(n=>n>5));//6console.log(numbers.findIndex(n=>n>5));//5
Function
Youalreadyknowthatfunctionsareaspecialdatatype.However,itturnsoutthatthere'smoretoitthanthat:functionsare actuallyobjects.Thereisabuilt-inconstructorfunctioncalledFunction()thatallowsforanalternative(butnotnecessarilyrecommended)waytocreateafunction.
Thefollowingexampleshowsthreewaystodefineafunction:
>functionsum(a,b){//functiondeclarationreturna+b;}
> sum(1, 2);3>varsum=function(a,b){//functionexpressionreturna+b;};>sum(1,2)3>varsum=newFunction('a','b','returna+b;');>sum(1,2)3
WhenusingtheFunction()constructor,youpasstheparameternamesfirst(asstrings)andthenthesourcecodeforthebodyofthefunction(againasastring).TheJavaScriptengineneedstoevaluatethesourcecodeyoupassandcreatethenewfunctionforyou.Thissourcecodeevaluationsuffersfromthesamedrawbacksastheeval()function,sodefiningfunctionsusingtheFunction()constructorshouldbeavoidedwhenpossible.
IfyouusetheFunction()constructortocreatefunctionsthathavelotsofparameters,bearinmindthattheparameterscanbepassedasasinglecomma-delimitedlist;so,forexample,thesearethesame:
>varfirst=newFunction('a,b,c,d','returnarguments;');>first(1,2,3,4);[1,2,3,4]>varsecond=newFunction(
'a, b, c','d','returnarguments;');>second(1,2,3,4);[1,2,3,4]>varthird=newFunction('a','b',
'c','d','returnarguments;');>third(1,2,3,4);[1,2,3,4]
Note
DonotusetheFunction()constructor.Aswitheval()andsetTimeout()(discussedlaterinthebook),alwaystrytostayawayfrompassingJavaScriptcodeasastring.
Propertiesoffunctionobjects
Likeanyotherobject,functionshaveaconstructorpropertythatcontainsareferencetotheFunction()constructor function.Thisistruenomatterwhichsyntaxyouusedtocreatethefunction:
>functionmyfunc(a){returna;}
> myfunc.constructor;functionFunction(){[nativecode]}
Functionsalsohavealengthproperty,whichcontainsthenumberofformalparametersthefunctionexpects:
>functionmyfunc(a,b,c){returntrue;}>myfunc.length;3
Usingtheprototypeproperty
Oneofthemostwidelyusedpropertiesoffunctionobjectsistheprototypeproperty.You'llseethispropertydiscussedindetailinthenextchapter,butfornow,let'sjustsay:
TheprototypepropertyofafunctionobjectpointstoanotherobjectItsbenefitsshineonlywhenyouusethisfunctionasaconstructorAllobjectscreatedwiththisfunctionkeepareferencetotheprototypepropertyandcanuseitspropertiesastheirown
Let'slookataquickexampletodemonstratetheprototypeproperty.Takeasimpleobjectthathasapropertynameandamethodsay()method:
varninja={name:'Ninja',say:function(){return'Iama'+this.name;
}};
Whenyoucreateafunction(evenonewithoutabody),youcanverifythat itautomaticallyhasaprototypepropertythatpointstoanewobject:
>functionF(){}>typeofF.prototype;"object"
Itgetsinterestingwhenyoumodifytheprototypeproperty.Youcanaddpropertiestoit,oryoucanreplacethedefaultobjectwithanyotherobject.Let'sassignninjato theprototype:
>F.prototype=ninja;
Now,andhere'swherethemagichappens,usingtheF()functionasaconstructorfunction,youcancreateanewobject,baby_ninja,whichwillhaveaccesstothepropertiesofF.prototype(whichpointstoninja)asifitwereitsown:
>varbaby_ninja=newF();>baby_ninja.name;"Ninja">baby_ninja.say();"IamaNinja"
Therewillbemuchmoreonthistopiclater.Infact,thenextchapterisallabouttheprototypeproperty.
Methodsoffunctionobjects
Functionobjects,beingadescendantofthetopparentobject,getthedefaultmethodssuchastoString().Wheninvokedonafunction,thetoString()methodreturnsthesourcecodeofthefunction:
>functionmyfunc(a,b,c){returna+b+c;}>myfunc.toString();"functionmyfunc(a,b,c){returna+b+c;}"
Ifyoutrytopeekintothesourcecodeofthebuilt-infunctions,you'llgetthe[nativecode]stringinsteadofthebodyofthefunction:
> parseInt.toString();"functionparseInt(){[nativecode]}"
Asyoucansee,youcanusetoString()todifferentiatebetweennativemethodsanddeveloper-
definedones.
Note
Thebehaviorofthefunction'stoString()isenvironmentdependent,anditdiffersamongbrowsersintermsofspacingandnewlines.
Callandapply
Functionobjectshavecall()andapply()methods.Youcanusethemtoinvokeafunctionandpassanyargumentstoit.
Thesemethodsalsoallowyourobjectstoborrowmethodsfromotherobjectsandinvokethemastheirown.Thisisaneasyandpowerfulwaytoreusecode.
Let'ssayyouhaveasome_objobject,whichcontainsthesay()method:
varsome_obj={name:'Ninja',say:function(who){return'Haya'+who+',Iama'+this.name;}};
Youcancallthesay()method,whichinternallyusesthis.nametogainaccesstoitsownnameproperty:
>some_obj.say('Dude');"HayaDude,IamaNinja"
Now,let'screateasimpleobject,my_obj,whichonlyhasanameproperty:
>varmy_obj={name:'Scriptingguru'};
Themy_objlikesthesome_objobject'ssay()methodsomuchthatitwantstoinvokeitasitsown.Thisispossibleusingthecall()methodofthesay()functionobject:
>some_obj.say.call(my_obj,'Dude');"HayaDude,IamaScriptingguru"
Itworked!Butwhathappenedhere?Youinvokedthecall()methodofthesay()functionobjectbypassingtwoparameters-themy_objobjectandtheDudestring.Theresultisthatwhensay()isinvoked,thereferencestothethisvaluethatitcontainspointtomy_obj.Thisway,this.namedoesn'treturnNinja,butScriptingguruinstead.
Ifyouhavemoreparameterstopasswheninvokingthecall()method,youjustkeepaddingthem:
some_obj.someMethod.call(my_obj,'a','b','c');
Ifyoudon'tpassanobjectasafirstparametertocall()oryoupassnull,theglobalobjectisassumed.
Themethodapply()worksthesamewayascall(),butwiththedifferencethatallparametersyouwanttopasstothemethodoftheotherobjectarepassedasanarray.Thefollowingtwolinesareequivalent:
some_obj.someMethod.apply(my_obj,['a','b','c']);some_obj.someMethod.call(my_obj,'a','b','c');
Continuingthepreviousexample,youcanusethefollowinglineofcode:
>some_obj.say.apply(my_obj,['Dude']);"HayaDude,IamaScriptingguru"
Theargumentsobjectrevisited
Inthepreviouschapter,youhaveseenhow,frominsideafunction,youhaveaccesstosomethingcalledarguments,whichcontainsthevaluesofalltheparameterspassedtothefunction:
>functionf(){return arguments;
}>f(1,2,3);[1,2,3]
Theargumentslookslikeanarray,butitisactuallyanarray-likeobject.Itresemblesanarraybecauseitcontainsindexedelementsandalengthproperty.However,thesimilarityendsthere,asargumentsdoesn'tprovideanyofthearraymethods,suchassort()or slice().
However,youcanconvertargumentstoanarrayandbenefitfromallthe arraygoodies.Here'swhatyoucando,practicingyournewly-learnedcall()method:
>functionf(){varargs=[].slice.call(arguments);returnargs.reverse();}
>f(1,2,3,4);[4,3,2,1]
Asyoucansee,youcanborrowslice()using[].sliceorthemoreverboseArray.prototype.slice.
Lexical this in arrow functionsWe discussed ES6 arrow functions and the syntax in detail in the last chapter. However, animportantaspectofarrowfunctionsisthattheybehavedifferentlyfromnormalfunctions.Thedifferenceissubtlebutimportant.Arrowfunctionsdonothavetheirownvalueofthis.Thevalueofthisinanarrowfunctionisinheritedfromtheenclosing(lexical)scope.
Functionshaveaspecialvariablethisthatreferstotheobjectviawhich themethodwasinvoked.Asthevalueofthisisdynamicallygivenbasedonthefunctioninvocation,itissometimescalleddynamicthis.Afunctionisexecutedintwoscopes-lexicalanddynamic.Alexicalscopeisascopethatsurroundsthefunctionscope,andthedynamicscopeisthescopethatcalledthefunction(usuallyanobject)
InJavaScript,traditionalfunctionsplayseveralroles.Theyarenon-methodfunctions(akasubroutinesorfunctions),methods(partofanobject),andconstructors.Whenfunctionsdothedutyofasubroutine,thereisasmallproblemduetodynamicthis.Assubroutinesarenotcalledonanobject,thevalueofthisisundefinedinastrictmodeandsettothe globalscopeotherwise.Thismakeswritingcallbacksdifficult.Considerthefollowingexample:
vargreeter={default:"Hello",greet:function(names){names.forEach(function(name){console.log(this.default+name);//Cannotreadproperty'default'ofundefined})}}console.log(greeter.greet(['world','heaven']))
WearepassingasubroutinetotheforEach()functiononthenamesarray.Thissubroutinehasanundefinedvalueofthis,andunfortunately,itdoesnothaveaccesstothisoftheoutermethodgreet.Clearly,thissubroutineneedsalexicalthis,derivethisfromthesurroundingscopeofthegreetmethod.Traditionally,tofixthislimitation,weassignthelexicalthisintoavariable,whichisthenaccessibletothesubroutineviaclosure.
Wecanfixtheearlierexampleasfollows:
vargreeter={default:"Hello",greet:function(names){letthat=thisnames.forEach(function(name){console.log(that.default+name);})}
}console.log(greeter.greet(['world','heaven']))
Thisisareasonablehacktosimulatelexicalthis.However,theproblemwithsuchhacksisthatitcreatestoomuchnoiseforthepersonwritingorreviewingthiscode.First,youhavetounderstandthequirkofthebehaviorofthis.Evenifyouunderstandthisbehaviorwell,youwillneedtocontinuouslyremainonthelookoutforsuchhacksinyourcode.
Arrowfunctionshavelexicalthisanddonotrequiresuchahack.Theyaremoresuitedassubroutinesbecauseofthis.Wecancoverttheprecedingexampletouse lexicalthisusingthearrowfunction:
vargreeter={default:"Hello",greet:function(names){
names.forEach(name=> {console.log(this.default+name);//lexical'this'availableforthissubroutine})}}console.log(greeter.greet(['world','heaven']))
Inferringobjecttypes
Youcanseethatyouhavethisarray-likeargumentsobjectlookingsomuchlikeanarrayobject.Howcanyoureliablytellthedifferencebetween thetwo?Additionally,typeofreturnsanobjectwhenusedwitharrays.Therefore,howcanyoutellthedifferencebetweenanobjectandanarray?
ThesilverbulletistheObjectobject'stoString()method.Itgivesyoutheinternalclassnameusedtocreateagivenobject:
>Object.prototype.toString.call({});"[objectObject]">Object.prototype.toString.call([]);"[objectArray]"
YouhavetocalltheoriginaltoString()methodasdefinedintheprototypeoftheObjectconstructor.Otherwise,ifyoucalltheArrayfunction'stoString(),itwillgiveyouadifferentresult,asit'sbeenoverriddenforthespecificpurposesofthearrayobjects:
>[1,2,3].toString();"1,2,3"
Theprecedingcodeissameas:
>Array.prototype.toString.call([1,2,3]);"1,2,3"
Let'shavesomemorefunwithtoString().Makeahandyreferencetosavetyping:
> var toStr = Object.prototype.toString;
Thefollowingexampleshowshowwecandifferentiatebetweenanarray andthearray-likeobjectarguments:
>(function(){returntoStr.call(arguments);}());"[objectArguments]"
YoucaneveninspectDOMelements:
>toStr.call(document.body);"[objectHTMLBodyElement]"
Boolean
Yourjourneythroughthe built-inobjectsinJavaScriptcontinues,andthenextthreearefairlystraightforward.TheyareBoolean,number,andstring.Theymerelywraptheprimitivedatatypes.
YoualreadyknowalotaboutBooleansfromChapter2,PrimitiveDataTypes,Arrays,Loops,andConditions.Now,let'smeettheBoolean()constructor:
>varb=newBoolean();
It'simportanttonotethatthiscreatesanewobject,b,andnotaprimitiveBooleanvalue.Togettheprimitivevalue,youcancallthevalueOf()method(inheritedfromObjectclassandcustomized):
>varb=newBoolean();>typeofb;"object">typeofb.valueOf();"boolean">b.valueOf();false
Overall,objectscreatedwiththeBoolean()constructorarenottoouseful,astheydon'tprovideanymethodsorpropertiesotherthantheinheritedones.
TheBoolean()function,whencalledasanormalfunctionwithoutnew,convertsnon-BooleanstoBooleans(whichislikeusingadoublenegation!!value):
>Boolean("test");true>Boolean("");false>Boolean({});true
Apartfromthesixfalsevalues,everythingelseistrueinJavaScript,includingallobjects.ThisalsomeansthatallBooleanobjectscreatedwithnewBoolean()arealsotrue,astheyareobjects:
>Boolean(newBoolean(false));true
Thiscanbeconfusing,andsinceBooleanobjectsdon'tofferanyspecialmethods,it'sbesttojuststickwithregularprimitiveBooleanvalues.
Number
SimilartoBoolean(),theNumber()functioncanbeusedas:
Aconstructorfunction(withnew)tocreateobjects.Anormalfunctioninordertotrytoconvertanyvaluetoanumber.Thisissimilartotheuseof parseInt() or parseFloat():
>varn=Number('12.12');>n;12.12>typeofn;"number">varn=newNumber('12.12');>typeofn;"object"
Asfunctionsareobjects,theycanalsohaveproperties.TheNumber()functionhasconstantbuilt-inpropertiesthatyoucannotmodify:
>Number.MAX_VALUE;1.7976931348623157e+308>Number.MIN_VALUE;5e-324>Number.POSITIVE_INFINITY;Infinity>Number.NEGATIVE_INFINITY;-Infinity>Number.NaN;NaN
Thenumberobjectsprovidethreemethods-toFixed(),toPrecision(),andtoExponential()(seeAppendixC,Built-inObjects,formoredetails):
>varn=newNumber(123.456);>n.toFixed(1);"123.5"
NotethatyoucanusethesemethodswithoutexplicitlycreatingaNumberobjectfirst.Insuchcases,theNumberobjectiscreated(anddestroyed)foryoubehindthescenes:
>(12345).toExponential();"1.2345e+4"
Likeallobjects,theNumberobjectalsoprovidethetoString()method.WhenusedwithNumberobject,thismethodacceptsanoptionalradixparameter(10beingthedefault):
>varn=newNumber(255);>n.toString();"255"
>n.toString(10);"255">n.toString(16);"ff">(3).toString(2);"11">(3).toString(10);"3"
String
YoucanusetheString()constructorfunctiontocreatestringobjects.Stringobjectsprovideconvenientmethodsfortextmanipulation.
Here'sanexamplethatshowsthedifferencebetweenaStringobjectandaprimitivestringdatatype:
>varprimitive='Hello';>typeofprimitive;"string">varobj=newString('world');>typeofobj;"object"
AStringobjectissimilartoanarrayofcharacters.Stringobjectshaveanindexedpropertyforeachcharacter(introducedinES5,butlongsupportedinmanybrowsers,exceptoldIEs),andtheyalsohavealengthproperty.
>obj[0];"w">obj[4];
"d">obj.length;5
ToextracttheprimitivevaluefromtheStringobject,youcanusethevalueOf()ortoString()methodinheritedfromObject.You'llprobablyneverneedtodothis,astoString()iscalledbehindthescenesifyouuseanobjectinaprimitivestringcontext:
>obj.valueOf();"world">obj.toString();
"world">obj+"";"world"
Theprimitivestringsarenotobjects,sotheydon'thaveanymethodsorproperties.However,JavaScriptalsooffersyouthesyntaxtotreatprimitivestringsasobjects(justlikeyoualreadysawwithprimitivenumbers).
Inthefollowingexample,Stringobjectsarebeingcreated(andthendestroyed)behindthesceneseverytimeyoutreataprimitivestringasifitwereanobject:
>"potato".length;6
> "tomato"[0];"t"
>"potatoes"["potatoes".length-1];"s"
HereisonefinalexampletoillustratethedifferencebetweenaprimitivestringandaStringobject.Inthisexample,weareconvertingthemtoBoolean.Theemptystringisafalsyvalue,butanystringobjectistruthy(becauseallobjectsaretruthy):
>Boolean("");false>Boolean(newString(""));true
Similar to Number() and Boolean(), if you use the String() function without new, it convertsthe parameter to a primitive:
>String(1);"1"
IfyoupassanobjecttoString(),thisobject'stoString()methodwillbecalledfirst:
>String({p:1});"[objectObject]">String([1,2,3]);"1,2,3">String([1,2,3])===[1,2,3].toString();true
Afewmethodsofstringobjects
Let'sexperimentwithafewofthemethodsyoucancallonstringobjects(seeAppendixC,Built-inObjects,forthecompletelist).
Startoffbycreatingastringobject:
>vars=newString("Couchpotato");
ThetoUpperCase()andtoLowerCase()methodstransformthecapitalizationofthestring:
>s.toUpperCase();"COUCHPOTATO">s.toLowerCase();"couchpotato"
ThecharAt()methodtellsyouthecharacterfoundatthepositionyouspecify,whichisthesameasusingsquarebrackets(treatingastringasanarrayofcharacters):
>s.charAt(0);"C">s[0];"C"
Ifyoupassanon-existentpositiontocharAt(),yougetanemptystring:
>s.charAt(101);""
TheindexOf()methodallowsyoutosearchwithinastring.Ifthereisamatch,themethodreturnsthepositionatwhichthefirstmatchisfound.Thepositioncountstartsat0,sothesecondcharacterinCouchisoatposition1:
>s.indexOf('o');1
Youcanoptionallyspecifywhere(atwhatposition)tostartthesearch.Thefollowingfindsthesecondo,becauseindexOf()isinstructedtostartthesearchatposition2:
>s.indexOf('o',2);7
ThelastIndexOf()startsthesearchfromtheendofthestring(butthepositionofthematchisstillcountedfromthebeginning):
>s.lastIndexOf('o');11
You can search , not only for characters, but also for strings, and the search is case sensitive:
>s.indexOf('Couch');0
Ifthereisnomatch,thefunctionreturnsposition-1:
>s.indexOf('couch');-1
Foracase-insensitivesearch,youcantransformthestringtolowercasefirstandthensearch:
>s.toLowerCase().indexOf('couch'.toLowerCase());0
Ifyouget0,thismeansthatthematchingpartofthestringstartsatposition0.Thiscancauseconfusionwhenyoucheckwithif,becauseifconvertstheposition0toaBooleanfalsevalue.So,whilethisissyntacticallycorrect,itislogicallywrong:
if(s.indexOf('Couch')){...}
TheproperwaytocheckwhetherastringcontainsanotherstringistocomparetheresultofindexOf()tothenumber-1:
if(s.indexOf('Couch')!==-1){...}
Theslice()andsubstring()returnapieceofthestringwhenyouspecifythestartandendpositions:
>s.slice(1,5);"ouch">s.substring(1,5);"ouch"
Notethatthesecondparameteryoupassistheendposition,notthelength ofthepiece.Thedifferencebetweenthesetwomethodsishowtheytreatnegativearguments.substring()treatsthemaszeros,whileslice()addsthemtothelengthofthestring.So,ifyoupassparameters(1,-1)tobothmethods,it'sthesameassubstring(1,0)andslice(1,s.length-1):
>s.slice(1,-1);"ouchpotat">s.substring(1,-1);"C"
There'salsothenon-standardmethodsubstr(),butyoushouldtrytoavoiditinfavorofsubstring().
Thesplit()methodcreatesanarrayfromthestringusinganotherstringthatyoupassasaseparator:
>s.split("");["Couch","potato"]
Thesplit()methodistheoppositeofthejoin()method,whichcreatesastringfromanarray:
>s.split('').join('');"Couchpotato"
Theconcat()gluesstringstogether,inthesamewayinwhichthe+operatordoesforprimitivestrings:
>s.concat("es");"Couch potatoes"
Notethatwhilesomeoftheprecedingmethodsdiscussedreturnnewprimitivestrings,noneofthemmodifythesourcestring.Afterallthemethodcallslistedpreviously,theinitialstringisstillthesame:
>s.valueOf();"Couchpotato"
YouhaveseenhowtouseindexOf()andlastIndexOf()tosearchwithinstrings,buttherearemorepowerfulmethods(search(),match(),andreplace())thattakeregularexpressionsasparameters.You'llseetheselaterintheRegExp()constructorfunction.
Atthispoint,you'redonewithallofthedatawrapperobjects,solet'smoveontotheutilityobjectsMath,Date,andRegExp.
Math
Mathisalittledifferentfromtheotherbuilt-inglobalobjectsyouhaveseenpreviously.It'snotafunction,and,therefore,cannotbeusedwithnewtocreateobjects.Mathisabuilt-inglobalobjectthatprovidesanumberofmethodsandpropertiesformathematicaloperations.
TheMathobject'spropertiesareconstants,soyoucan'tchangetheirvalues.Theirnamesareallinuppercasetoemphasizethedifferencebetweenthemandanormalproperty(similartotheconstantpropertiesoftheNumber()constructor).Let'sseeafewoftheseconstantproperties:
TheconstantPI:
>Math.PI;3.141592653589793
Squarerootof2:
>Math.SQRT2;1.4142135623730951
Euler'sconstant:
>Math.E;2.718281828459045
Naturallogarithmof2:
>Math.LN2;0.6931471805599453
Naturallogarithmof10:
>Math.LN10;2.302585092994046
Now,youknowhowtoimpressyourfriendsthenexttimethey(forwhateverreason)startwondering,"Whatwasthevalueofe?Ican'tremember."JusttypeMath.Eintheconsoleandyouhavetheanswer.
Let'stakealookatsome ofthemethodstheMathobjectprovides(thefulllistisinAppendixC,Built-inObjects).
Generatingrandomnumbers:
>Math.random();0.3649461670235814
Therandom()functionreturnsanumberbetween0and1,soifyouwantanumberbetween,let'ssay,0and100,youcanusethefollowinglineofcode:
>100*Math.random();
Fornumbersbetweenanytwovalues,usetheformula((max-min)*Math.random())+min.Forexample,arandomnumberbetween2and10canbeobtainedusingtheformulaasfollows:
>8*Math.random()+2;9.175650496668485
Ifyouonlyneedaninteger,youcanuseoneofthefollowingroundingmethods:
floor()torounddownceil()toroundupround()toroundtothenearest
Forexample,togeteither0or1,youcanusethefollowinglineofcode:
>Math.round(Math.random());
Ifyouneedthelowestorthehighestamongasetofnumbers,youhavethemin()andmax()methods.So,ifyouhave aformonapagethatasksforavalidmonth,you canmakesurethatyoualwaysworkwithsanedata(avaluebetween1and12):
>Math.min(Math.max(1,input),12);
TheMathobjectalsoprovidestheabilitytoperformmathematicaloperationsforwhichyoudon'thaveadesignatedoperator.Thismeansthatyoucanraisetoapowerusingpow(),findthesquarerootusingsqrt(),andperformallthetrigonometricoperations-sin(),cos(),atan(),andsoon.
Forexample,tocalculate2tothepowerof8,youcanusethefollowinglineofcode:
>Math.pow(2,8);256
To calculate the square root of 9, you can use the following line of code:
>Math.sqrt(9);3
Date
Date()isaconstructorfunctionthatcreatesdate objects.Youcancreateanewobjectbypassing:
Nothing (defaults to today's date)Adate-likestringSeparatevaluesfor day,month,time,andsoonAtimestamp
Hereisanobjectinstantiatedwithtoday'sdate/time(usingthebrowser'stimezone):
> new Date();WedFeb27201323:49:28GMT-0800(PST)
TheconsoledisplaystheresultofthetoString()methodcalledontheDateobject,soyougetthislongstringWedFeb27201323:49:28GMT-0800(PST)asarepresentationofthedateobject.
HereareafewexamplesofusingstringstoinitializeaDateobject.Notehowmanydifferentformatsyoucanusetospecifythedate:
>newDate('20151112');ThuNov12201500:00:00GMT-0800(PST)>newDate('112016');FriJan01201600:00:00GMT-0800(PST)>newDate('1mar20165:30');TueMar01201605:30:00GMT-0800(PST)
TheDateconstructorcanfigureoutadatefromdifferentstrings,butthisisnotreallyareliablewayofdefiningaprecisedate,forexample,whenpassinguserinputtotheconstructor.AbetterwayistopassnumericvaluestotheDate()constructorrepresenting:
YearMonth-0(January)to11(December)Day-1to31Hour-0to23Minutes-0to59Seconds-0to59Milliseconds-0to999
Let'slookatsomeexamples.
Passingalltheparametersbywritingthefollowinglineofcode:
>newDate(2015,0,1,17,05,03,120);TueJan01201517:05:03GMT-0800(PST)
Passingdateandhourbywritingthefollowinglineofcode:
>newDate(2015,0,1,17);TueJan01201517:00:00GMT-0800(PST)
Watchoutforthefactthatthemonthstartsfrom0,so1isFebruary:
>newDate(2016,1,28);SunFeb28201600:00:00GMT-0800(PST)
Ifyoupassavaluegreaterthantheoneallowed,yourdateoverflowsforward.Asthere'snoFebruary30in2016,thismeansithastobeMarch1(2016isaleapyear):
>newDate(2016,1,29);MonFeb29201600:00:00GMT-0800(PST)>newDate(2016,1,30);TueMar01201600:00:00GMT-0800(PST)
Similarly,December32becomesJanuary1ofthenextyear:
>newDate(2012,11,31);MonDec31201200:00:00GMT-0800(PST)>newDate(2012,11,32);TueJan01201300:00:00GMT-0800(PST)
Finally,adateobjectcanbeinitializedwithatimestamp(thenumberofmillisecondssincetheUNIXepoch,where0millisecondsisJanuary1,1970):
>newDate(1357027200000);TueJan01201300:00:00GMT-0800(PST)
IfyoucallDate()withoutnew,yougetastringrepresentingthecurrentdate,whetherornotyoupassanyparameters.Thefollowingexamplegivesthecurrenttime(currentwhenthisexamplewasrun):
>Date();WedFeb27201323:51:46GMT-0800(PST)>Date(1,2,3,"itdoesn'tmatter");WedFeb27201323:51:52GMT-0800(PST)>typeofDate();"string">typeofnewDate();"object"
Methodstoworkwithdateobjects
Onceyou'vecreatedadateobject,therearelotsofmethodsyoucancallonthatobject.Mostofthemethodscanbedividedintoset*()andget*()methods,forexample,getMonth(),setMonth(),getHours(),setHours(),andsoon.Let'sseesomeexamples.
Creatingadateobjectbywritingthefollowingcode:
>vard=newDate(2015,1,1);>d.toString();SunFeb01201500:00:00GMT-0800(PST)
SettingthemonthtoMarch(monthsstartfrom0):
>d.setMonth(2);1425196800000>d.toString();SunMar01201500:00:00GMT-0800(PST)
Gettingthemonthbywritingthefollowingcode:
>d.getMonth();2
Inadditiontoallthemethodsofdateobjects,therearealsotwomethods(plusonemoreaddedinES5)thatarepropertiesoftheDate()function/object.Thesedonotneed adateobject;theyworkjustliketheMathobjectmethods.Inclass-basedlanguages,suchmethodswouldbecalledstaticbecausetheydon'trequireaninstance.
TheDate.parse()methodtakesastringandreturnsatimestamp:
>Date.parse('Jan11,2018');1515657600000
The Date.UTC() method takes all the parameters for year, month, day, and so on, and produces atimestamp inUniversal Time (UT):
>Date.UTC(2018,0,11);1515628800000
AsthenewDate()constructorcanaccepttimestamps,youcanpasstheresultofDate.UTC()toit.Usingthefollowingexample,youcanseehowUTC()workswithUniversalTime,whilenewDate()workswithlocaltime:
>newDate(Date.UTC(2018,0,11));WedJan10201816:00:00GMT-0800(PST)>newDate(2018,0,11);ThuJan11201800:00:00GMT-0800(PST)
TheES5additiontotheDateconstructoristhenow()method,whichreturnsthecurrenttimestamp.ItprovidesamoreconvenientwaytogetthetimestampinsteadofusingthegetTime()methodonaDateobjectasyouwouldinES3:
>Date.now();1362038353044
>Date.now()===newDate().getTime();true
Youcanthinkoftheinternalrepresentationofthe datebeinganintegertimestampandallothermethodsbeingsugarontopofit.So,itmakessensethatvalueOf()isatimestamp:
>newDate().valueOf();1362418306432
Also,datescasttointegerswiththe+operator:
>+newDate();1362418318311
Calculatingbirthdays
Let'slookatonefinalexampleofworkingwithDateobjects.Iwascuriousaboutwhichdaymybirthdayfallsonin2016:
>vard=newDate(2016,5,20);>d.getDay();1
Startingthecountfrom0 (Sunday),1meansMonday.Isthatso?
>d.toDateString();"MonJun202016"
ok,goodtoknow,butMondayisnotnecessarilythebestdayforaparty.So,howaboutaloopthatshowshowmanytimesJune20isaFridayfromyear2016toyear3016,orbetteryet,let'sseethedistributionofallthedaysoftheweek.Afterall,withalltheprogressinDNAhacking,we'reallgoingtobealiveandkickingin3016.
First,let'sinitializeanarraywithsevenelements,oneforeachdayoftheweek.Thesewillbeusedascounters.Then,asaloopgoesupto3016,let'sincrementthecounters:
varstats=[0,0,0,0,0,0,0];
Hereistheloop:
for(vari=2016;i<3016;i++){stats[newDate(i,5,20).getDay()]++;}
Hereistheresult:
>stats;[140, 146, 140, 145, 142, 142, 145]
142Fridaysand145Saturdays.Woo-hoo!
RegExp
Regularexpressionsprovideapowerfulwaytosearchandmanipulatetext.Differentlanguageshavedifferentimplementations(thinkdialects)oftheregularexpressionsyntax.JavaScriptusesthePerl5syntax.
Insteadofsayingregular expression,peopleoftenshortenittoregexorregexp.
Aregularexpressionconsistsof:
ApatternyouusetomatchtextZeroormoremodifiers(alsocalledflags)thatprovidemoreinstructionsonhowthepatternshouldbeused
Thepatterncanbeassimpleasliteraltexttobematchedverbatim,butthat'srare,andinsuchcasesyou'rebetteroffusingindexOf().Mostofthetime,thepatternismorecomplexandcouldbedifficulttounderstand.Masteringregularexpressions'patternsisalargetopic,whichwon'tbediscussedinfulldetailhere.Instead,you'llseewhatJavaScriptprovidesintermsofsyntax,objects, and methods in order to support the use of regular expressions. You can also refer toAppendixD,RegularExpressions,tohelpyouwhenyou'rewritingpatterns.
JavaScriptprovidestheRegExp()constructor,whichallowsyoutocreateregularexpressionobjects:
>varre=newRegExp("j.*t");
Thereisalsothemoreconvenientregexpliteralnotation:
>varre=/j.*t/;
Intheprecedingexample,j.*tistheregularexpressionpattern.Itmeans"matchesanystringthatstartswithj,endswitht,andhaszeroormorecharactersinbetween".Theasterisk(*)means"zeroormoreofthepreceding,"andthedot(.)means"anycharacter".ThepatternneedstobequotedwhenpassedtoaRegExp()constructor.
PropertiesofRegExpobjects
Regularexpressionobjectshavethefollowingproperties:
global:Ifthispropertyisfalse,whichisthedefault,thesearchstopswhenthefirstmatchisfound.Setthistotrueifyouwantallmatches.ignoreCase:Whenthematchiscaseinsensitive,thispropertydefaultstofalse(meaningthedefaultisacase-sensitivematch).multiline:Searchmatchesthatmayspanovermorethanonelinedefaulttofalse.lastIndex:Thepositionatwhichtostartthesearch;thisdefaultsto0.
source:ThiscontainstheRegExppattern.
Noneoftheseproperties,exceptforlastIndex,canbechangedoncetheobjecthasbeencreated.
Thefirstthreeitemsintheprecedinglistrepresenttheregexmodifiers.Ifyoucreatearegexobjectusingtheconstructor,youcanpassanycombinationofthefollowingcharactersasasecondparameter:
gforglobaliforignoreCasemformultiline
Theseletterscanbeinanyorder.Ifaletterispassed,thecorrespondingmodifierpropertyissettotrue.Inthefollowingexample,allmodifiersaresettotrue:
>varre=newRegExp('j.*t','gmi');
Let'sverify:
>re.global;true
Once set, the modifier cannot be changed:
>re.global=false;>re.global;true
Tosetanymodifiersusingtheregexliteral,youaddthemaftertheclosingslash:
>varre=/j.*t/ig;>re.global;true
MethodsofRegExpobjects
Regexobjectsprovidetwomethodsyoucanusetofindmatches-test()andexec().Theybothacceptastringparameter.Thetest()methodreturnsaBoolean(truewhenthere'samatch,falseotherwise),whileexec()returnsanarrayofmatchedstrings.Obviously,exec()isdoingmorework,sousetest()onlyifyoureallyneedtodosomethingwiththematches.Peopleoftenuseregularexpressionstovalidatedata.Inthiscase,test()shouldbeenough.
Inthefollowingexample,thereisnomatchbecauseofthecapitalJ:
>/j.*t/.test("Javascript");false
Acase-insensitivetestgivesapositiveresult:
>/j.*t/i.test("Javascript");true
Thesametestusingexec()returnsanarray,andyoucanaccessthefirstelementasshownhere:
>/j.*t/i.exec("Javascript")[0];"Javascript"
Stringmethodsthatacceptregularexpressionsasarguments
Previouslyinthischapter,youlearnedaboutstringobjectsandhowyoucanusetheindexOf()andlastIndexOf()methodstosearchwithintext.Usingthesemethods,youcanonlyspecifyliteralstringpatternstosearch.Amorepowerfulsolutionwouldbetouseregularexpressionstofindtext.Stringobjectsofferyouthisability.
Stringobjectsprovidethefollowingmethodsthatacceptregularexpressionobjectsasparameters:
match():Returnsanarrayofmatchessearch():Returnsthepositionofthefirstmatchreplace():Allowsyoutosubstitutematchedtextwithanotherstringsplit():Acceptsaregexpwhensplittingastringintoarrayelements
search()andmatch()
Let'slookatsomeexamplesofusingthesearch()andmatch()methods. First,youcreateastringobject:
>vars=newString('HelloJavaScriptWorld');
Usingmatch(),yougetanarraycontainingonlythefirstmatch:
>s.match(/a/);["a"]
Usingthegmodifier,youperformaglobalsearch,sotheresultarraycontainstwoelements:
>s.match(/a/g);["a","a"]
Acase-insensitivematchisasfollows:
>s.match(/j.*a/i);["Java"]
Thesearch()methodgivesyouthepositionofthematchingstring:
>s.search(/j.*a/i);
5
replace()
Thereplace()methodallowsyoutoreplacethematchedtextwithsomeotherstring.Thefollowingexampleremovesallcapitalletters(itreplacesthemwithblankstrings):
>s.replace(/[A-Z]/g,'');"elloavacriptorld"
Ifyouomitthegmodifier,you'reonlygoingtoreplacethefirstmatch:
>s.replace(/[A-Z]/,'');"elloJavaScriptWorld"
Whenamatchisfound,ifyouwanttoincludethematchedtextinthereplacementstring,youcanaccessitusing$&.Here'showtoaddanunderscorebeforethematchwhilekeepingthematch:
>s.replace(/[A-Z]/g,"_$&");"_Hello_Java_Script_World"
Whentheregularexpressioncontainsgroups(denotedbyparentheses),thematchesofeachgroupareavailableas$1forthefirstgroup,$2thesecond,andsoon:
>s.replace(/([A-Z])/g,"_$1");"_Hello_Java_Script_World"
Imagineyouhavearegistrationformonyourwebpagethatasksforane-mailaddress,username,and password. The user enters their e-mail IDs, and then, your JavaScript kicks in and suggeststheusername,takingitfromthee-mailaddress:
>varemail="[email protected]";>varusername=email.replace(/(.*)@.*/,"$1");>username;"stoyan"
Replacecallbacks
Whenspecifyingthereplacement,youcanalsopassafunctionthatreturnsastring.Thisgivesyoutheabilitytoimplementanyspeciallogicyoumayneedbeforespecifying thereplacements:
>functionreplaceCallback(match){return"_"+match.toLowerCase();}
>s.replace(/[A-Z]/g,replaceCallback);"_hello_java_script_world"
Thecallbackfunctionreceivesanumberofparameters(thepreviousexampleignoresallbutthefirstone):
ThefirstparameteristhematchThelastisthestringbeingsearchedTheonebeforelastisthepositionofthematchTherestoftheparameterscontainanystringsmatchedbyanygroupsinyourregexpattern
Let's test this. First, let's create a variable to store the entire arguments array passed to thecallbackfunction:
>varglob;
Next,definearegularexpressionthathasthreegroupsandmatchese-mailaddressesintheformatsomething@something.something:
>varre=/(.*)@(.*)\.(.*)/;
Finally,let'sdefineacallbackfunctionthatstorestheargumentsinglobandthenreturnsthereplacement:
varcallback=function(){glob=arguments;returnarguments[1]+'at'+arguments[2]+'dot'+arguments[3];};
Now,performatest:
>"[email protected]".replace(re,callback);"stoyanatphpieddotcom"
Here'swhatthecallbackfunctionreceivedasarguments:
>glob;["[email protected]","stoyan","phpied","com",0,"[email protected]"]
split()
Youalreadyknowaboutthesplit()method,whichcreatesanarrayfromaninputstringandadelimiterstring.Let'stakeastringofcomma-separatedvaluesandsplitit:
>varcsv='one,two,three,four';>csv.split(',');["one","two","three","four"]
Becausetheinputstringhappenstohaverandominconsistentspacesbeforeandafterthecommas,thearrayresulthasspacestoo.Witharegularexpression,youcanfixthisusing\s*,whichmeanszeroormorespaces:
>csv.split(/\s*,\s*/);
["one","two","three","four"]
PassingastringwhenaRegExpisexpected
Onelastthingtonoteisthatthefourmethodsthat youhavejustseen(split(),match(),search(),andreplace())canalsotakestringsasopposedtoregularexpressions.Inthiscase,thestringargumentisusedtoproduceanewregexasifitwerepassedtonewRegExp().
Anexampleofpassinga stringtoreplaceisshownasfollows:
>"test".replace('t','r');"rest"
Theprecedinglinesofcodearethesameasthefollowingone:
>"test".replace(newRegExp('t'),'r');"rest"
When you pass a string, you cannot set modifiers the way you do with a normal constructor orregexliteral.There'sacommonsourceoferrorswhenusingastringinsteadofaregularexpressionobjectforstringreplacements,andit'sduetothefactthatthegmodifierisfalsebydefault.Theoutcomeisthatonlythefirststringisreplaced,whichisinconsistentwithmostotherlanguagesandalittleconfusing.Hereisanexample:
>"pool".replace('o','*');"p*ol"
Mostlikely,youwanttoreplacealloccurrences:
>"pool".replace(/o/g,'*');"p**l"
Errorobjects
Errorshappen,andit'sgoodtohavethemechanismsinplacesothatyourcodecanrealizethattherehasbeenanerrorconditionandcanrecoverfromitinagracefulmanner.JavaScriptprovidesthetry,catch,andfinallystatementstohelpyoudealwitherrors.Ifanerroroccurs,anerrorobjectisthrown.Errorobjectsarecreatedusingoneofthesebuilt-inconstructors-EvalError,RangeError,ReferenceError,SyntaxError,TypeError,andURIError.AlltheseconstructorsinheritfromError.
Let'sjustcauseanerrorandseewhathappens.What'sasimplewaytocauseanerror?Justcallafunctionthatdoesn'texist.Typethisintotheconsole:
>iDontExist();
You'll get something like the following:
Thedisplayoferrorscanvarygreatlybetweenbrowsersandotherhostenvironments.Infact,mostrecentbrowserstendtohidetheerrorsfromtheusers.However,youcannotassumethatallofyourusershavedisabledthedisplayoferrors,anditisyourresponsibilitytoensureanerror-freeexperienceforthem.Thepreviouserrorpropagatedtotheuser,becausethecodedidn'ttrytotrap(catch)thiserror.Thecodedidn'texpecttheerrorandwasnotpreparedtohandleit.Fortunately,it'strivialtotraptheerror.Allyouneedisthetrystatementfollowedbyacatchstatement.
Thiscodehidestheerrorfromtheuser:
try{iDontExist();}catch(e){//donothing}
Here you have:
Thetrystatementfollowedbyablockofcode.Thecatchstatementfollowedbyavariablenameinparenthesesandanotherblockofcode.
Therecanbeanoptionalfinallystatement(notusedinthisexample)followedbyablockofcode,whichisexecutedregardlessofwhethertherewasanerrorornot.
Inthepreviousexample, thecodeblockthatfollowsthecatchstatementdidn'tdoanything.However,thisistheplacewhereyouputthecodethatcanhelprecoverfromtheerror,oratleastgivefeedbacktotheuserthatyourapplicationisawarethattherewasaspecialcondition.
Thevariableeintheparenthesesafterthecatchstatementcontainsanerrorobject.Likeanyotherobject,itcontainspropertiesandmethods.Unfortunately,differentbrowsersimplementthesemethodsandpropertiesdifferently,buttherearetwopropertiesthatareconsistentlyimplemented-e.nameande.message.
Let'strythiscodenow:
try{iDontExist();}catch(e){alert(e.name+':'+e.message);}finally{alert('Finally!');}
This will present an alert() showing e.name and e.message and then another alert() sayingFinally!.
InFirefoxandChrome,thefirstalertwillsayReferenceError:iDontExistisnotdefined.InInternetExplorer,itwillbeTypeError:Objectexpected.Thistellsustwothings:
Thee.namemethodcontainsthenameoftheconstructorthatwasusedtocreatetheerrorobjectAstheerrorobjectsarenotconsistentacrosshostenvironments(browsers),itwouldbesomewhattrickytohaveyourcodeactdifferentlydependingonthetypeoferror(thevalueof e.name)
YoucanalsocreateerrorobjectsyourselfusingnewError()oranyoftheothererrorconstructorsandthenlettheJavaScriptengineknowthatthere'sanerroneousconditionusingthethrowstatement.
Forexample,imagineascenariowhereyoucallthemaybeExists()functionandafterthatmakecalculations.Youwanttotrapallerrorsinaconsistentway,nomatterwhethertheerroristhatmaybeExists()doesn'texistorthatyourcalculationsfoundaproblem.Considerthefollowingcode:
try{vartotal=maybeExists();if(total===0){thrownewError('Divisionbyzero!');}else{alert(50/total);}}catch(e){alert(e.name+':'+e.message);}finally{alert('Finally!');}
ThiscodewillalertdifferentmessagesdependingonwhetherornotmaybeExists()isdefinedandthevaluesitreturns:
IfmaybeExists()doesn'texist,yougetReferenceError:maybeExists()isnotdefinedin
FirefoxandTypeError:ObjectexpectedinIEIfmaybeExists()returns0,yougetError: Divisionbyzero!IfmaybeExists()returns2,yougetanalertthatsays25
Inallcases,therewillbeasecondalertthatsaysFinally!.
Insteadofthrowingagenericerror,thrownewError('Divisionbyzero!'),youcanbemorespecificifyouchooseto,forexample,throwthrownewRangeError('Divisionbyzero!').Alternatively,youdon'tneedaconstructor;youcansimplythrowanormalobject:
throw{name:"MyError",message:"OMG!Somethingterriblehashappened"}
Thisgivesyoucross-browsercontrolovertheerrorname.
ExercisesLets solve the following exercise:
1. Lookatthefollowingcode:
functionF(){functionC(){returnthis;
}returnC();}varo=newF();
Doesthevalueofthisrefertotheglobalobjectortheobjecto?2. What'stheresultofexecutingthispieceofcode?
functionC(){this.a=1;returnfalse;}console.log(typeofnewC());
3. What'stheresultofexecutingthefollowingpieceofcode?
>c=[1,2,[1,2]];> c.sort();
>c.join('--');>console.log(c);
4. ImaginetheString()constructordidn'texist.Createaconstructorfunction,MyString(),thatactslikeString()ascloselyaspossible.You'renotallowedtouseanybuilt-instringmethodsorproperties,andrememberthattheString()doesn'texist.Youcanusethiscodetotestyourconstructor:
>vars=newMyString('hello');>s.length;5>s[0];"h">s.toString();"hello">s.valueOf();"hello">s.charAt(1);"e">s.charAt('2');"l">s.charAt('e');"h">s.concat('world!');
"helloworld!">s.slice(1,3);"el">s.slice(0,-1);"hell">s.split('e');["h","llo"]>s.split('l');["he","","o"]
Note
Youcanuseaforlooptoloopthroughtheinputstring,treatingitasanarray.
5. UpdateyourMyString()constructortoincludeareverse()method.
Note
Trytoleveragethefactthatarrayshaveareverse()method.
6. ImaginethatArray()andthearrayliteralnotationdon'texist.CreateaconstructorcalledMyArray()thatbehavesasclosetoArray()aspossible.Testitwiththefollowingcode:
>vara=newMyArray(1,2,3,"test");>a.toString();"1,2,3,test">a.length;4>a[a.length-1];"test">a.push('boo');5>a.toString();"1,2,3,test,boo">a.pop();
"boo">a.toString();"1,2,3,test">a.join(',');"1,2,3,test">a.join('isn't');"1isn't2isn't3isn'ttest"
Ifyoufoundthisexerciseamusing,don'tstopwiththejoin()method;goonwithasmanymethodsaspossible.
7. ImagineMathdidn'texist.CreateaMyMathobjectthatalsoprovides thefollowingadditionalmethods:
MyMath.rand(min,max,inclusive):Thisgeneratesarandomnumberbetweenminandmax,inclusiveifinclusiveistrue(default)MyMath.min(array):ThisreturnsthesmallestnumberinagivenarrayMyMath.max(array):Thisreturnsthelargestnumberinagivenarray
SummaryIn Chapter 2, Primitive Data Types, Arrays, Loops, and Conditions, you saw that there are fiveprimitivedatatypes(number,string,Boolean,null,andundefined),andwealsosaidthateverythingthatisnotaprimitivepieceofdataisanobject.Now,youalsoknowthat:
Objectsarelikearrays,butyouspecifythekeysObjectscontainpropertiesPropertiescanbefunctions(functionsaredata;remembervarf=function(){};).PropertiesthatarefunctionsarealsocalledmethodsArraysareactuallyobjectswithpredefinednumericpropertiesandanauto-incrementinglengthpropertyArrayobjectshaveanumberofconvenientmethods(suchassort()orslice())Functionsarealsoobjects,andtheyhaveproperties(suchaslengthandprototype)andmethods(suchascall()andapply())
Regardingthefiveprimitivedatatypes,apartfromundefinedandnull,theotherthreehavethecorrespondingconstructorfunctions-Number(),String(),andBoolean().Usingthese,youcancreateobjects,calledwrapperobjects,whichcontainmethodsforworkingwithprimitivedataelements.
Number(),String(),andBoolean()canbeinvoked:
Withthenewoperator,tocreatenewobjects.Withoutthenewoperator,toconvertanyvaluetothecorrespondingprimitivedatatype.
Otherbuilt-inconstructorfunctionsyou'renowfamiliarwithincludeObject(),Array(),Function(),Date(),RegExp(),andError().You'realsofamiliarwithMath-aglobalobjectthatisnotaconstructor.
Now,youcanseehowobjectshaveacentralroleinJavaScriptprogramming,asprettymucheverythingisanobjectorcanbewrappedbyanobject.
Finally,let'swrapuptheliteralnotationsyou'renowfamiliarwith:
Name Literal Constructor Example
Object {} newObject() {prop:1}
Array [] newArray() [1,2,3,'test']
Regularexpression /pattern/modifiers newRegExp('pattern','modifiers') /java.*/img
Chapter 5. ES6 Iterators and GeneratorsSo far, we have discussed language constructs of JavaScript without looking at any specificlanguageversion.Inthischapter,however,wewillprimarilyfocusonafewlanguagefeaturesintroducedinES6.ThesefeatureshaveabigimpactonhowyouwriteJavaScriptcode.Notonlydotheyimprovethelanguagesignificantly,theyalsoofferseveralfunctionalprogrammingconstructsunavailabletoJavaScriptprogrammersthusfar.
Inthischapter,wewilltakealookatnewlyintroducediteratorsandgeneratorsinES6.Withthatknowledge,wewillproceedtotakeadetailedlookattheenhancedCollectionsconstructs.
For...of loopFor...of loops are introduced in ES6 along with the iterable and iterator constructs. This newloop constructs replaces both the for...in and for...each loop constructs of ES5. As thefor...of loop supports the iteration protocol, it can be used on built-in objects such as arrays,strings, maps, sets, and so on, and custom objects that are iterables. Consider the following pieceofcodeasanexample:
constiter=['a','b'];for(constiofiter){console.log(i);}"a""b"
Thefor...ofloopworkswithiterablesandbuilt-inslikearraysareiterables.Ifyounotice,weareusingconstinsteadofvarwhenwedefinetheloopvariable.Thisisagoodpracticebecausewhenyouuseconst,afreshvariableiscreatedwithanewbindingandstoragespace.Youshoulduseconstovera vardeclarationwiththefor...ofloopwhenyoudon'tintendtomodifythevalueoftheloopvariableinsidetheblock.
Othercollectionssupportfor...oflooptoo.Forexample,asastringisasequenceofUnicodecharacters,for...ofloopworksjustfine:
for(letcof"String"){console.log(c);}//"s""t""r""i""n""g"
Themaindifferencebetweenthefor...inandfor...ofloopisthatthefor...inloopiteratesthroughallenumerablepropertiesofanobject.For...ofloophasaspecificpurpose,andthatistofollowtheiterationbehaviorbasedonhowtheobjectdefinestheiterableprotocol.
Iterators and iterablesES6 introduces a new mechanism of iterating over data. Traversing a list of data and doingsomethingwithitisaverycommonoperation.ES6enhancestheiterationconstructs.Therearetwoprimaryconceptsinvolvedwiththischange-iteratorsanditerables.
Iterators
AJavaScriptiteratorisanobjectthatexposesthenext()method.Thismethodreturnsthenextitemfromthecollectionintheformofanobjectthathastwoproperties-doneandvalue.Inthefollowingexample,wewillreturnaniteratorfromanarraybyexposingthenext()method:
//Takeanarrayandreturnaniteratorfunctioniter(array){varnextId=0;return{next:function(){if(nextId<array.length){return{value:array[nextId++],done:false};}else{return{done:true};}}}}varit=iter(['Hello','Iterators']);console.log(it.next().value);//'Hello'console.log(it.next().value);//'Iterators'console.log(it.next().done);//true
In the preceding example, we are returning value and done till we have elements in the array.When we exhaust elements in the array to return, we will return done as true, indicating that theiteration has no more values. Elements from an iterator are accessed using the next() methodrepeatedly.
Iterables
Aniterableisanobjectthatdefinesitsiterationbehaviororinternaliteration.Suchobjectscanbeusedinthefor...of loopsintroducedinES6.Built-intypessuchasarraysandstringsdefinedefaultiterationbehavior.Foranobjecttobeiterable,itmustimplementthe@@iteratormethod,meaningtheobjectmusthaveapropertywith'Symbol.iterator'askey.
Anobjectbecomesiterableifitimplementsamethodwhosekeyis'Symbol.iterator'.Thismethodmustreturnaniteratorviathenext()method.Let'stakealookatthefollowingexampletoclarifythis:
//Aniterableobject//1.Hasamethodwithkeyhas'Symbol.iterator'//2.Thismethodreturnsaniteratorviamethod'next'letiter={0:'Hello',1:'Worldof',2:'Iterators',length:3,[Symbol.iterator](){letindex=0;return{next:()=>{letvalue=this[index];letdone=index>=this.length;index++;return{value,done};}};}};for(letiofiter){console.log(i);}"Hello"
"World of ""Iterators"
Let'sbreakthisexampledownintosmallerpieces.Wearecreatinganiterableobject.Wewillcreateaniterobjectusingobjectliteralsyntaxthatwearealreadyfamiliarwith.Onespecialaspectofthisobjectisa[Symbol.iterator]method.ThismethoddefinitionusesacombinationofcomputedpropertiesandES6shorthandmethoddefinitionsyntax,whichwealreadydiscussedinthelastchapter.Asthisobjectcontainsa[Symbol.iterator]method,thisobjectisiterable,oritfollowsaniterableprotocol.Thismethodalsoreturnstheiteratorobjectthatdefinestheiterationbehaviorviaexposingthenext()method.Nowthisobjectcanbeusedwiththefor...ofloop.
GeneratorsClosely linked with iterators and iterables, generators are one of the most talked about features ofES6.Generatorfunctionsreturnageneratorobject;thistermsoundsconfusingatfirst.Whenyouwriteafunction,youalsoinstinctivelyunderstanditsbehavior-thefunctionstartsexecution,line-by-line,andfinishesexecutionwhenthelastlineisexecuted.Oncethefunctionislinearlyexecutedthisway,therestofthecodethatfollowsthefunctionisexecuted.
Inlanguageswheremultithreadingissupported,suchflowofexecutioncanbeinterruptedandpartiallyfinishedtaskscanbesharedbetweendifferentthreads,processes,andchannels.JavaScriptissingle-threaded,andyoudon'tneed todealwithchallengesaroundmultithreadingatthemoment.
However,generatorfunctionscanbepausedandresumedlater.Theimportantideahereisthatthegeneratorfunctionchoosestopauseitself,itcannotbepausedbyanyexternalcode.Duringexecution,thefunctionusestheyieldkeywordtopause.Onceageneratorfunctionispaused,itcanonlyberesumedbycodeoutsidethefunction.
Youcanpauseandresumeageneratorfunctionasmanytimesyouwantto.Withgeneratorfunctions,apopularpatternistowriteinfiniteloopsandpauseandresumethemwhenneeded.Thereareprosandconsofdoingthis,butthepatternhascaughtupalready.
Anotherimportantpointtounderstandisthatgeneratorfunctionsalsoallowtwo-waymessagepassing,inandoutofit.Wheneveryoupausethefunctionusingyieldkeyword,themessageissentoutofthegeneratorfunction,andwhenthefunctionisresumed,themessageispassedbacktothegeneratorfunction.
Let'slookatthefollowingexampletoclarifyhowthegeneratorfunctionswork:
function*generatorFunc(){console.log('1');//----------->Ayield;//----------->Bconsole.log('2');//----------->C}constgeneratorObj=generatorFunc();console.log(generatorObj.next());//"1"//Object{//"done":false,//"value":undefined
//}
Thisisaverysimplegeneratorfunction.However,thereareseveralinterestingaspectsthatneedcarefulunderstanding.
First,noticeanasterix*immediatelyafterthekeywordfunction,thisisthesyntaxtoindicatethatthefunctionisageneratorfunction.Itisalsookaytokeeptheasteriximmediatelyprecedingthefunctionname.Bothofthefollowingarevaliddeclarations:
function*f(){}function*f(){}
Insidethefunction,therealmagicisaroundtheyieldkeyword.Whentheyieldkeywordisencountered,thefunctionpausesitself.Beforewemovefurther,let'sseehowthefunctionisinvoked:
constgeneratorObj=generatorFunc();generatorObj.next();//"1"
Whenweinvokethegeneratorfunction,itisnotexecutedlikeanormalfunction,butitreturnsagenerator object. You can use this generator object to control the execution of the generatorfunction.Thenext()methodonthegeneratorobjectresumestheexecutionofthefunction.
Whenwecallnext()thefirsttime,theexecutionproceedsupuntilthefirstlineofthefunction(markedby'A'),andpauseswhentheyieldkeywordisencountered.Ifwecallthenext()functionagain,itwillresumetheexecutiontothe nextlinefromthepointtheexecutionwaspausedlasttime:
console.log(generatorObj.next());//"2"//Object{//"done":true,//"value":undefined//}
Oncetheentirefunctionbodyisexecuted,anycallstonext()onthegeneratorobjecthavenoeffect.Wetalkedaboutgeneratorfunctionsallowingatwo-waymessagepassing.Howdoesthatwork?Inthepreviousexample,youcanseethatwheneverweresumethegeneratorfunction,wereceiveanobjectwithtwovalues,doneandvalue;inourcase,wereceivedundefinedasthevalue.Thisisbecausewedidnotreturnanyvaluewiththeyieldkeyword.Whenyoureturnavaluewiththeyieldkeyword,thecallingfunctionreceivesit.Considerthefollowingexample:
function* logger() {console.log('start')console.log(yield)console.log(yield)console.log(yield)return('end')}
vargenObj=logger();
//thefirstcallofnextexecutesfromthe
startofthefunctionuntilthefirstyieldstatementconsole.log(genObj.next())//"start",Object{"done":false,"value":undefined}console.log(genObj.next('Save'))//"Save",Object{"done":false,"value":undefined}console.log(genObj.next('Our'))//"Our",Object{"done":false,"value":undefined}console.log(genObj.next('Souls'))//"Souls",Object{"done":true,"value":"end"}
Let'stracetheflowofexecutionofthisexamplestepbystep.Thegeneratorfunctionhasthreepausesoryields.Wecancreatethegeneratorobjectbywritingthefollowinglineofcode:
vargenObj=logger();
Wewillstarttheexecutionofthegeneratorfunctionbycallingthenextmethod;thismethodstartstheexecutiontillthefirstyield.Ifyounotice,wearenotpassinganyvaluetothenext()methodinthefirstcall.Thepurposeofthisnext()methodisjusttostartthegeneratorfunction.Wewillcallthenext()methodagain,butthistimewitha"Save"valuepassedasaparameter.Thisvalueisreceivedbyyieldwhenthefunctionexecutionisresumed,andwecanseethevalueprintedonconsole:
"Save",Object{"done":false,"value":undefined}
Wewillcallthenext()methodagainwithtwodifferentvalues,andtheoutputissimilartotheoneintheprecedingcode.Whenwecallthenext()methodthelasttime,theexecutionendsandthegeneratorfunctionreturnsanendvaluetothecallingpieceofcode.Attheendoftheexecution,youwillseedonesetastrueandvalueassignedthevaluereturnedbythefunction,thatis,end:
"Souls",Object{"done":true,"value":"end"}
Itisimportanttonotethatthepurposeofthefirstnext()methodistostarttheexecutionofthegeneratorfunction-ittakesustothefirstyieldkeywordandhence,anyvaluepassedtothefirstnext()methodisignored.
Fromthediscussionsofar,itisapparentthatgeneratorobjectsconformtotheiteratorcontract:
function*logger(){yield'a'yield'b'}vargenObj=logger();//thegeneratorobjectisbuiltusinggeneratorfunctionconsole.log(typeofgenObj[Symbol.iterator]==='function')//true//itisaniterableconsole.log(typeofgenObj.next==='function')//true//andaniterator(hasanext()method)console.log(genObj[Symbol.iterator]()===genObj)//true
Thisexampleconfirmsthatgeneratorfunctionsalsoconformtotheiterablescontract.
Iteratingovergenerators
Generatorsareiterators, andlikeallES6constructsthatsupportiterables,theycanbeusedtoiterateovergenerators.
Thefirstmethodistousethefor...ofloop,asshowninthefollowingcode:
function*logger(){yield'a'yield'b'}for(constioflogger()){console.log(i)}//"a""b"
Wearenotcreatingageneratorobjecthere.TheFor...ofloophassupportforiterablesandgeneratorsnaturallyfallintothisloop.
Thespreadoperatorcanbeusedtoturniterables intoarrays.Considerthefollowingexample:
function* logger() {yield'a'yield'b'}constarr=[...logger()]console.log(arr)//["a","b"]
Finally,youcanusethedestructuringsyntaxwithgenerators,asfollows:
function*logger(){yield'a'yield'b'}
const [x,y] = logger()console.log(x,y)//"a""b"
Generatorsplayanimportantroleinasynchronousprogramming.Shortly,wewilllookatasynchronousprogrammingandpromisesinES6.JavaScriptandNode.jsofferagreatenvironmenttowriteasynchronousprograms.Generatorscanhelpyouwritecooperativemultitaskingfunctions.
CollectionsES6 introduces four data structures-Map, WeakMap, Set, and WeakSet. JavaScript, whencompared to other languages such as Python and Ruby, had a very weak standard library tosupporthashorMapdatastructuresordictionaries.SeveralhackswereinventedtosomehowachievethebehaviorofaMapbymappingastringkeywithanobject.Thereweresideeffectsofsuchhacks.Languagesupportforsuchdatastructureswassorelyneeded.
ES6supportsstandarddictionarydatastructures; wewilllookatmoredetailsaroundtheseinthenextsection.
Map
Mapallowsarbitraryvaluesaskeys.Thekeysaremappedtovalues.Mapsallowfastaccesstovalues.Let'slookatsomeexamplesofmaps:
const m = new Map(); //Creates an empty Mapm.set('first',1);//Setavalueassociatedwithakeyconsole.log(m.get('first'));//Getavalueusingthekey
WewillcreateanemptyMapusingtheconstructor.Youcanusetheset() methodtoaddanentrytotheMapassociatingkeywithvalue,andoverwritinganyexistingentrywiththesamekey.Itscounterpartmethod,get(),getsthevalueassociatedwithakey,orundefinedifthereisnosuchentryinthemap.
Thereareotherhelpermethodsavailablewithmaps,whichareasfollows:
console.log(m.has('first'));//Checksforexistenceofakey//truem.delete('first');console.log(m.has('first'));//false
m.set('foo',1);m.set('bar', 0);
console.log(m.size);//2m.clear();//clearstheentiremapconsole.log(m.size);//0
YoucancreateaMapusingthefollowingiterable[key,value]pairsaswell:
constm2=newMap([[ 1, 'one' ],
[2,'two'],[3,'three'],]);
Youcanchaintheset() methodforacompactsyntaxasfollows:
constm3=newMap().set(1,'one').set(2,'two').set(3,'three');
Wecanuseanyvalueasakey.Forobjects,thekeycanonlybestrings,butwithcollections,thislimitationisremoved.Wecanuseanobjectasakeyaswell,thoughsuchuseisnotverypopular:
constobj={}constm2=newMap([[1,'one'],["two",'two'],[obj,'three'],]);console.log(m2.has(obj));//true
Iteratingovermaps
Oneimportantthingtorememberisthatorderisimportantwithmaps.Mapsretaintheorderinwhichelementswereadded.
TherearethreeiterablesyoucanusetoiterateoveraMap,thatis,keys,values,andentries.
Thekeys()methodreturnsiterableoverthekeysofaMapasfollows:
constm=newMap([[1,'one'],[2,'two'],[3,'three'],]);for(constkofm.keys()){console.log(k);}//123
Similarly,thevalues()methodreturnsiterableoverthevaluesofaMap,asshowninthefollowingexample:
for(constvofm.values()){console.log(v);}//"one"//"two"//"three"
Theentries()methodreturnsentriesoftheMapinformofa[key,value]pair,asyoucanseeinthefollowingcode:
for(constentryofm.entries()){console.log(entry[0],entry[1]);}//1"one"//2"two"//3"three"
Youcanusedestructuringtomakethisconciseasfollows:
for(const[key,value]ofm.entries()){console.log(key, value);
}//1"one"//2"two"//3"three"
Anevenmoresuccinct:
for(const[key,value]ofm){
console.log(key,value);}//1"one"//2"two"//3"three"
Convertingmapstoarrays
Thespreadoperator(...)comesinhandyifyouwanttoconvertaMaptoanarray:
constm=newMap([[1,'one'],[2,'two'],[3,'three'],]);constkeys=[...m.keys()]console.log(keys)//Array[//1,//2,//3//]
Asmapsareiterable,youcanconverttheentireMapintoanarrayusingspreadoperators:
constm=newMap([[1,'one'],[2,'two'],[3,'three'],]);constarr=[...m]console.log(arr)//Array[//[1,"one"],//[2,"two"],//[3,"three"]//]
Set
ASetisacollectionofvalues.Youcanaddandremovevaluesfromit.Althoughthissoundssimilartoarrays,setsdon'tallowthesamevaluetwice.ValueinaSetcanbeofanytype.Sofar,youmustbewonderinghowdifferentisthisfromanArray?ASetisdesignedtodoonethingquickly-membershiptesting.Arraysarerelativelysloweratthis.SetoperationsaresimilartoMapoperations:
consts=newSet();s.add('first');s.has('first');//trues.delete('first');//trues.has('first');//false
Similartomaps,youcancreateaSetviaaniterator:
constcolors=newSet(['red',white,'blue']);
WhenyouaddavaluetotheSet,andthevaluealreadyexisted,nothinghappens.Similarly,ifyoudeleteavaluefromtheSet,andthevaluedidn'texistinthefirstplace,nothinghappens.Thereisnowaytocatchthisscenario.
WeakMapandWeakSet
WeakMapandWeakSethavethesimilar,butrestricted,APIsastheMapandSetrespectively,andtheyworkmostlyliketheirstrongcounterparts.Thereareafewdifferencesthough,whichareasfollows:
WeakMaponlysupportsthenew,has(),get(),set(),anddelete()methodsWeakSetonlysupportsnew,has(),add(),anddelete()KeysofaWeakMapmustbeobjectsValuesofaWeakSetmustbeobjectsYoucan'titerateoverWeakMap;theonlywayyoucanaccessavalueisviaitskeyYoucan'titerateoveraWeakSetYoucan'tclearaWeakMaporaWeakSet
Let'sunderstandWeakMapfirst.ThedifferencebetweenaMapandaWeakMapisthataWeakMapallowsitselftobegarbagecollected.ThekeysinaWeakMapareweaklyheld.WeakMapkeysarenotcountedwhenthegarbagecollectordoesareferencecount(atechniquetoseeallalivereferences),andtheyaregarbagecollectedwhen possible.
WeakMapsareusefulwhenyoudon'thaveanycontroloverthelifecycleoftheobjectyouarekeepingintheMap.Youdon'tneedtoworryaboutmemoryleakwhenusingWeakMapsbecausetheobjectswillnotkeepthememoryoccupiedeveniftheirlifecycleislong.
SameimplementationdetailsapplytoWeakSetaswell.However,asyoucannotiterateoveraWeakSet,therearenotmanyusecasesforaWeakSet.
SummaryIn this chapter, we took a detailed look at ES6 Generators. Generators are one of the mostanticipatedfeaturesofES6.Theabilitytopauseandresumeexecutionofafunctionopensupalotofpossibilitiesaroundco-operativeprogramming.Theprimarystrengthofgeneratorsisthattheyprovideasingle-threaded,synchronous-lookingcodestyle,whilehidingtheasynchronousnatureaway.Thismakesiteasierforustoexpressinaverynaturalwaywhattheflowofourprogram'ssteps/statementsiswithoutsimultaneouslyhavingtonavigateasynchronoussyntaxandgotchas.Weachieveseparationofconcernusinggeneratorsduetothis.
Generatorsworkhand-in-handwiththeiteratorsanditerablescontract.ThesearewelcomeadditiontoES6andsignificantlybooststhedatastructuresthelanguageoffers.Iteratorsprovideasimplewaytoreturna(potentiallyunbounded)sequenceofvalues.The@@iteratorsymbolisusedtodefinedefaultiteratorsforobjects,makingthemaniterable.
Themostimportantusecaseforiteratorsbecomesevidentwhenwewanttouseitinaconstructthatconsumesiterables,suchasthefor...ofloop.Inthischapterwealsolookedatanewloopconstructfor...ofintroducedinES6.for...ofworkswithalotofnativeobjectsbecausetheyhavedefault@@iteratormethodsdefined.WelookedatnewadditionstotheES6collectionslike-Maps,Sets,WeakMaps,andWeakSets.Thesecollectionshaveadditionaliteratormethods-.entries(),.values()and.keys().
ThenextchapterwilltakeadetailedlookatJavaScriptPrototypes.
Chapter 6. PrototypeIn this chapter, you'll learn about the prototype property of the function objects. UnderstandinghowtheprototypeworksisanimportantpartoflearningtheJavaScriptlanguage.Afterall,JavaScriptisoftenclassifiedashavingaprototype-basedobjectmodel.There'snothingparticularlydifficultabouttheprototype,butit'sanewconcept,andassuch,maysometimestakeabitoftimetosinkin.Likeclosures(seeChapter3,Functions),theprototypeisoneofthosethingsinJavaScriptwhich,onceyouget,seemsoobviousandmakeperfectsense.Aswiththerestofthisbook,you'restronglyencouragedtotypeinandplayaroundwiththeexamples-thismakesitmucheasiertolearnandremembertheconcepts.
Inthischapter,wewillcoverthefollowingtopics:
EveryfunctionhasaprototypepropertyanditcontainsanobjectAddingpropertiestotheprototypeobjectUsingthepropertiesaddedtotheprototypeThedifferencebetweenownpropertiesandpropertiesoftheprototypeThe__proto__property,thesecretlinkeveryobjectkeepstoitsprototypeMethodssuchasisPrototypeOf(),hasOwnProperty(),andpropertyIsEnumerable()Enhancingbuilt-inobjects,suchasarraysorstrings,andwhythatcanbeabadidea
The prototype propertyThe functions in JavaScript are objects, and they contain methods and properties. Some of themethodsthatyou'realreadyfamiliarwithareapply()andcall(),andsomeoftheotherpropertiesarelengthandconstructor.Anotherpropertyofthefunctionobjectsisprototype.
Ifyoudefineasimplefunction,foo(),youcanaccessitspropertiesasyouwoulddowithanyotherobject.Considerthefollowingcode:
>functionfoo(a,b){returna*b;}>foo.length;2>foo.constructor;functionFunction(){[nativecode]}
Theprototypepropertyisapropertythatisavailabletoyouassoonasyoudefinethefunction.Itsinitialvalueisanemptyobject:
>typeoffoo.prototype;"object"
It'sasifyouhaveaddedthispropertyyourself,asfollows:
>foo.prototype={};
Youcanaugmentthisemptyobjectwithpropertiesandmethods.Theywon'thaveanyeffectonthefoo()functionitself;they'llonlybeusedifyoucallfoo()asaconstructor.
Addingmethodsandpropertiesusingtheprototype
Inthepreviouschapter,youlearnedhowtodefineconstructorfunctionsthatyoucanusetocreate(construct)newobjects.Themainideaisthat,insideafunctioninvokedwithnew,youwillhaveaccesstothethisvalue,whichreferstotheobjecttobereturnedbytheconstructor.Augmenting,whichisaddingmethodsandpropertiestothis,ishowyoucanaddfunctionalitytotheobjectbeingconstructed.
Let'stakealookattheconstructorfunction,Gadget(),whichusesthistoaddtwopropertiesandonemethodtotheobjectsitcreates,asfollows:
functionGadget(name,color){this.name=name;this.color=color;
this.whatAreYou = function () {return'Iama'+this.color+''+this.name;};}
Addingmethodsandpropertiestotheprototypepropertyoftheconstructorfunctionisanotherwaytoaddfunctionalitytotheobjectsthisconstructorproduces.Let'saddtwomoreproperties,priceandrating,aswellasagetInfo()method.Asprototypealreadypointstoanobject,youcanjustkeepadding propertiesandmethodstoit,asfollows:
Gadget.prototype.price=100;Gadget.prototype.rating=3;Gadget.prototype.getInfo=function(){return'Rating:'+this.rating+',price:'+this.price;};
Alternatively, instead of adding properties to the prototype object one by one, you canoverwrite the prototype completely, replacing it with an object of your choice, as shown in thefollowing example:
Gadget.prototype={price:100,rating:.../*andsoon...*/};
Using the prototype's methods and propertiesAll the methods and properties you have added to the prototype are available as soon as youcreate a new object using the constructor. If you create a newtoy object using the Gadget()constructor, you can access all the methods and properties that are already defined, as you can seeinthefollowingcode:
>varnewtoy=newGadget('webcam','black');>newtoy.name;"webcam">newtoy.color;"black">newtoy.whatAreYou();"Iamablackwebcam">newtoy.price;100>newtoy.rating;3>newtoy.getInfo();"Rating:3,price:100"
It'simportanttonotethattheprototypeislive.ObjectsarepassedbyreferenceinJavaScript,andtherefore,theprototypeisnotcopiedwitheverynewobjectinstance.Whatdoesthismeaninpractice?Itmeansthatyoucanmodifytheprototypeatanytime,andalltheobjects,eventhosecreatedbeforethemodification,willseethechanges.
Let's continue the example by adding a new method to the prototype:
Gadget.prototype.get=function(what){returnthis[what];};
Eventhoughthenewtoyobjectwascreatedbeforetheget()methodwasdefined,thenewtoyobjectstillhasaccessto thenewmethod,whichisasfollows:
>newtoy.get('price');100>newtoy.get('color');"black"
Ownpropertiesversusprototypeproperties
Intheprecedingexample,getInfo()wasusedinternallytoaccessthepropertiesoftheobject.Itcould'vealsousedGadget.prototypetoachievethesameoutput,asfollows:
Gadget.prototype.getInfo=function(){return'Rating:'+Gadget.prototype.rating+',price:'+Gadget.prototype.price;};
What'sthedifference?Toanswerthisquestion,let'sexamineindetailhowtheprototypeworks.
Let'stakethenewtoyobjectagain:
varnewtoy=newGadget('webcam','black');
Whenyoutrytoaccessapropertyofnewtoy,say,newtoy.name,theJavaScriptenginelooksthroughallofthepropertiesoftheobjectsearchingforonecalledname,andifitfindsit,itreturnsitsvalue,asfollows:
>newtoy.name;"webcam"
Whatifyoutrytoaccesstheratingproperty?TheJavaScriptengineexaminesallofthepropertiesofthenewtoyobjectanddoesn'tfindtheonecalledrating.Then,thescriptengineidentifiestheprototypeoftheconstructorfunctionusedtocreatethisobject(thesameasifyoudonewtoy.constructor.prototype).Ifthepropertyisfoundintheprototypeobject,thefollowingpropertyisused:
>newtoy.rating;3
Youcandothesameandaccesstheprototypedirectly.Everyobjecthasaconstructorproperty,whichisareferencetothefunctionthatcreatedtheobject,sointhiscaselookatthefollowingcode:
>newtoy.constructor===Gadget;true>newtoy.constructor.prototype.rating;3
Now,let'stakethislookuponestepfurther.Everyobjecthasaconstructor.Theprototypeisanobject,soitmusthaveaconstructortoo,which,inturn,hasaprototype.Youcangouptheprototypechain,andyou willeventuallyendupwiththebuilt-inObject()object,whichisthehighest-levelparent.Inpractice,thismeansthatifyoutrynewtoy.toString()andnewtoydoesn'thaveitsowntoString()method,anditsprototypedoesn'teither,intheend,you'llget
theobject'stoString()method:
>newtoy.toString();"[objectObject]"
Overwritingaprototype'spropertywithanownproperty
Astheprecedingdiscussiondemonstrates,ifoneofyourobjectsdoesn'thaveacertainpropertyofitsown,itcanuseone,ifitexists,somewhereuptheprototypechain.Whatiftheobjectdoeshaveitsownpropertyandtheprototypealsohasonewiththesamename?Then,theownpropertytakesprecedenceovertheprototype's.
Considerascenariowhereapropertynameexistsasbothanownpropertyandapropertyoftheprototypeobject:
>functionGadget(name){this.name=name;}>Gadget.prototype.name='mirror';
Creatinganewobjectandaccessingitsnamepropertygivesyoutheobject'sownnameproperty,asfollows:
>vartoy=newGadget('camera');>toy.name;"camera"
YoucantellwherethepropertywasdefinedusinghasOwnProperty(),whichisasfollows:
>toy.hasOwnProperty('name');true
Ifyoudeletethetoyobject'sownnameproperty,theprototype'sproperty withthesamenameshinesthrough:
>deletetoy.name;true>toy.name;"mirror">toy.hasOwnProperty('name');false
Ofcourse,youcanalwaysrecreatetheobject'sownpropertyasfollows:
>toy.name='camera';> toy.name;
"camera"
YoucanplayaroundwiththehasOwnProperty()methodtofindouttheoriginsofaparticularpropertyyou'recuriousabout.ThetoString()methodwasmentionedearlier.Whereisitcomingfrom?
>toy.toString();"[objectObject]">toy.hasOwnProperty('toString');false>toy.constructor.hasOwnProperty('toString');false>toy.constructor.prototype.hasOwnProperty('toString');false>Object.hasOwnProperty('toString');false>Object.prototype.hasOwnProperty('toString');true
Enumeratingproperties
Ifyouwanttolistallthepropertiesofanobject,youcanuseafor...inloop.InChapter2,PrimitiveDataTypes,Arrays,Loops,andConditions,yousawthatyoucanalsoloopthroughalltheelementsofanarraywithfor...in,butasmentionedthere,forisbettersuitedforarraysandfor...inforobjects.Let'stakeanexampleofconstructingaquerystringforaURLfromanobject:
var params = {productid:666,section:'products'};
varurl='http://example.org/page.php?',i,query=[];
for(iinparams){query.push(i+'='+params[i]);}
url+=query.join('&');
Thisproducestheurlstringasfollows:
http://example.org/page.php?productid=666§ion=products.
Thefollowingareafewdetailstobeawareof:
Notallpropertiesshowupinafor...inloop.Forexample,thelength(forarrays)andconstructorpropertiesdon'tshowup.Thepropertiesthatdoshowuparecalledenumerable.YoucancheckwhichonesareenumerablewiththehelpofthepropertyIsEnumerable()methodthateveryobjectprovides.InES5,youcanspecifywhichpropertiesareenumerable,whileinES3youdon'thavethatcontrol.Prototypesthatcomethroughtheprototypechainalsoshowup,providedtheyareenumerable.Youcancheckwhetherapropertyisanobject'sownpropertyoraprototype's
propertyusingthehasOwnProperty()method.ThepropertyIsEnumerable()methodreturnsfalseforalloftheprototype'sproperties,eventhosethatareenumerableandshowup inthefor...inloop.
Let'sseethesemethodsinaction.TakethissimplifiedversionofGadget():
functionGadget(name,color){this.name=name;
this.color = color;this.getName=function(){returnthis.name;};}Gadget.prototype.price=100;Gadget.prototype.rating=3;
Createanewobjectasfollows:
varnewtoy=newGadget('webcam','black');
Now,ifyouloopusingafor...inloop,youcanseealloftheobject'sproperties,includingthosethatcomefromtheprototype:
for(varpropinnewtoy){console.log(prop+'='+newtoy[prop]);}
Theresultalsocontainstheobject'smethods,asmethodsarejustpropertiesthathappentobefunctions:
name=webcamcolor=blackgetName=function(){returnthis.name;}price=100rating=3
Ifyouwanttodistinguishbetweentheobject'sownpropertiesandtheprototype'sproperties,usehasOwnProperty().Trythefollowingfirst:
>newtoy.hasOwnProperty('name');true
>newtoy.hasOwnProperty('price');false
Let'sloopagain,butthistime,showingonlytheobject'sownproperties:
for(varpropinnewtoy){if(newtoy.hasOwnProperty(prop)){console.log(prop+'='+newtoy[prop]);
}}
Theresultisasfollows:
name=webcamcolor=blackgetName=function(){returnthis.name;
}
Now,let'strypropertyIsEnumerable().Thismethodreturnstruefortheobject'sownpropertiesthatarenotbuiltin,forexample:
>newtoy.propertyIsEnumerable('name');true
Mostbuilt-inpropertiesandmethodsarenotenumerable:
>newtoy.propertyIsEnumerable('constructor');false
Anypropertiescomingdowntheprototypechainarenotenumerable:
> newtoy.propertyIsEnumerable('price');false
However,notthatsuchpropertiesareenumerableifyoureachtheobjectcontainedintheprototypeandinvokeitspropertyIsEnumerable()method.Considerthefollowingcode:
>newtoy.constructor.prototype.propertyIsEnumerable('price');true
UsingisPrototypeOf()method
ObjectsalsohavetheisPrototypeOf()method.Thismethodtellsyouwhetherthatspecificobjectisusedasaprototypeofanotherobject.
Let'stakeasimpleobjectnamedmonkey:
varmonkey={hair:true,feeds:'bananas',breathes:'air'};
Now,let'screateaHuman()constructorfunctionandsetitsprototypepropertytopointtomonkey:
functionHuman(name){this.name=name;}Human.prototype=monkey;
Now,ifyoucreateanewHumanobjectcalledgeorgeandaskIfmonkeytheprototypeofgeorge?,you'llgettrue:
>vargeorge=newHuman('George');>monkey.isPrototypeOf(george);true
Notethatyouhavetoknow,orsuspect,whotheprototypeisandthenaskisittruethatyourprototypeismonkey?inordertoconfirmyoursuspicion.But,whatifyoudon'tsuspectanything,andyouhavenoidea?Canyoujustasktheobjecttotellyouitsprototype?Theansweris,youcan'tinallbrowsers,butyoucaninmostofthem.MostrecentbrowsershaveimplementedtheadditiontoES5calledObject.getPrototypeOf().
>Object.getPrototypeOf(george).feeds;"bananas">Object.getPrototypeOf(george)===monkey;true
Forsomeofthepre-ES5environmentsthatdon'thavegetPrototypeOf(),youcanusethespecialproperty,__proto__.
Thesecret__proto__link
Asyoualreadyknow,theprototypepropertyisconsultedwhenyoutrytoaccessapropertythatdoesnotexistinthecurrentobject.
Consideranotherobjectcalledmonkey,anduseitasaprototypewhencreatingobjectswiththeHuman()constructor:
>varmonkey={feeds:'bananas',breathes:'air'};>functionHuman(){}>Human.prototype=monkey;
Now,let'screateadeveloperobject,andgiveitthefollowingproperties:
>vardeveloper=newHuman();>developer.feeds='pizza';>developer.hacks='JavaScript';
Now,let'saccesstheseproperties(forexample,hacksisapropertyofthedeveloperobject):
>developer.hacks;"JavaScript"
Thefeedspropertycanalsobefoundintheobject,asfollows:
>developer.feeds;"pizza"
Thebreathespropertydoesn'texistasapropertyofthedeveloperobject,sotheprototypeislookedup,asifthereisasecretlinkorpassagewaythatleadstotheprototypeobject:
>developer.breathes;"air"
The secret link is exposed in most modern JavaScript environments as the __proto__ property,the word proto with two underscores before and after:
>developer.__proto__===monkey;true
Youcanusethissecretpropertyforlearningpurposes,butit'snotagoodideatouseitinyourrealscriptsbecauseitdoesnotexistinallbrowsers(notablyIE),soyour scriptswon'tbeportable.
Beawarethat__proto__isnotthesameasprototype,as__proto__isapropertyofthe
instances(objects),whereasprototypeisapropertyoftheconstructorfunctionsusedtocreatethoseobjects:
>typeofdeveloper.__proto__;"object">typeofdeveloper.prototype;"undefined">typeofdeveloper.constructor.prototype;"object"
Onceagain,youshoulduse__proto__onlyforlearningordebuggingpurposes.Or,ifyou'reluckyenoughandyourcodeonlyneedstoworkinES5-compliantenvironments,youcanuseObject.getPrototypeOf().
Augmenting built-in objectsThe objects created by the built-in constructor functions, such as Array, String, and evenObject and Function, can be augmented (or enhanced) through the use of prototypes. This meansthat you can, for example, add new methods to the Array prototype, and in this way you can makethem available to all arrays. Let's see how to do this.
InPHP,thereisafunctioncalledin_array(),whichtellsyouwhetheravalueexistsinanarray.InJavaScript,thereisnoinArray()method,although,inES5,there'sindexOf(),whichyoucanuseforthesamepurpose.So,let'simplementitandaddittoArray.prototype,asfollows:
Array.prototype.inArray=function(needle){for(vari=0,len=this.length;i<len;i++){if(this[i]===needle){returntrue;}}returnfalse;};
Now,allarrayshaveaccesstothenewmethod.Let'stestthefollowingcode:
>varcolors=['red','green','blue'];>colors.inArray('red');true>colors.inArray('yellow');false
Thatwasniceandeasy!Let'sdoitagain.Imagineyourapplicationoftenneedstospellwordsbackward,andyoufeelthereshouldbeabuilt-in reverse()methodforstringobjects.Afterall,arrayshavereverse().Youcaneasilyaddareverse()methodtotheStringprototypebyborrowingArray.prototype.reverse()(therewasasimilarexerciseattheendofChapter4,Objects):
String.prototype.reverse=function(){returnArray.prototype.reverse.apply(this.split('')).join('');};
Thiscodeusesthesplit()methodtocreateanarrayfromastring,thencallsthereverse()methodonthisarray,whichproducesareversedarray.Theresultingarrayisthenturnedbackintoastringusingthejoin()method.Let'stestthenewmethod:
>"bumblebee".reverse();"eebelbmub"
Augmentingbuilt-inobjects-discussion
Augmentingbuilt-inobjectsthroughtheprototypeisapowerfultechnique,andyoucanuseittoshapeJavaScriptinanywayyoulike.Becauseofitspower,though,youshouldalwaysthoroughlyconsideryouroptionsbeforeusingthisapproach.
ThereasonisthatonceyouknowJavaScript,you'reexpectingittoworkthesameway,nomatterwhichthird-partylibraryorwidgetyou'reusing.Modifyingcoreobjectscanconfusetheusersandmaintainersofyourcodeandcreateunexpectederrors.
JavaScript evolves and browser's vendors continuously support more features. What you consideramissingmethodtodayanddecidetoaddtoacoreprototypecouldbeabuilt-inmethodtomorrow.Inthiscase,yourmethodisnolongerneeded.Additionally,whatifyouhavealreadywrittenalotofcodethat usesthemethodandyourmethodisslightlydifferentfromthenewbuilt-inimplementation?
Themostcommonandacceptableusecasetoaugmentbuilt-inprototypesistoaddsupportfornewfeatures(onesthatarealreadystandardizedbytheECMAScriptcommitteeandimplementedinnewbrowsers)tooldbrowsers.OneexamplewillbeaddinganES5methodtooldversionsofIE.Theseextensionsareknownasshimsorpolyfills.
Whenaugmentingprototypes,youwillfirstcheckifthemethodexistsbeforeimplementingityourself.Thisway,youcanusethenativeimplementationinthebrowserifoneexists.Forexample,let'saddthetrim()methodforstrings,whichisamethodthatexistsinES5butismissinginolderbrowsers:
if(typeofString.prototype.trim!=='function'){String.prototype.trim=function(){returnthis.replace(/^\s+|\s+$/g,'');};}>"hello".trim();"hello"
Tip
Bestpractice
Ifyoudecidetoaugmentabuilt-inobject,oritsprototypewithanewproperty,docheckfortheexistenceofthenewpropertyfirst.
Prototypegotchas
Thefollowingarethetwoimportantbehaviorstoconsiderwhendealingwithprototypes:
The prototype chain is live, except for when you completely replace the prototype objectThe prototype.constructor method is not reliable
Let'screateasimpleconstructorfunctionandtwoobjects:
>functionDog(){this.tail=true;}>varbenji=newDog();>varrusty=newDog();
Even after you've created the benji and rusty objects, you can still add properties to theprototype of Dog() and the existing objects will have access to the new properties. Let's throw inthe say() method:
>Dog.prototype.say=function(){return'Woof!';};
Bothobjectshaveaccesstothenewmethod:
>benji.say();"Woof!"rusty.say();"Woof!"
Uptothispoint,ifyouconsultyourobjects,askingwhichconstructorfunctionwasusedtocreatethem,they'llreportitcorrectly:
>benji.constructor===Dog;true
> rusty.constructor === Dog;true
Now,let'scompletelyoverwritetheprototypeobjectwithabrandnewobject:
>Dog.prototype={paws:4,hair:true};
Itturnsoutthattheoldobjectsdonotgetaccesstothenewprototype'sproperties;theystillkeepthe secret link pointing to the old prototype object, as follows:
>typeofbenji.paws;"undefined"
>benji.say();"Woof!">typeofbenji.__proto__.say;"function">typeofbenji.__proto__.paws;"undefined"
Anynewobjectsthatyouwillcreatefromnowonwillusetheupdatedprototype,whichisasfollows:
>varlucy=newDog();>lucy.say();TypeError:lucy.sayisnotafunction>lucy.paws;
4
Thesecret__proto__linkpointstothenewprototypeobject,asshowninthefollowinglinesofcode:
>typeoflucy.__proto__.say;"undefined">typeoflucy.__proto__.paws;"number"
Nowtheconstructorpropertyofthenewobjectnolongerreportscorrectly.YouwillexpectittopointtoDog(),butinsteaditpointstoObject(),asyoucanseeinthefollowingexample:
>lucy.constructor;functionObject(){[nativecode]}>benji.constructor;functionDog(){this.tail=true;}
Youcaneasilypreventthisconfusionbyresettingtheconstructorpropertyafteryouoverwritetheprototypecompletely,asfollows:
>functionDog(){}>Dog.prototype={};>newDog().constructor===Dog;false>Dog.prototype.constructor=Dog;>newDog().constructor===Dog;true
Tip
Bestpractice
Whenyouoverwritetheprototype,remembertoresettheconstructorproperty.
ExercisesLets practice the following exercise:
1. CreateanobjectcalledshapethathasthetypepropertyandagetType()method.2. DefineaTriangle()constructorfunctionwhoseprototypeisshape.Objectscreatedwith
Triangle()shouldhavethreeownproperties-a,b,andc,representingthelengthsofthesidesofatriangle.
3. AddanewmethodtotheprototypecalledgetPerimeter().4. Testyourimplementationwiththefollowingcode:
>vart=newTriangle(1,2,3);>t.constructor===Triangle;true>shape.isPrototypeOf(t);true>t.getPerimeter();6
> t.getType();"triangle"
5. Loopovert,showingonlyyourownpropertiesandmethods,noneoftheprototype's.6. Makethefollowingcodework:
>[1,2,3,4,5,6,7,8,9].shuffle();[2,4,1,8,9,6,5,3,7]
SummaryLet's summarize the most important topics you have learned in this chapter:
Allfunctionshaveapropertycalledprototype.Initially,itcontainsanemptyobject-anobjectwithoutanyownproperties.Youcanaddpropertiesandmethodstotheprototypeobject.Youcanevenreplaceitcompletelywithanobjectofyourchoice.Whenyoucreateanobjectusingafunctionasaconstructor(withnew),theobjectgetsasecretlinkpointingtotheprototypeoftheconstructorandcanaccesstheprototype'sproperties.Anobject'sownpropertiestakeprecedence overaprototype'spropertieswiththesamename.UsethehasOwnProperty()methodtodifferentiatebetweenanobject'sownpropertiesandprototypeproperties.Thereisaprototypechain.Whenyouexecutefoo.bar,andifyourfooobjectdoesn'thaveapropertycalledbar,theJavaScriptinterpreterlooksforabarpropertyintheprototype.Ifnoneisfound,itkeepssearchingintheprototype'sprototype,thentheprototypeoftheprototype'sprototype,anditwillkeepgoingallthewayuptoObject.prototype.Youcanaugmenttheprototypesofbuilt-inconstructorfunctions,and allobjectswillseeyouradditions.AssignafunctiontoArray.prototype.flipandallarrayswillimmediatelygetaflip()method,asin[1,2,3].flip().But,docheckwhetherthemethod/propertyyouwanttoaddalreadyexists,soyoucanfuture-proofyourscripts.
Chapter 7. InheritanceIf you go back to Chapter 1, Object-Oriented JavaScript, and review the Object-orientedprogrammingsection,you'llseethatyoualreadyknowhowtoapplymostofthemtoJavaScript.Youknowwhatobjects,methods,andpropertiesare.Youknowthatthere arenoclassesinES5,althoughyoucanachievethemusingconstructorfunctions.ES6introducesthenotionofclasses;wewilltakeadetailedlookathowES6classesworkinthenextchapter.Encapsulation?Yes,theobjectsencapsulateboth thedataandthemeans(methods)todosomethingwiththedata.Aggregation?Sure,anobjectcancontainotherobjects.Infact,thisisalmostalwaysthecasesincemethodsarefunctionsandfunctionsarealsoobjects.
Now,let'sfocusontheinheritancepart.Thisisoneofthemostinterestingfeatures,asitallowsyoutoreuseexistingcode,thuspromotinglaziness,whichislikelytobewhatbroughthumanspeciestocomputerprogramminginthefirstplace.
JavaScriptisadynamiclanguage,andthereisusuallymorethanonewaytoachieveanygiventask.Inheritanceisnotanexception.Inthischapter,you'llseesomecommonpatternsforimplementinginheritance.Havingagoodunderstandingofthesepatternswillhelpyoupicktherightone,ortherightmix,dependingonyourtask,project,orstyle.
Prototype chainingLet's start with the default way of implementing inheritance - inheritance chaining through theprototype.
Asyoualreadyknow,everyfunctionhasaprototypeproperty,whichpointstoanobject.Whenafunctionisinvokedusingthenewoperator,anobjectiscreatedandreturned.Thisnewobjecthasasecretlinktotheprototypeobject.Thesecretlink(called__proto__insomeenvironments)allowsmethodsandpropertiesoftheprototypeobjecttobeusedasiftheybelongedtothenewlycreatedobject.
Theprototypeobjectisjustaregularobjectand,therefore,italsohasthesecretlinktoitsprototype.Andso,achaincalledaprototypechainiscreated:
In this illustration, an object A contains a number of properties. One of the properties is thehidden__proto__property,whichpointstoanotherobject,B.B's__proto__propertypointstoC.ThischainendswiththeObject.prototypeobject,thegrandparent,andeveryobjectinheritsfromit.
Thisisallgoodtoknow,buthowdoesithelpyou?ThepracticalsideisthatwhenobjectAlacksapropertybutBhasit,Acanstillaccessthispropertyasitsown.ThesameappliesifBalsodoesn'thavetherequiredproperty,butCdoes.Thisishowinheritancetakesplace-anobjectcan
accessanypropertyfoundsomewheredowntheinheritancechain.
Throughoutthischapter,you'llseedifferentexamplesthatusethefollowinghierarchy-agenericShape parent is inherited by a 2D shape, which in turn is inherited by any number of specifictwo-dimensional shapes such as a triangle, rectangle, and so on.
Prototypechainingexample
Prototypechainingisthedefaultwaytoimplementinheritance.Inordertoimplementthehierarchy,let'sdefinethreeconstructorfunctions:
functionShape(){this.name='Shape';this.toString=function(){returnthis.name;
};}
functionTwoDShape(){this.name='2Dshape';}
functionTriangle(side,height){this.name='Triangle';this.side=side;this.height=height;this.getArea=function(){returnthis.side*this.height/2;};}
Thecodethatperformstheinheritancemagicisasfollows:
TwoDShape.prototype=newShape();Triangle.prototype=newTwoDShape();
What'shappeninghere?YoutaketheobjectcontainedintheprototypepropertyofTwoDShape,andinsteadofaugmentingitwithindividualproperties,youcompletelyoverwriteitwithanotherobject,createdbyinvokingtheShape()constructorwithnew.ThesameprocesscanbefollowedforTriangle-itsprototypeisreplacedbyanobjectcreatedbynewTwoDShape().It'simportanttorememberthatJavaScriptworkswithobjects,notclasses.YouneedtocreateaninstanceusingthenewShape()constructor,andafterthat,youcaninherititsproperties;youdon'tinheritfromShape()directly.Additionally,afterinheriting,youcanmodifytheShape()constructor,overwriteit,orevendeleteit,andthiswillhavenoeffectonTwoDShape,becauseallyouneededisoneinstancetoinheritfrom.
Asyouknowfromthepreviouschapter,overwritingtheprototype(asopposedtojustaddingproperties to it), has side effects on the constructor property. Therefore, it's a good idea toreset the constructor property after inheriting. Consider the following example:
TwoDShape.prototype.constructor=TwoDShape;Triangle.prototype.constructor=Triangle;
Now,let'stestwhathashappenedsofar.CreatingaTriangleobjectandcallingitsown
getArea()methodworksasexpected:
>varmy=newTriangle(5,10);>my.getArea();25
Although the my object doesn't have its own toString() method, it inherited one and you can callit. Note how the inherited method toString() binds the this object to my:
>my.toString();"Triangle"
It'sfascinatingtoconsiderwhattheJavaScriptenginedoeswhenyoucall my.toString():
Itloopsthroughallofthepropertiesofmyanddoesn'tfindamethodcalledtoString().Itlooksattheobjectthatmy.__proto__pointstothisobjectistheinstancenewTwoDShape()createdduringtheinheritanceprocess.Now,theJavaScriptengineloopsthroughtheinstanceofTwoDShapeanddoesn'tfindatoString()method.Itthenchecks__proto__ofthatobject.Thistime,__proto__pointstotheinstancecreatedbynewShape().TheinstanceofnewShape()isexamined,andtoString()isfinallyfound.Thismethodisinvokedinthecontextofmy,meaningthatthispointstomy.
Ifyouaskmy,Who'syourconstructor?,itreportsitcorrectlybecauseoftheresetoftheconstructorpropertyaftertheinheritance:
>my.constructor===Triangle;true
Usingtheinstanceofoperator,youcanvalidatethatmyisaninstanceofallthreeconstructors:
>myinstanceofShape;true>myinstanceofTwoDShape;true>myinstanceofTriangle;true>myinstanceofArray;false
ThesamehappenswhenyoucallisPrototypeOf()ontheconstructorsbypassingmy:
>Shape.prototype.isPrototypeOf(my);true>TwoDShape.prototype.isPrototypeOf(my);true>Triangle.prototype.isPrototypeOf(my);true>String.prototype.isPrototypeOf(my);
false
Youcanalsocreateobjectsusingtheothertwoconstructors.ObjectscreatedwithnewTwoDShape()alsogetthetoString()methodinheritedfromShape():
>vartd=newTwoDShape();>td.constructor === TwoDShape;
true>td.toString();"2Dshape">vars=newShape();>s.constructor===Shape;true
Movingsharedpropertiestotheprototype
Whenyoucreateobjects usingaconstructorfunction,ownpropertiesareaddedusingthis.Thiscouldbeinefficientincaseswherepropertiesdon'tchangeacrossinstances.Inthepreviousexample,Shape()wasdefinedasfollows:
function Shape(){this.name='Shape';}
ThismeansthateverytimeyoucreateanewobjectusingnewShape(),anewnamepropertyiscreatedandstoredsomewhereinthememory.Theotheroptionistohavethenamepropertyaddedtotheprototypeandsharedamongalltheinstances:
functionShape(){}Shape.prototype.name='Shape';
Now,everytimeyoucreateanobjectusingnewShape(),thisobjectdoesn'tgetitsownpropertyname,butusestheoneaddedtotheprototype.Thisismoreefficient,butyoushouldonlyuseitforpropertiesthatdon'tchangefromoneinstancetoanother.Methodsareidealforthistypeofsharing.
Let'simprovetheprecedingexamplebyaddingallmethodsandsuitablepropertiestoprototype.InthecaseofShape()andTwoDShape(),everythingismeanttobeshared:
//constructorfunctionShape(){}
//augmentprototypeShape.prototype.name='Shape';Shape.prototype.toString=function(){returnthis.name;};
//anotherconstructorfunctionTwoDShape(){}
//takecareofinheritanceTwoDShape.prototype=newShape();TwoDShape.prototype.constructor=TwoDShape;
//augmentprototypeTwoDShape.prototype.name='2Dshape';
Asyoucansee,youhavetotakecareofinheritancefirstbeforeaugmentingtheprototype.Otherwise, anything you add to TwoDShape.prototype gets wiped out when you inherit.
TheTriangleconstructorisalittledifferent,becauseeveryobjectitcreatesisanewtriangle,
whichislikelytohavedifferentdimensions.So,it'sgoodtokeepsideandheightasownpropertiesandsharetherest.ThegetArea()method,forexample,isthesame,regardlessoftheactualdimensionsofeachtriangle.Again,youdotheinheritancebitfirstandthenaugmenttheprototype:
functionTriangle(side,height){this.side=side;this.height=height;}//takecareofinheritanceTriangle.prototype=newTwoDShape();Triangle.prototype.constructor=Triangle;
//augmentprototypeTriangle.prototype.name='Triangle';Triangle.prototype.getArea=function(){returnthis.side*this.height/2;};
Alltheprecedingtestcodeworksexactlythesame.Hereisanexample:
>varmy=newTriangle(5,10);>my.getArea();25>my.toString();"Triangle"
Thereisonlyaslightbehind-the-scenesdifferencewhencallingmy.toString().ThedifferenceisthatthereisonemorelookuptobedonebeforethemethodisfoundinShape.prototype,asopposedtointhenewShape()instance,likeitwasinthepreviousexample.
YoucanalsoplaywithhasOwnProperty()toseethedifferencebetweentheownpropertyversusapropertycomingdowntheprototypechain:
>my.hasOwnProperty('side');true>my.hasOwnProperty('name');false
ThecallstoisPrototypeOf()andtheinstanceofoperatorfromthepreviousexampleworkinexactlythesameway:
>TwoDShape.prototype.isPrototypeOf(my);true>myinstanceofShape;true
Inheriting the prototype onlyAs explained earlier, for reasons of efficiency, you should add the reusable properties andmethodstotheprototype.Ifyoudoso,thenit'sagoodideatoinheritonlytheprototype,becauseallthereusablecodeisthere.ThismeansthatinheritingtheShape.prototypeobjectisbetterthaninheritingtheobjectcreatedwithnewShape().Afterall,newShape()onlygivesyouownshapepropertiesthatarenotmeanttobereused(otherwise,theywouldbeintheprototype).Yougainalittlemoreefficiencyby:
NotcreatinganewobjectforthesakeofinheritancealoneHavingfewerlookupsduringruntime(whenitcomestosearchingfortoString())
For example, here's the updated code; the changes are highlighted:
functionShape(){}//augmentprototypeShape.prototype.name='Shape';Shape.prototype.toString=function(){returnthis.name;};
functionTwoDShape(){}//takecareofinheritanceTwoDShape.prototype=Shape.prototype;TwoDShape.prototype.constructor=TwoDShape;//augmentprototypeTwoDShape.prototype.name='2Dshape';
function Triangle(side, height) {this.side=side;this.height=height;}
//takecareofinheritanceTriangle.prototype=TwoDShape.prototype;Triangle.prototype.constructor=Triangle;//augmentprototypeTriangle.prototype.name='Triangle';Triangle.prototype.getArea=function(){returnthis.side*this.height/2;};
Thetestcodegivesyouthesameresult:
>varmy=newTriangle(5,10);>my.getArea();25>my.toString();"Triangle"
What'sthedifferenceinthelookupswhencallingmy.toString()?First,asusual,theJavaScriptenginelooksforatoString()methodofthemyobjectitself.Theenginedoesn'tfindsuchamethod,soitinspectstheprototype.TheprototypeturnsouttobepointingtothesameobjectthattheprototypeofTwoDShapepointstoandalsothesameobjectthatShape.prototypepointsto.Rememberthatobjectsarenotcopiedbyvalue,butonlybyreference.So,thelookupisonlyatwo-stepprocessasopposedtofour(inthepreviousexample)orthree(inthefirstexample).
Simplycopyingtheprototypeismoreefficient,butithasasideeffectbecause,alltheprototypesofthechildrenandparentspointtothesameobject,whenachildmodifiestheprototype,theparentsgetthechangesandsodothesiblings.
Lookatthefollowingline:
Triangle.prototype.name = 'Triangle';
Itchangesthenameproperty,soiteffectivelychangesShape.prototype.nametoo.IfyoucreateaninstanceusingnewShape(),itsnamepropertysays"Triangle":
>vars=newShape();>s.name;"Triangle"
Thismethodismoreefficient,butmaynotsuitallyourusecases.
Atemporaryconstructor-newF()
Asolutiontothepreviouslyoutlinedproblem,whereallprototypespointtothesameobjectandtheparentsgetchildren'sproperties,istouseanintermediarytobreakthechain.Theintermediaryisintheformofatemporaryconstructorfunction.CreatinganemptyfunctionF()andsettingitsprototypetotheprototypeoftheparentconstructorallowsyoutocallnewF()andcreateobjectsthathavenopropertiesoftheirown,butinheriteverythingfromtheparent'sprototype.
Let'stakealookatthemodifiedcode:
functionShape(){}//augmentprototypeShape.prototype.name='Shape';
Shape.prototype.toString = function () {returnthis.name;};
functionTwoDShape(){}//takecareofinheritancevarF=function(){};F.prototype=Shape.prototype;TwoDShape.prototype=newF();TwoDShape.prototype.constructor=TwoDShape;//augmentprototypeTwoDShape.prototype.name='2Dshape';
functionTriangle(side,height){this.side=side;this.height=height;}
//takecareofinheritancevarF=function(){};F.prototype=TwoDShape.prototype;Triangle.prototype=newF();Triangle.prototype.constructor=Triangle;//augmentprototypeTriangle.prototype.name='Triangle';
Triangle.prototype.getArea = function () {returnthis.side*this.height/2;};
Creatingmytriangleandtestingthemethods:
>varmy=newTriangle(5,10);>my.getArea();25
>my.toString();"Triangle"
Usingthisapproach,theprototypechainstaysinplace:
>my.__proto__===Triangle.prototype;true>my.__proto__.constructor===Triangle;true>my.__proto__.__proto__===TwoDShape.prototype;true>my.__proto__.__proto__.__proto__.constructor===Shape;true
Also,theparents'propertiesarenotoverwrittenbythechildren:
>vars=newShape();>s.name;"Shape">"Iama"+newTwoDShape();//callingtoString()"Iama2Dshape"
Atthesametime,thisapproachsupportstheidea thatonlypropertiesandmethodsaddedtotheprototypeshouldbeinheritedandownpropertiesshouldnot.Therationalebehindthisisthatownpropertiesarelikelytobetoospecifictobereusable.
Uber - access to the parent from a childobjectClassicalOOlanguagesusuallyhaveaspecialsyntaxthatgivesyouaccesstotheparentclass,alsoreferredtothesuperclass.Thiscouldbeconvenientwhenachildwantstohaveamethodthatdoeseverythingtheparent'smethoddoes,plussomethinginadditiontoit.Insuchcases,thechildcallstheparent'smethodwiththesamenameandworkswiththeresult.
InJavaScript,thereisnosuchspecialsyntax,but it'strivialtoachievethesamefunctionality.Let'srewritethelastexample,andwhiletakingcareofinheritance,alsocreateanuberpropertythatpointstotheparent'sprototypeobject:
functionShape(){}//augmentprototypeShape.prototype.name='Shape';Shape.prototype.toString=function(){varconst=this.constructor;returnconst.uber?this.const.uber.toString()+','+this.name:this.name;
};
functionTwoDShape(){}//takecareofinheritancevarF=function(){};F.prototype=Shape.prototype;TwoDShape.prototype=newF();TwoDShape.prototype.constructor=TwoDShape;TwoDShape.uber=Shape.prototype;//augmentprototypeTwoDShape.prototype.name='2Dshape';
functionTriangle(side,height){this.side=side;this.height=height;}
//takecareofinheritancevarF=function(){};F.prototype=TwoDShape.prototype;Triangle.prototype=newF();Triangle.prototype.constructor=Triangle;Triangle.uber=TwoDShape.prototype;//augmentprototypeTriangle.prototype.name='Triangle';
Triangle.prototype.getArea = function () {returnthis.side*this.height/2;};
Thenewthingshereare:
Anewuberpropertypointstotheparent'sprototypeTheupdatedtoString()method
Previously,toString()onlyreturnedthis.name.Now,inadditiontothis,thereisachecktoseewhetherthis.constructor.uberexistsand,ifitdoes,callitstoString()first.Thethis.constructoristhefunctionitself,andthis.constructor.uberpointstotheparent'sprototype.TheresultisthatwhenyoucalltoString()foraTriangleinstance,alltoString()methodsuptheprototypechainarecalled:
>varmy=newTriangle(5,10);>my.toString();"Shape,2Dshape,Triangle"
Thenameoftheuberpropertycould'vebeensuperclass,butthiswouldsuggestthatJavaScripthasclasses.Ideally,itcould'vebeensuper(asinJava),butsuperisareservedwordinJavaScript.TheGermanwordubersuggestedbyDouglasCrockfordmeansmoreorlessthesameassuper,andyouhavetoadmit,itsoundsubercool.
Isolating the inheritance part into a functionLet's move the code that takes care of all the inheritance details from the last example into areusableextend()function:
functionextend(Child,Parent){varF=function(){};F.prototype=Parent.prototype;Child.prototype=newF();Child.prototype.constructor=Child;Child.uber=Parent.prototype;}
Usingthisfunction(oryourowncustomversionofit)helpsyoukeepyourcodecleanwithregardtotherepetitiveinheritance-relatedtasks.Thisway,youcaninheritbysimplyusingthefollowingtwolinesofcode:
extend(TwoDShape, Shape);extend(Triangle,TwoDShape);
Let'sseeacompleteexample:
//inheritancehelperfunctionextend(Child,Parent){varF=function(){};F.prototype=Parent.prototype;Child.prototype=newF();Child.prototype.constructor=Child;Child.uber=Parent.prototype;}
//define->augmentfunctionShape(){}Shape.prototype.name='Shape';Shape.prototype.toString=function(){returnthis.constructor.uber?this.constructor.uber.toString()+','+this.name:this.name;};
//define->inherit->augmentfunctionTwoDShape(){}extend(TwoDShape,Shape);TwoDShape.prototype.name='2Dshape';
//definefunctionTriangle(side,height){this.side=side;this.height=height;}
//inheritextend(Triangle,TwoDShape);//augmentTriangle.prototype.name='Triangle';Triangle.prototype.getArea=function(){returnthis.side*this.height/2;};
Letstestthefollowingcode:
>newTriangle().toString();"Shape,2Dshape,Triangle"
Copying propertiesNow, let's try a slightly different approach. Since inheritance is all about reusing code, can yousimplycopythepropertiesyoulikefromoneobjecttoanother?Orfromaparenttoachild?Keepingthesameinterfaceastheprecedingextend()function,youcancreateaextend2()function,whichtakestwoconstructorfunctionsandcopiesallthepropertiesfromtheparent'sprototypetothechild'sprototype.Thiswill,ofcourse,carryovermethodstoo,asmethodsarejustpropertiesthathappentobefunctions:
functionextend2(Child,Parent){varp=Parent.prototype;varc=Child.prototype;for(variinp){c[i]=p[i];}c.uber=p;}
Asyoucansee,asimpleloopthroughthepropertiesisallittakes.Aswiththepreviousexample,youcansetanuberpropertyifyouwanttohavehandyaccesstoparent'smethodsfromthechild.Unlikethepreviousexamplethough,it'snotnecessarytoresetChild.prototype.constructorbecausehere,thechildprototypeisaugmented,notoverwrittencompletely.So,theconstructorpropertypointstotheinitialvalue.
Thismethodisalittleinefficientcomparedtothepreviousmethodbecausepropertiesofthechildprototypearebeingduplicatedinsteadofsimplybeinglookedupviatheprototypechainduringexecution.Bearinmindthatthisisonlytrueforpropertiescontainingprimitivetypes.Allobjects(includingfunctionsandarrays)arenotduplicated,becausethesearepassedbyreferenceonly.
Let'sseeanexampleofusingtwoconstructorfunctions,Shape()andTwoDShape().TheShape()function'sprototypeobjectcontainsaprimitiveproperty,name,andanon-primitiveone,thetoString()method:
varShape=function(){};var TwoDShape = function () {};
Shape.prototype.name='Shape';Shape.prototype.toString=function(){returnthis.uber?this.uber.toString()+','+this.name:this.name;};
Ifyouinheritwithextend(),neithertheobjectscreatedwithTwoDShape()noritsprototypegetanownnameproperty,buttheyhaveaccesstotheonetheyinherit:
>extend(TwoDShape,Shape);
>vartd=newTwoDShape();>td.name;"Shape">TwoDShape.prototype.name;"Shape">td.__proto__.name;"Shape">td.hasOwnProperty('name');false>td.__proto__.hasOwnProperty('name');false
However,ifyouinheritwithextend2(),theprototypeofTwoDShape()getsitsowncopyofthenameproperty.ItalsogetsitsowncopyoftoString(),butit'sareferenceonly,sothefunctionwillnotberecreatedasecondtime:
>extend2(TwoDShape,Shape);>vartd=newTwoDShape();>td.__proto__.hasOwnProperty('name');true>td.__proto__.hasOwnProperty('toString');true>td.__proto__.toString===Shape.prototype.toString;true
Asyoucansee,thetwotoString()methodsarethesamefunctionobject.Thisisgoodbecauseitmeansthatnounnecessaryduplicatesofthemethodsarecreated.
So,youcansaythatextend2()islessefficientthanextend()becauseitrecreatesthepropertiesoftheprototype.However,thisisnotsobadbecauseonlytheprimitivedatatypesareduplicated.Additionally,thisisbeneficialduringtheprototypechainlookupsastherearefewerchainlinkstofollowbeforefindingtheproperty.
Takealookattheuberpropertyagain.Thistime,forachange,it'ssetontheParentobject'sprototypep,notontheParentconstructor.ThisiswhytoString()uses itasthis.uberasopposedtothis.constructor.uber.Thisisjustanillustrationthatyou canshapeyourfavoriteinheritancepatterninanywayyouseefit.Let'stestitout:
>td.toString();"Shape,Shape"
TwoDShapedidn'tredefinethenameproperty,hencetherepetition.Itcandothatatanytime,and(theprototypechainbeinglive)alltheinstancesseetheupdate:
>TwoDShape.prototype.name="2Dshape";>td.toString();"Shape,2Dshape"
Heads-up when copying by referenceThe fact that objects (including functions and arrays) are copied by reference could sometimesleadtoresultsyoudon'texpect.
Let'screatetwoconstructorfunctionsandaddpropertiestotheprototypeofthefirstone:
>functionPapa(){}>functionWee(){}>Papa.prototype.name='Bear';>Papa.prototype.owns=["porridge","chair","bed"];
Now,let'shaveWeeinheritfromPapa(eitherextend()orextend2()willdo):
>extend2(Wee,Papa);
Usingextend2(),theWeefunction'sprototypeinheritedthepropertiesofPapa.prototypeasitsown:
>Wee.prototype.hasOwnProperty('name');true>Wee.prototype.hasOwnProperty('owns');
true
Thenamepropertyisprimitive,soanewcopyofitiscreated.Theownspropertyisanarrayobject,soit'scopiedbyreference:
>Wee.prototype.owns;["porridge","chair","bed"]>Wee.prototype.owns===Papa.prototype.owns;true
ChangingtheWeefunction'scopyofnamedoesn'taffectPapa:
>Wee.prototype.name+=',LittleBear';"Bear,LittleBear">Papa.prototype.name;"Bear"
ChangingtheWeefunction'sownsproperty,however,affectsPapa,becausebothpropertiespointtothesamearrayinmemory:
>Wee.prototype.owns.pop();"bed">Papa.prototype.owns;["porridge","chair"]
It'sadifferentstorywhenyoucompletelyoverwritetheWeefunction'scopyofownswithanotherobject(asopposedtomodifyingtheexistingone).Inthiscase,Papa.ownskeepspointingtothe
oldobject,whileWee.ownspointstoanewone:
>Wee.prototype.owns=["emptybowl","brokenchair"];>Papa.prototype.owns.push('bed');>Papa.prototype.owns;["porridge","chair","bed"]
Thinkofanobjectassomethingthatiscreatedandstoredinaphysicallocationinmemory.Variablesandpropertiesmerelypointtothislocation,sowhenyouassignabrandnewobjecttoWee.prototype.owns,youessentiallysay-Hey,forgetaboutthisotheroldobject,moveyourpointertothisnewoneinstead.
Thefollowingdiagramillustrateswhathappensifyouimaginethememorybeingaheapofobjects(likeawallofbricks)andyoupointto(referto)someoftheseobjects:
Anewobjectiscreated,andApointstoit.AnewvariableBiscreatedandmadeequaltoA,meaningitnowpointstothesameplaceAispointingto.ApropertycolorischangedusingtheBhandle(pointer).Thebrickisnowwhite.AcheckforA.color==="white"wouldbetrue.Anewobjectiscreated,andtheBvariable/pointerisrecycledtopointtothatnewobject.AandBarenowpointingtodifferentpartsofthememorypile.Theyhavenothingincommonandchangestooneofthemdon'taffecttheother:
Ifyouwanttoaddresstheproblemthatobjectsarecopiedbyreference,consideradeepcopy,describedlaterinthechapter.
Objects inherit from objectsAll the examples so far in this chapter assume that you create your objects with constructorfunctions,andyouwantobjectscreatedwithoneconstructortoinheritpropertiesthatcomefromanotherconstructor.However,youcanalsocreateobjectswithoutthehelpofaconstructorfunction,justusingtheobjectliteral,andthisis,infact,lesstyping.So,howaboutinheritingthose?
InJavaorPHP,youdefineclassesandhavetheminheritfromotherclasses.That'swhyyou'llseethetermclassical,becausetheOOfunctionalitycomesfromtheuseofclasses.InJavaScript,therearenoclasses,soprogrammersthatcomefromaclassicalbackgroundresorttoconstructorfunctions,becauseconstructorsaretheclosesttowhattheyareusedto.In addition,JavaScriptprovidesthenewoperator,whichcanfurthersuggestthatJavaScriptislikeJava.Thetruthisthat,intheend,itallcomesdowntoobjects.Thefirst exampleinthischapterusedthissyntax:
Child.prototype=newParent();
Here,theChildconstructor(orclass,ifyouwill)inheritsfromParent.However,thisisdonebycreatinganobjectusingnewParent()andinheritingfromit.That'swhythisisalsoreferredtoasapseudo-classicalinheritancepattern,becauseitresemblesclassicalinheritance,althoughitisn't(noclassesareinvolved).
So,whynotgetridofthemiddleman(theconstructor/class)andjusthave objectsinheritfromobjects?Inextend2(),thepropertiesoftheparentprototypeobjectwerecopiedaspropertiesofthechildprototypeobject.Thetwoprototypesare,inessence,justobjects.Forgettingaboutprototypesandconstructorfunctions,youcansimplytakeanobjectandcopyallofitspropertiesintoanotherobject.
You already know that objects can start as a blank canvas without any own properties, using varo = {};, and then get properties later. However, instead of starting fresh, you can start bycopying all of the properties of an existing object. Here's a function that does exactly this: it takesanobjectandreturnsanewcopyofit:
functionextendCopy(p){varc={};for(variinp){c[i]=p[i];}c.uber=p;returnc;}
Simplycopyingallthepropertiesisastraightforwardpattern,andit'swidelyused.Let'sseethisfunctioninaction.Youstartbyhavingabaseobject:
varshape={name:'Shape',toString:function(){returnthis.name;}};
Inordertocreateanewobjectthatbuildsupontheoldone,youcancalltheextendCopy()function,whichreturnsanewobject.Then,youcanaugmentthenewobjectwithadditionalfunctionality:
vartwoDee=extendCopy(shape);twoDee.name='2Dshape';twoDee.toString=function(){returnthis.uber.toString()+','+this.name;
};
Hereisatriangleobjectthatinheritsthe2Dshapeobject:
vartriangle=extendCopy(twoDee);triangle.name='Triangle';triangle.getArea=function(){returnthis.side*this.height/2;};
Usingthetriangle,forexample:
>triangle.side=5;>triangle.height=10;>triangle.getArea();25>triangle.toString();"Shape,2Dshape,Triangle"
Apossible drawback of this method is the somewhat verbose way of initializing the newtriangleobject,where youmanuallysetvaluesforsideandheight,asopposedtopassingthemasvaluestoaconstructor.However,thisiseasilyresolvedbyhavingafunction,forexample,calledinit()(or__construct()ifyoucomefromPHP)thatactsasaconstructorandacceptsinitializationparameters.Alternatively,haveextendCopy()accepttwoparameters,anobjecttoinheritfromandanotherobjectliteralofpropertiestoaddtothe copybeforeit'sreturned.Inotherwords,justmergetwoobjects.
Deep copyThe extendCopy() function discussed previously creates what is called a shallow copy of anobject, just like extend2() before that. The opposite of a shallow copy would be, naturally, adeep copy. As discussed previously (in the Heads-up when copying by reference section of thischapter),whenyoucopy objects,youonlycopypointerstothelocationinmemorywheretheobjectisstored.Thisiswhathappensinashallowcopy.Ifyoumodifyanobjectinthecopy,youalsomodifytheoriginal. Thedeepcopyavoidsthisproblem.
Thedeepcopyisimplementedinthesamewayastheshallowcopy-youloopthroughthepropertiesandcopythemonebyone.However,whenyouencounterapropertythatpointstoanobject,youcallthedeepcopyfunctionagain:
functiondeepCopy(p,c){c=c||{};for(variinp){if(p.hasOwnProperty(i)){
if (typeof p[i] === 'object') {c[i]=Array.isArray(p[i])?[]:{};deepCopy(p[i],c[i]);}else{c[i]=p[i];}}}returnc;}
Let'screateanobjectthathasarraysandasubobjectasproperties:
var parent = {numbers:[1,2,3],letters:['a','b','c'],obj:{prop:1},bool:true};
Let'stestthisbycreatingadeepcopyandashallowcopy.Unliketheshallowcopy,whenyouupdatethenumberspropertyofadeepcopy,theoriginalisnotaffected:
>varmydeep=deepCopy(parent);>varmyshallow=extendCopy(parent);
>mydeep.numbers.push(4,5,6);6>mydeep.numbers;[1,2,3,4,5,6]>parent.numbers;
[1,2,3]>myshallow.numbers.push(10);4>myshallow.numbers;[1,2,3,10]>parent.numbers;[1,2,3,10]>mydeep.numbers;[1,2,3,4,5,6]
TwosidenotesaboutthedeepCopy()function:
Filteringoutnon-ownpropertieswithhasOwnProperty()isalwaysagoodideatomakesureyoudon'tcarryoversomeone'sadditionstothecoreprototypes.Array.isArray()existssinceES5becauseit'ssurprisinglyhardotherwisetotellrealarraysfromobjects.Thebestcross-browsersolution(ifyouneedtodefineisArray()inES3browsers)looksalittlehacky,butitworks:
if(Array.isArray!=="function"){Array.isArray=function(candidate){returnObject.prototype.toString.call(candidate)==='[objectArray]';};}
Using object() methodBased on the idea that objects inherit from objects, Douglas Crockford advocates the use of anobject()functionthatacceptsanobjectandreturnsanewonethathastheparentasaprototype:
functionobject(o){functionF(){}F.prototype=o;returnnewF();}
Ifyouneedaccesstoanuberproperty,youcanmodifytheobject()functionasfollows:
functionobject(o){varn;functionF(){}F.prototype=o;n=newF();n.uber=o;returnn;}
UsingthisfunctionisthesameasusingextendCopy(),youtakeanobjectsuchastwoDee,createanewobjectfromit,andthenproceedtoaugmentingthenewobject:
vartriangle=object(twoDee);triangle.name='Triangle';triangle.getArea=function(){returnthis.side*this.height/2;};
Thenewtrianglestillbehavesthesameway:
>triangle.toString();"Shape,2Dshape,Triangle"
Thispatternisalsoreferredtoasprototypalinheritance,becauseyouuseaparentobjectastheprototypeofachildobject.It'salsoadoptedandbuiltuponinES5andcalledObject.create().Hereisanexample:
>varsquare=Object.create(triangle);
Using a mix of prototypal inheritance andcopyingpropertiesWhenyouuseinheritance,youwillmostlikelywanttotakeanalreadyexistingfunctionalityandthenbuilduponit.Thismeanscreatinganewobjectbyinheritingfroman existingobjectandthenaddingadditionalmethodsandproperties.Youcandothiswithonefunctioncallusingacombinationofthelasttwoapproachesjustdiscussed.
Youcan:
Useprototypalinheritancetouseanexistingobjectasaprototypeof anewoneCopyallthepropertiesofanotherobjectintothenewlycreatedone:
functionobjectPlus(o,stuff){var n;
functionF(){}F.prototype=o;n=newF();n.uber=o;for(variinstuff){n[i]=stuff[i];}returnn;}
Thisfunctiontakesanobjectotoinheritfromandanotherobjectstuffthathastheadditionalmethodsandpropertiesthataretobecopied.Let'sseethisinaction.
Startwiththebaseshapeobject:
varshape={name:'Shape',toString:function(){returnthis.name;}};
Createa2Dobjectbyinheritingshapeandaddingmoreproperties.Theadditionalpropertiesaresimplycreatedwithanobjectliteral:
vartwoDee=objectPlus(shape,{name:'2Dshape',
toString: function () {returnthis.uber.toString()+','+this.name;}});
Now,let'screateatriangleobjectthatinheritsfrom2Dandaddsmoreproperties:
vartriangle=objectPlus(twoDee,{name:'Triangle',getArea:function(){returnthis.side*this.height/2;},side:0,height:0});
Youcantesthowitallworksbycreatingaconcretetrianglemywithdefinedsideandheight:
varmy=objectPlus(triangle,{side:4,height:4});>my.getArea();8>my.toString();
"Shape, 2D shape, Triangle, Triangle"
Thedifferencehere,whenexecutingtoString(),isthattheTrianglenameisrepeatedtwice.That'sbecausetheconcreteinstancewascreatedbyinheritingtriangle,sotherewasonemorelevelofinheritance.Youcouldgivethenewinstanceaname:
>objectPlus(triangle,{side:4,height:4,name:'My4x4'}).toString();"Shape,2Dshape,Triangle,My4x4"
ThisobjectPlus()isevenclosertoES5'sObject.create();onlytheES5onetakestheadditionalproperties(thesecondargument)usingsomethingcalledpropertydescriptors(discussedinAppendixC,Built-InObjects).
Multiple inheritanceMultiple inheritance is where a child inherits frommore than one parent. Some OO languagessupportmultipleinheritanceoutoftheboxandsomedon't.Youcanarguebothways,thatmultipleinheritanceisconvenientorthatit'sunnecessary,complicatesapplicationdesign,andit'sbettertouseaninheritancechaininstead.Leavingthediscussionofmultipleinheritance'sprosandconsforthelong,coldwinternights,let'sseehowyoucandoitinpracticeinJavaScript.
Theimplementationcanbeassimpleastakingtheideaofinheritancebycopyingpropertiesandexpandingitsothatittakesanunlimitednumberofinputobjectstoinheritfrom.
Let'screateamulti()functionthatacceptsanynumberofinputobjects.Youcanwraptheloopthatcopiespropertiesinanotherloopthatgoesthroughalltheobjectspassedasargumentstothefunction:
functionmulti(){varn={},stuff,j=0,len=arguments.length;for(j=0;j<len;j++){stuff=arguments[j];for(variinstuff){if(stuff.hasOwnProperty(i)){n[i]=stuff[i];
}}}returnn;}
Let'stestthisbycreatingthreeobjects-shape,twoDee,andathird,unnamedobject.Then,creatingatriangleobjectmeanscallingmulti()andpassingallthreeobjects:
varshape={name: 'Shape',
toString:function(){returnthis.name;}};
vartwoDee={name:'2Dshape',dimensions:2};
vartriangle=multi(shape,twoDee,{name:'Triangle',getArea:function(){returnthis.side*this.height/2;},side:5,
height:10});
Doesthiswork?Let'ssee.ThegetArea()methodshouldbeanownproperty,dimensionsshouldcomefromtwoDee,andtoString()shouldcomefromshape:
>triangle.getArea();25>triangle.dimensions;2>triangle.toString();"Triangle"
Bearinmindthatmulti()loopsthroughtheinputobjectsintheordertheyappearandifithappensthattwoofthemhavethesameproperty, thelastonewins.
Mixins
Youmightcomeacrossthetermmixin.Thinkofamixinasanobjectthatprovidessomeusefulfunctionalitybutisnotmeanttobeinheritedandextendedbysubobjects.Theapproachtomultipleinheritanceoutlinedpreviouslycanbeconsideredanimplementationofthemixinsidea.Whenyoucreateanewobject,youcanpickandchooseanyotherobjectstomixintoyournewobject.Bypassingthemalltomulti(),yougetalltheirfunctionalitywithoutmakingthempartoftheinheritancetree.
Parasitic inheritanceIf you like the fact that you can have all kinds of different ways to implement inheritance inJavaScriptandyou'rehungryformore,here'sanotherone.Thispattern,courtesyofDouglasCrockford,iscalledparasiticinheritance.It'saboutafunctionthatcreatesobjectsbytakingallthefunctionalityfromanotherobjectintoanewone,augmentingthenewobject,andreturningit,pretendingthatithasdoneallthework.
Here'sanordinaryobject,definedwithanobject literal,andunawareofthefactthatit'ssoongoingtofallvictimtoparasitism:
vartwoD={name:'2Dshape',dimensions:2};
A function that creates triangle objects could:
UsethetwoDobjectasaprototypeofanobjectcalledthat(similartothisforconvenience).Thiscanbedoneinanywayyousawpreviously,forexample,usingtheobject()functionorcopyingalltheproperties.Augmentthatwithmoreproperties.Returnthat:
functiontriangle(s,h){varthat=object(twoD);that.name='Triangle';
that.getArea = function () {returnthis.side*this.height/2;};that.side=s;that.height=h;returnthat;}
Becausetriangle()isanormalfunction,notaconstructor,itdoesn'trequirethenewoperator.However,becauseitreturnsanobject,callingitwithnewbymistakeworkstoo:
>vart=triangle(5,10);>t.dimensions;2>vart2=newtriangle(5,5);>t2.getArea();12.5
Notethatthatisjustaname,itdoesn'thaveaspecialmeaning,thewaythisdoes.
Borrowing a constructorOne more way of implementing inheritance (the last one in the chapter, I promise) has to do againwithconstructorfunctionsandnottheobjectsdirectly.Inthispattern,theconstructorofthechildcallstheconstructoroftheparentusingeitherthe call()orapply()method.Thiscanbecalledstealingaconstructororinheritancebyborrowingaconstructorifyouwanttobemoresubtleaboutit.
Thecall()andapply()methodswerediscussedinChapter4,Objects, buthere'sarefresher;theyallowyoutocallafunctionandpassanobjectthatthefunctionshouldbindtoitsthisvalue.Soforinheritancepurposes,thechildconstructorcallstheparent'sconstructorandbindsthechild'snewlycreatedthisobjectastheparent'sthis.
Let'shavethisparentconstructorShape():
functionShape(id){this.id=id;}Shape.prototype.name='Shape';Shape.prototype.toString=function(){returnthis.name;};
Now,let'sdefineTriangle(),whichusesapply()tocalltheShape()constructor,passingthis(aninstancecreatedwithnewTriangle())andanyadditionalarguments:
functionTriangle(){Shape.apply(this,arguments);}Triangle.prototype.name='Triangle';
NotethatbothTriangle()andShape()haveaddedsomeextrapropertiestotheirprototypes.
Now,let'stestthisbycreatinganewtriangleobject:
>vart=newTriangle(101);>t.name;"Triangle"
Thenewtriangleobjectinheritstheidpropertyfromtheparent,butitdoesn'tinheritanythingaddedtotheparent'sprototype:
>t.id;101>t.toString();"[objectObject]"
ThetrianglefailedtogettheShapefunction'sprototypepropertiesbecausetherewasneveranewShape()instancecreated,sotheprototypewasneverused.However,yousawhowtodothisatthebeginningofthischapter.YoucanredefineTriangleasfollows:
functionTriangle(){Shape.apply(this,arguments);}Triangle.prototype=newShape();Triangle.prototype.name='Triangle';
Inthisinheritancepattern,theparent'sownpropertiesarerecreatedasthechild'sownproperties.Ifachildinheritsanarrayorotherobject,it'sacompletelynewvalue(notareference),andmodifyingitwon'taffecttheparent.
Thedrawbackisthatthe parent'sconstructorgetscalledtwice-oncewithapply()toinheritownpropertiesandoncewithnewtoinherittheprototype.Infact,theownpropertiesoftheparentareinheritedtwice.Let'stakethissimplifiedscenario:
functionShape(id){this.id=id;}functionTriangle(){Shape.apply(this,arguments);}Triangle.prototype=newShape(101);
Here,wewillcreateanewinstance:
>vart=newTriangle(202);>t.id;202
There'sanownpropertyid,butthere'salsoonethatcomesdowntheprototypechain,readytoshinethrough:
>t.__proto__.id;101>deletet.id;true
>t.id;101
Borrowingaconstructorandcopyingitsprototype
Theproblemofthedoubleworkperformedbycallingtheconstructortwicecaneasilybecorrected.Youcancallapply()ontheparentconstructortogetallownpropertiesandthencopytheprototype'spropertiesusingasimpleiteration(orextend2()asdiscussedpreviously):
function Shape(id) {this.id=id;}Shape.prototype.name='Shape';Shape.prototype.toString=function(){returnthis.name;};
functionTriangle(){Shape.apply(this,arguments);}extend2(Triangle,Shape);Triangle.prototype.name='Triangle';
Letstestthefollowingcode:
>vart=newTriangle(101);>t.toString();"Triangle">t.id;101
Nodoubleinheritance:
>typeoft.__proto__.id;"undefined"
Theextend2()methodalsogivesaccesstouberifneeded:
>t.uber.name;"Shape"
Case study - drawing shapesLet's finish off this chapter with a more practical example of using inheritance. The task is to beabletocalculatetheareaandtheperimeterofdifferentshapes,aswellastodrawthem,whilereusingasmuchcodeaspossible.
Analysis
Let'shaveoneShapeconstructorthatcontainsallthecommonparts.Fromthere,let'shaveTriangle,Rectangle,andSquareconstructors,allinheritingfromShape.Asquareisreallyarectanglewiththesamelengthsides,solet'sreuseRectanglewhenbuildingSquare.
Inordertodefineashape,you'llneedpointswithxandycoordinates.Agenericshapecanhaveanynumberofpoints.Atriangleisdefinedwiththreepoints,arectangle(tokeepitsimpler)withonepointandthelengthsofthesides.Theperimeterofanyshapeisthesumofitsside'slengths.Calculatingtheareaisshapespecificandwillbeimplementedbyeachshape.
ThecommonfunctionalityinShapewouldbe:
Adraw()methodthatcandrawanyshapegiventhepointsAgetParameter()methodApropertythatcontainsanarrayofpointsOthermethodsandpropertiesasneeded
Forthedrawingpart,let'susea<canvas>tag.It'snotsupportedinearlyIEs,buthey,thisisjustanexercise.
Let'shavetwootherhelperconstructors-PointandLine.Pointwillhelpwhendefiningshapes.Linewillmakecalculationseasier,asitcangivethelengthofthelineconnectinganytwogivenpoints.
Youcanplaywithaworkingexampleathttp://www.phpied.com/files/canvas/.Justopenyourconsoleandstartcreatingnewshapesasyou'llseeinamoment.
Implementation
Let'sstartbyaddingacanvastagtoablankHTMLpage:
<canvasheight="600"width="800"id="canvas"/>
Then,puttheJavaScriptcodeinside<script>tags:
<script>// ... code goes here
</script>
Now,let'stakealookatwhat'sintheJavaScriptpart.FirstisthehelperPointconstructor.Itjustcan'tgetanysimplerthanthefollowing:
functionPoint(x,y){this.x=x;this.y=y;}
Bear in mind that the coordinates of the points on the canvas start from x=0, y=0, which is the topleft. The bottom right will be x = 800, y = 600:
NextcomestheLineconstructor.Ittakestwopointsandcalculatesthelengthofthelinebetween
them,usingthePythagoreantheorema2+b2=c2(imaginearight-angledtrianglewherethehypotenuseconnectsthetwogivenpoints):
function Line(p1, p2) {
this.p1=p1;this.p2=p2;this.length=Math.sqrt(Math.pow(p1.x-p2.x,2)+Math.pow(p1.y-p2.y,2));}
NextcomestheShapeconstructor.Theshapeswillhavetheirpoints(and thelinesthatconnectthem)asownproperties.Theconstructoralsoinvokesaninitializationmethod,init(),thatwillbedefinedintheprototype:
functionShape(){this.points=[];this.lines=[];this.init();}
Now,thebigpart-themethodsofShape.prototype.Let'sdefineallthesemethodsusingtheobjectliteralnotation.Refertothecommentsforguidelinesastowhateachmethoddoes:
Shape.prototype={//resetpointertoconstructorconstructor:Shape,
// initialization, sets this.context to point//tothecontextifthecanvasobjectinit:function(){if(this.context===undefined){varcanvas=document.getElementById('canvas');Shape.prototype.context=canvas.getContext('2d');}},
//methodthatdrawsashapebyloopingthroughthis.pointsdraw:function(){vari,ctx=this.context;ctx.strokeStyle=this.getColor();ctx.beginPath();ctx.moveTo(this.points[0].x,this.points[0].y);for(i=1;i<this.points.length;i++){ctx.lineTo(this.points[i].x,this.points[i].y);}ctx.closePath();ctx.stroke();},
//methodthatgeneratesarandomcolorgetColor:function(){vari,rgb=[];
for (i = 0; i< 3; i++) {rgb[i]=Math.round(255*Math.random());
}return'rgb('+rgb.join(',')+')';},
//methodthatloopsthroughthepointsarray,//createsLineinstancesandaddsthemtothis.linesgetLines:function(){if(this.lines.length>0){returnthis.lines;}vari,lines=[];for(i=0;i<this.points.length;i++){lines[i]=newLine(this.points[i],this.points[i+1]||this.points[0]);}this.lines=lines;returnlines;},
//shellmethod,tobeimplementedbychildrengetArea:function(){},
//sumsthelengthsofalllinesgetPerimeter:function(){vari,perim=0,lines=this.getLines();for(i=0;i<lines.length;i++){perim+=lines[i].length;}returnperim;}};
Now,thechildrenconstructorfunctions.Trianglecomesfirst:
functionTriangle(a,b,c){this.points=[a,b,c];this.getArea=function(){varp=this.getPerimeter(),s=p/2;returnMath.sqrt(s*(s-this.lines[0].length)*(s-this.lines[1].length)*(s-this.lines[2].length));};}
TheTriangleconstructortakesthreepointobjectsandassignsthemtothis.points(itsowncollectionofpoints).Then,itimplementsthegetArea()method,usingHeron'sformula:
Area=s(s-a)(s-b)(s-c)
sisthesemi-perimeter(perimeterdividedbytwo).
NextcomestheRectangleconstructor.Itreceivesonepoint(theupper-leftpoint)andthelengthsofthetwosides.Then,itpopulatesitspointsarraystartingfromthatonepoint:
functionRectangle(p,side_a,side_b){this.points=[p,newPoint(p.x+side_a,p.y),//toprightnewPoint(p.x+side_a,p.y+side_b),//bottomrightnewPoint(p.x,p.y+side_b)//bottomleft];
this.getArea = function () {returnside_a*side_b;};}
ThelastchildconstructorisSquare.Asquareisaspecialcaseofarectangle,soitmakessensetoreuseRectangle.Theeasiestthingtodohereistoborrowtheconstructor:
functionSquare(p,side){Rectangle.call(this,p,side,side);
}
Nowthatallconstructorsaredone,let'stakecareofinheritance.Anypseudo-classicalpattern(onethatworkswithconstructorsasopposedtoobjects)willdo.Let'stryusingamodifiedandsimplifiedversionoftheprototype-chainingpattern(thefirstmethoddescribedinthischapter).Thispatterncallsforcreatinganewinstanceoftheparentandsettingitasthechild'sprototype.Inthiscase,it'snotnecessarytohaveanewinstanceforeachchild-theycanallshareit:
(function () {vars=newShape();Triangle.prototype=s;Rectangle.prototype=s;Square.prototype=s;})();
Testing
Let'stestthisbydrawingshapes.First,definethreepointsforatriangle:
>var p1 = new Point(100, 100);>varp2=newPoint(300,100);>varp3=newPoint(200,0);
NowyoucancreateatrianglebypassingthethreepointstotheTriangleconstructor:
>vart=newTriangle(p1,p2,p3);
Youcancallthemethodstodrawthetriangleonthecanvasandgetitsareaandperimeter:
>t.draw();>t.getPerimeter();482.842712474619>t.getArea();10000.000000000002
Nowlet'splaywitharectangleinstance:
>varr=newRectangle(newPoint(200,200),50,100);>r.draw();>r.getArea();5000>r.getPerimeter();300
Andfinally,let'splaywithasquare:
>vars=newSquare(newPoint(130,130),50);>s.draw();>s.getArea();2500>s.getPerimeter();200
It'sfuntodrawtheseshapes.Youcanalsobeaslazyasthefollowingexample,whichdrawsanothersquare,reusingatriangle'spoint:
> new Square(p1, 200).draw();
Theresultofthetestswillbesomethinglikethefollowing:
ExercisesLets do the following exercise:
1. Implementmultipleinheritancebutwithaprototypalinheritancepattern,notpropertycopying.Hereisan example:
varmy=objectMulti(obj,another_obj,a_third,{additional:"properties"});
The additional property should be an own property; all the rest should be mixed into theprototype.
2. Usethecanvasexampletopractice.Tryoutdifferentthings.Herearesomeexamples:Drawafewtriangles,squares,andrectangles.Addconstructorsformoreshapes,suchasTrapezoid,Rhombus,Kite,andPentagon.Ifyouwanttolearnmoreaboutthecanvastag,createaCircleconstructortoo.Itwillneedtooverwritethedraw()methodoftheparent.Canyouthinkofanotherwaytoapproachtheproblemanduseanothertypeofinheritance?Pickoneofthemethodsthatusesuberasawayforachildtoaccessitsparent.Addfunctionalitywheretheparentscankeeptrackofwhotheirchildrenare,perhapsusingapropertythatcontainsachildrenarray?
SummaryIn this chapter, you learned quite a few ways (patterns) of implementing inheritance, and thefollowingtablesummarizesthem.Thedifferenttypescanroughlybedividedintothefollowing:
PatternsthatworkwithconstructorsPatternsthatworkwithobjects
Youcanalsoclassifythepatternsbasedonwhetherthey:
UsetheprototypeCopypropertiesDoboth(copypropertiesoftheprototype):
# Name Example Classification Notes
1
Prototypechaining
(pseudo-classical)
Child.prototype=newParent();
WorkswithconstructorsUsestheprototypechain
ThedefaultmechanismTip-moveallproperties/methodsthataremeanttobereusedtotheprototype,andaddthenon-reusableas own properties
2Inheritonlytheprototype
Child.prototype=Parent.prototype;
WorkswithconstructorsCopiestheprototype(noprototypechain,asallsharethesameprototypeobject)
Moreefficient;nonewinstancesarecreatedjustforthesakeofinheritancePrototypechainlookupduringruntime;itisfast,sincethere'snochainDrawback:childrencanmodifyparents'functionality
3Temporaryconstructor
functionextend(Child,Parent){varF=function(){};F.prototype=Parent.prototype;Child.prototype = new F();Child.prototype.constructor=Child;Child.uber=Parent.prototype;}
Works withconstructorsUsestheprototypechain
Unlike#1,itonlyinheritspropertiesofthe prototype; own properties(createdwiththisinsidetheconstructor)arenotinherited.Providesconvenientaccesstotheparent(throughuber)
4Copyingtheprototype
properties
functionextend2(Child,Parent){varp=Parent.prototype;varc=Child.prototype;for(variinp){c[i]=p[i];}c.uber=p;}
WorkswithconstructorsCopiespropertiesUsestheprototypechain
AllpropertiesoftheparentprototypebecomepropertiesofthechildprototypeNoneedtocreateanewobjectonlyforinheritancepurposesShorterprototypechains
5
Copyallproperties
(shallowcopy)
functionextendCopy(p){varc={};for(variinp){c[i]=p[i];}c.uber=p;returnc;}
Works with objectsCopiesproperties
SimpleDoesn'tuseprototypes
6 DeepcopySameasthepreviousone,butrecurseintoobjects
WorkswithobjectsCopiesproperties
Sameas#5,butclonesobjectsandarrays
7Prototypalinheritance
functionobject(o){functionF(){}F.prototype=o;returnnewF();}
WorkswithobjectsUsestheprototypechain
Nopseudo-classes,objectsinheritfrom objectsLeveragesthebenefitsoftheprototype
8Extendandaugment
functionobjectPlus(o,stuff) {varn;functionF(){}F.prototype=o;n=newF();n.uber=o;for(variinstuff){n[i]=stuff[i];}returnn;}
WorkswithobjectsUsestheprototypechainCopiesproperties
Mixofprototypalinheritance(#7)andcopyingproperties(#5)Onefunctioncalltoinheritandextendatthesametime
9Multipleinheritance
functionmulti(){varn={},stuff,j=0,len=arguments.length;for(j=0;j<len;j++){stuff=arguments[j];for(variinstuff){n[i]=stuff[i];}}returnn;}
WorkswithobjectsCopiesproperties
Amixin-styleimplementationCopiesallthepropertiesofalltheparentobjectsintheorderofappearance
10Parasiticinheritance
functionparasite(victim){varthat=object(victim);that.more=1;returnthat;}
WorkswithobjectsUsestheprototypechain
Constructor-likefunctioncreatesobjectsCopiesanobject,andaugmentsandreturnsthecopy
11Borrowingconstructors
functionChild(){Parent.apply(this,arguments);}
Workswithconstructors
InheritsonlyownpropertiesCanbecombinedwith#1toinherittheprototypetooConvenientwaytodealwiththeissues whenachildinheritsapropertythatis anobject(andtherefore,passed by reference)
functionChild(){ Workswith Combinationof#11and#4
12Borrowaconstructorandcopytheprototype
Parent.apply(this,arguments);}extend2(Child,Parent);
constructorsUsestheprototypechainCopiesproperties
Allowsyoutoinheritbothownpropertiesandprototypepropertieswithoutcallingtheparentconstructortwice
Givensomanyoptions,youmustbewonderingwhichistherightone.Thatdependsonyourstyleand preferences, your project, task, and team. Are you more comfortable thinking in terms ofclasses?Thenpickoneofthemethodsthatworkwithconstructors.Areyougoingtoneedjustoneorafewinstancesofyourclass?Thenchooseanobject-basedpattern.
Arethesetheonlywaysofimplementinginheritance?No.Youcanchooseapatternfromtheprecedingtable,youcanmixthem,oryoucanthinkofyourown.Theimportantthingistounderstandandbecomfortablewithobjects,prototypes,andconstructors;therestisjustpurejoy.
Chapter 8. Classes and ModulesIn this chapter, we will explore some of the most interesting features introduced in ES6.JavaScriptisaprototype-basedlanguageandsupportsprototypicalinheritance.Inthepreviouschapter,wediscussedtheprototypepropertyofanobjectandhowprototypicalinheritanceworksinJavaScript.ES6bringsinclasses.Ifyouarecomingfromtraditionalobject-orientedlanguagessuchasJava,youwillimmediatelyrelatetothewell-knownconceptsofclasses.However,theyarenotthesameinJavaScript.ClassesinJavaScriptareasyntacticsugarovertheprototypicalinheritancewediscussedinthelastchapter.
Inthischapter,wewilltakeadetailedlookatES6classesandmodules-thesearewelcomechangestothiseditionofJavaScriptandmakeObjectOrientedProgramming(OOP)andinheritancesignificantlyeasier.
Ifyouarecomingfroma traditionalobject-orientedlanguage,prototypicalinheritancemayfeelabitoutofplaceforyou.ES6classesofferamoretraditionalsyntaxforyoutogetfamiliarizedwithprototypicalinheritanceinJavaScript.
Beforewetryanddelvedeeperintoclasses,letmeshowyouwhyyoushouldusetheES6classessyntaxovertheprototypicalinheritancesyntaxof ES5.
Inthissnippet,IamcreatingaclasshierarchyofPerson,Employee,andEngineer,prettystraightforward.First,wewillseetheES5prototypicalinheritance,whichiswrittenasfollows:
varPerson=function(firstname){if(!(thisinstanceofPerson)){thrownewError("Personisaconstructor");}
this.firstname = firstname;};
Person.prototype.giveBirth=function(){//...wegivebirthtotheperson};
varEmployee=function(firstname,lastname,job){if(!(thisinstanceofEmployee)){thrownewError("Employeeisaconstructor");}Person.call(this,firstname);this.job=job;};Employee.prototype=Object.create(Person.prototype);Employee.prototype.constructor=Employee;Employee.prototype.startJob=function(){//...Employeestartsjob};
varEngineer=function(firstname,lastname,job,department){if(!(thisinstanceofEngineer)){thrownewError("Engineerisaconstructor");}Employee.call(this,firstname,lastname,job);this.department=department;};Engineer.prototype=Object.create(Employee.prototype);Engineer.prototype.constructor=Engineer;Engineer.prototype.startWorking=function(){//...Engineerstartsworking};
Nowlet'slookattheequivalentcodeusingtheES6classessyntax:
classPerson{constructor(firstname){this.firsnamet=firstname;}giveBirth(){//...apersonisborn}}
classEmployeeextendsPerson{constructor(firstname,lastname,job){super(firstname);this.lastname=lastname;this.position=position;}
startJob(){//...Employeestartsjob
}}
classEngineerextendsEmployee{constructor(firstname,lastname,job,department){super(firstname,lastname,job);this.department=department;}
startWorking(){//...Engineerstartsworking}}
Ifyouobservethetwoprecedingcodesnippets,itwillbeobvioustoyouthatthesecondexampleisprettyneat.IfyoualreadyknowJavaorC#,youwillfeelrightathome.However,oneimportantthingtorememberisthatclassesdonotintroduceanynewobject-orientedinheritancemodeltothelanguage,butbringinamuchnicerwaytocreateobjectsandhandleinheritance.
Defining classesUnder the hood, classes are special functions. Just like you can define functions using functionexpressionsanddeclarations,youcandefineclassesaswell.Onewaytodefineclassesisusingclassdeclaration.
Youcanusetheclasskeywordandthenameoftheclass.ThissyntaxisverysimilartothatofJavaorC#:
classCar{constructor(model,year){this.model=model;this.year=year;}}console.log(typeofCar);//"function"
Toestablishthefactthatclassesareaspecialfunction,ifwegetthetypeoftheCarclass,wewillgetafunction.
Thereisanimportantdistinctionbetweenclassesandnormalfunctions.Whilenormalfunctionsarehoisted,classesarenot.Anormalfunctionisavailableimmediatelywhenyouenterascopeinwhichitisdeclared;thisiscalledhoisting,whichmeansthatanormalfunctioncanbedeclaredanywhereinthescope,anditwillbeavailable.However,classesarenothoisted;theyare available only after they are declared. For a normal function, you can say:
normalFunction();//usefirstfunctionnormalFunction(){}//declarelater
However,youcannotusetheclassbeforedeclaringit,forexample:
varford=newCar();//ReferenceErrorclassCar{}
Theotherwaytodefineaclassistouseaclassexpression.Aclassexpression,likeafunctionexpression,mayormaynothaveaname.
Thefollowingexampleshowsananonymousclassexpression:
constCar=class{constructor(model,year){this.model=model;this.year=year;}}
Ifyounametheclassexpression,thenameislocaltotheclass'sbodyand notavailableoutside:
constNamedCar=classCar{constructor(model,year){this.model=model;this.year=year;}getName(){returnCar.name;}}constford=newNamedCar();console.log(ford.getName());//Carconsole.log(ford.name);//ReferenceError:nameisnotdefined
Asyoucansee,here,wewillgiveanametotheCarclass.Thisnameisavailablewithinthebodyoftheclass,butwhenwetrytoaccessitoutsidetheclass,wegetareferenceerror.
Youcannotusecommaswhileseparatingmembersofaclass.Semicolonsarevalidthough.ThisisfunnyasES6ignoressemicolonsandthereisaragingdebateaboutusingsemicolonsinES6.Considerthefollowingcodesnippetasanexample:
classNoCommas{method1(){}
member1;//Thisisignoredandcanbeusedtoseparateclassmembersmember2,//Thisisanerrormethod2(){}}
Oncedefined,wecanuseclassesviaanewkeywordandnotafunctioncall;here'stheexample:
class Car {constructor(model,year){this.model=model;this.year=year;}}constfiesta=newCar('Fiesta','2010');
Constructor
Wehaveusedtheconstructorfunctionintheexamplessofar.Aconstructorisaspecialmethodusedtocreateandinitializeanobjectcreatedwiththeclass.Youcanhaveonlyoneconstructorinaclass.Constructorsareabitdifferentfromthenormalconstructorfunctions.Unlikenormalconstructors,aclassconstructorcancallitsparentclassconstructorviasuper().Wewilldiscussthisindetailwhenwelookatinheritance.
Prototypemethods
Prototypemethodsareprototypepropertiesoftheclass,andtheyareinheritedbyinstancesoftheclass.
Prototypemethodscanalsohavegetterandsettermethods.ThesyntaxofgettersandsettersisthesameasES5:
classCar{constructor(model,year){this.model=model;
this.year = year;}getmodel(){returnthis.model}calculateCurrentValue(){return"7000"}}constfiesta=newCar('Fiesta','2010')console.log(fiesta.model)
Similarly,computedpropertiesarealsosupported.Youcandefinethenameofthemethodusingtheexpression.Theexpressionneedstobeputinsidesquarebrackets.Wediscussedthisshorthandsyntaxinearlierchapters.Thefollowingareallequivalent:
classCarOne{driveCar(){}}classCarTwo{['drive'+'Car'](){}}
const methodName = 'driveCar';classCarThree{[methodName](){}}
Staticmethods
Staticmethodsareassociatedwiththeclassandnotwithaninstanceofthatclass(object).Inotherwords,youcanonlyreachastaticmethodusingthenameoftheclass.Staticmethodsareinvokedwithoutinstantiatingtheclassandtheycannotbecalledonaninstanceofaclass.Staticmethodsarepopularincreatingutilityorhelpermethods.Considerthefollowingpieceofcode:
classLogger{staticlog(level,message){console.log(`${level}:${message}`)}}//InvokestaticmethodsontheClassLogger.log("ERROR","Theendisnear")//"ERROR:Theendisnear"
//Notoninstanceconstlogger=newLogger("ERROR")logger.log("Theendisnear")//logger.logisnotafunction
Staticproperties
Youmayask-well,wehavestaticmethods,whataboutstaticproperties?InthehurryofgettingES6ready,theydidnotaddstaticproperties.Theywillbeaddedinfutureiterationsofthelanguage.
Generatormethods
Wediscussedhugelyusefulgeneratorfunctionsafewchaptersback.Youcanaddgeneratorfunctionsaspartofclass,andtheyarecalledgeneratormethods.AgeneratormethodisusefulbecauseyoucandefinetheirkeyasSymbol.iterator.Thefollowingexampleshowshowgeneratormethodscanbedefinedinsideaclass:
classiterableArg{constructor(...args){this.args=args;}*[Symbol.iterator](){for(constargofthis.args){yieldarg;}
}}
for(constxofnewiterableArg('ES6','wins')){console.log(x);}
//ES6//wins
SubclassingSo far, we discussed how to declare classes and the types of members classes can support. Amajoruseofaclassistoserveasatemplatetocreateothersubclasses.Whenyoucreateachildclassfromaclass,youderivepropertiesoftheparentclassandextendtheparentclassbyaddingmorefeaturesofitsown.
Let'slookatthefollowingdefactoexampleofinheritance:
class Animal {constructor(name){this.name=name;}speak(){console.log(this.name+'genericnoise');}}classCatextendsAnimal{speak(){console.log(this.name+'saysMeow.');}}varc=newCat('Grace');c.speak();//"GracesaysMeow."
Here,AnimalisthebaseclassandtheCatclassisderivedfromtheclassAnimal.Theextendclauseallowsyoutocreateasubclassofanexistingclass.Thisexampledemonstratesthesyntaxofsubclassing.Let'senhancethisexampleabitmorebywritingthefollowingcode:
classAnimal{constructor(name){this.name=name;}speak(){console.log(this.name+'genericnoise');}}classCatextendsAnimal{speak(){console.log(this.name+'saysMeow.');}}classLionextendsCat{speak(){super.speak();console.log(this.name+'Roars....');}}varl=newLion('Lenny');l.speak();
//"LennysaysMeow."//"LennyRoar...."
Here,weareusingthesuperkeywordtocallfunctionsfromtheparentclass.Thefollowingarethethreewaysinwhichthesuperkeywordcanbeused:
You can use super (<params>) as a function call to invoke the constructor of the parentclassYoucanusesuper.<parentClassMethod>toaccesstheparentclassmethodsYoucanusesuper.<parentClassProp>toaccesstheparentclassproperties
Inthederiveclassconstructor,youmustcallthesuper()methodbeforeyoucanuse,thiskeyword;forexample,thefollowingpieceofcodewillfail:
classBase{}classDeriveextendsBase{constructor(name){this.name=name;//'this'isnotallowedbeforesuper()}}
Youcan'timplicitlyleaveaderivedconstructorwithasuper()methodasanerror:
classBase{}classDeriveextendsBase{constructor(){//missingsuper()callinconstructor}}
Ifyoudon'tprovideaconstructorforthebaseclass,thefollowingconstructorisused:
constructor(){}
Forthederivedclasses,thedefaultconstructorisasfollows:
constructor(...args){super(...args);
}
Mixins
JavaScriptsupportsonlysingleinheritance.Atmost,aclasscanhaveonesuperclass.Thisislimitingwhenyouwanttocreateclasshierarchiesbutalsowanttoinherittoolmethodsfromdifferentsources.
Let'ssaywehaveascenariowherewehaveaPersonclass,andwecreateasubclass,Employee:
classPerson{}classEmployeeextendsPerson{}
Wealsowanttoinheritfunctionsfromtwoutility classes,BackgroundCheck-thisclassdoesemployeebackgroundchecks-andOnboard-thisclasshandlesemployeeonboardingprocesses,suchasprintingbadgesandsoon:
classBackgroundCheck{check(){}}classOnboard{printBadge(){}}
BothBackgroundCheckandOnboardclassesaretemplates,andtheirfunctionalitywillbeusedmultipletimes.Suchtemplates(abstractsubclasses)arecalledmixins.
As multiple inheritance is not possible in JavaScript, we will employ a different technique toachievethis.ApopularwayofimplementingmixinsinES6istowriteafunctionwithasuperclassasaninputandasubclassextendingthatsuperclassastheoutput,forexample:
classPerson{}constBackgroundCheck=Tools=>classextendsTools{check(){}};constOnboard=Tools=>classextendsTools{printBadge(){}};classEmployeeextendsBackgroundCheck(Onboard(Person)){}
ThisessentiallymeansthatEmployeeisasubclassofBackgroundCheck,whichinturnisasubclassofOnboard,whichinturnisasubclassofPerson.
ModulesJavaScript modules are not new. In fact, there were have been libraries that support modules forsometimenow.ES6,however,offersbuilt-inmodules.Traditionally,JavaScript'smajorusewasonbrowsers,wheremostoftheJavaScriptcodewaseitherembeddedorsmallenoughtomanagewithoutmuchtrouble.Thingshavechanged.JavaScriptprojectsarenowonamassivescale.Withoutanefficientsystemofspreadingthecode intofilesanddirectories,managingcodebecomesanightmare.
ES6modulesarefiles.Onemoduleperfileandonefilepermodule.Thereisnomodulekeyword.Whatevercodeyouwriteinthemodulefileislocaltothemoduleunlessyouexportit.Youmayhaveabunchoffunctionsinamodule,andyouwanttoexportonlyafewofthem.Youcanexportmodulefunctionalityinacoupleofways.
Thefirstwayistousetheexportkeyword.Youcanexportanytop-level function,class,var,let,orconst.
The following example shows a module inside server.js where we export a function, aclass, and a const. We don't export the processConfig() function, and any file importing thismodule won't be able to access the unexported function:
//----------------server.js---------------------exportconstport=8080;exportfunctionstartServer(){//...startserver}exportclassConfig{//...}functionprocessConfig(){//...}
Anycodethathasaccesstoserver.jscanimporttheexportedfunctionality:
//--------------app.js----------------------------import{Config,startServer}from'server'startServer(port);
Inthiscase,anotherJavaScriptfileisimportingConfigandstartServerfromtheservermodule(withthecorrespondingJavaScriptfileserver.js,wedropthefileextension).
Youcanalsoimporteverythingthatwasexportedfromthemodule:
import*from'server'
Ifyouhaveonlyonethingtoexport,youcanusethedefaultexportsyntax. Considerthefollowingpieceofcodeasanexample:
//----------------server.js---------------------exportdefaultclass{//...}//--------------app.js----------------------------importServerfrom'server';consts=newServer();
Inthisexample,wewillkeeptheclassanonymousaswecanusethemodulenameitselfasthereferenceoutside.
BeforeES6modules,externallibrariessupportedseveralapproachestomodules.Theyestablishedfairlygoodguidelines/stylesforES6tofollow.ThefollowingstyleisfollowedbyES6:
Modulesaresingletons.Amoduleisimportedonlyonce,evenifyoutrytoimportitseveraltimesinyourcode.Variable,functions, andothertypeofdeclarationsarelocaltothemodule.Onlydeclarationsmarkedwithexportareavailableoutsidethemoduleforimport.Modulescanimportfromothermodules.Thefollowingarethethreeoptionsforreferringtoothermodules:
Youcanuserelativepaths("../lib/server");thesepathsareresolvedrelativelytothefileimportingthemodule.Forexample,ifyouareimportingthemodulefrom<project_path>/src/app.js,andthemodulefileislocatedat<project_path>/lib/server.js,youwillneedtoprovidea pathrelativetotheapp.js-../lib/serverinthiscase.Absolutepathscanalsopointtothemodulefiledirectly.Youcandropthefile.jsextensionwhileimportingthemodule.
BeforewegointomoredetailsoftheES6modulesystem,weneedtounderstandhowES5supportedthemviaexternallibraries.ES5hastwonon-compatiblemodulesystems,whichareasfollows:
CommonJS:ThisisthedominantstandardasNode.jsadopteditAMD(AsynchronousModuleDefinition):ThisisslightlymorecomplicatedthanCommonJSanddesignedforasynchronousmoduleloading,andtargetedtowardbrowsers
ES6moduleswereaimedtobeeasytouseforengineerscomingfromanyofthesesystems.
Exportlists
Insteadoftaggingeachexportedfunctionorclassfromyourmodulewiththeexportkeyword,youcanwriteasinglelistofallthethingsyouwanttoexportfromthemodule,whichareasfollows:
export{port,startServer,Config};constport=8080;functionstartServer(){//...startserver
}classConfig{//...}functionprocessConfig(){//...}
Thefirstlineofthemoduleisthelistofexports.Youcanhavemultipleexportlistsinthemodulefileandthelistcanappearanywhereinthefile.Youcanalsohaveamixofexportlistandexportdeclarationsinthesamemodulefile,butyoucanexportonenameonlyonce.
Inalargeproject,therearecaseswhenyouencounternameconflicts.Supposeyouimporttwomodules,andbothofthemexportafunctionwiththesamename.Insuchcases,youcanrenametheimportsasfollows:
import{truncasStringLib}from"../lib/string.js"import{truncasMathLib}from"../lib/math.js"
Here, both the imported modules exported a name, trunc, and hence created a conflict of names.We can alias them to resolve this conflict.
Youcandotherenaming whileexportingaswell,whichisasfollows:
functionv(){}functionv2(){}export{vasfunctionV(),v2asfunctionV2(),v2asfunctionLatest()}
IfyouarealreadyusingES5modulesystems,ES6modulesmaylookredundant.However,itwasveryimportantforthelanguagetohavesupportforsuchanimportantfeature.ES6modulesyntaxisalsostandardizedandmorecompactthantheotheralternatives.
SummaryIn this chapter, we focused on understanding ES6 classes. ES6 classes give formal support to thecommonJavaScriptpatternofsimulatingclass-likeinheritancehierarchiesusingfunctionsandprototypes.Theyaresyntacticsugaringoverprototype-basedOO,offeringaconvenientdeclarativeformforclasspatternswhichencourageinteroperability.ES6 classesofferamuchnicer,cleaner,andclearersyntaxforcreatingtheseobjectsanddealingwithinheritance.ES6classesprovidesupportforconstructors,instanceandstaticmethods,(prototype-based)inheritance,andsupercalls.
Sofar,JavaScriptlackedoneofthemostbasicfeatures-modules.BeforeES6,wewrotemodulesusingeitherCommonJSorAMD.ES6bringsmodulesintoJavaScriptofficially.Inthischapter,wetookadetailedlookathowmodulesareusedinES6.
ThenextchapterfocusesonanotherinterestingadditiontoES6-proxiesandpromises.
Chapter 9. Promises and ProxiesThis chapter introduces the important concept of asynchronous programming and howJavaScriptisanideallanguagetoutilizeit.Theothertopicthatwewillcoverinthischapterismetaprogrammingwithproxies.ThesetwoconceptsareintroducedinES6.
Inthischapter,ourprimaryfocusistounderstandasynchronousprogramming,beforewejumpintothelanguage-specificconstructs,let'sspendtimeinunderstandingtheconceptfirst.
Thefirstmodel-thesynchronousmodel-iswhereitallbegan.Thisisthesimplestmodelofprogramming.Eachtaskisexecutedoneatatime,andonlyafterthefirsttaskcompletesexecutioncan,thenexttaskstart.Whenyouprograminthismodel,weexpectthatalltasksbeforethecurrenttaskarecompleteandthereisnoerror.Takealookatthefollowingfigure:
Thesinglethreadedasynchronousmodelisafamiliarmodelweallknow.However,thismodelcanbewastefulandoptimized.Foranynontrivialprogramscomposedofseveraldifferenttasks,thismodelcanbeslow.Considerthefollowinghypotheticalscenarioasanexample:
varresult=database.query("SELECT*FROMtable");console.log("Afterreadingfromthedatabase");
Withthesynchronousmodelinmind,twotasksareexecutedoneaftertheother.Thismeansthatthesecondstatementwillonlybeexecutedoncethefirsthascompletedexecution.Assumingthe
firststatementisacostlyoneandtakes10seconds(itisnormaltotakeevenmoretimetoreadfromaremotedatabase),thesecondstatementwillbeblocked.
This is a serious problemwhen you need to write high - performance and scalable systems. Thereisanotherproblemthatmanifestswhenyouarewritingprogramswhereyouneedtowriteinterfacesforhumaninteractionslikewedoonwebsitesthatrunonabrowser.Whileyouareperformingataskthatmaytakesometime,youcannotblocktheuser.Theymaybeenteringsomethinginaninputfieldwhilethecostlytaskisrunning;itwouldbeaterribleexperienceifweblockuserinputwhilewearebusydoingacostlyoperation.Insuchscenarios,thecostlytasksneedtoberuninthebackgroundwhilewecanhappilytakeinputfromtheuser.
Tosolvethis,onesolutionistospliteachtaskintoitsownthreadofcontrol.Thisiscalledthemulti-threadedorthreadedmodel.Considerthefollowingfigure:
Thedifferenceishowthetasksaresplit.Inthethreadedmodel,eachtaskisperformedinitsownthreadofcontrol.Usually,threadsaremanagedbytheoperatingsystemandcanberuninparallelon different CPU cores or on a single core with appropriate thread scheduling done by the CPU.WithmodernCPUs,thethreadedmodelcanbeextremelyoptimalinperformance.Severallanguagessupportthispopularmodel.Althoughapopularmodel,thethreadedmodelcanbecomplextoimplementinpractice.Thethreadsneedtocommunicateandcoordinatewitheachother.Inter-threadcommunicationcangettrickyveryquickly.Therearevariationsofthethreadedmodelwherethestateisimmutable.Insuchcases,themodelbecomessimpleraseachthreadisresponsibleforimmutablestateandthereisnoneedtomanagestatebetweenthreads.
Asynchronous programming modelThe third model is what interests us the most. In this model, tasks are interleaved in a singlethreadofcontrol.Considerthefollowingfigure:
Theasynchronousmodelissimplerbecauseyouhaveonlyonethread.Whenyouareexecutingonetask,youaresurethatonlythattaskisbeingexecuted.Thismodeldoesn'trequirecomplexmechanismforinter-threadcoordinationand,hence,ismorepredictable.Thereisonemoredifferencebetweenthethreadedandtheasynchronousmodels;inthethreadedmodel,youdon'thaveawaytocontrolthethreadexecutionasthethreadschedulingismostlydonebytheoperatingsystem.However,intheasynchronousmodel,thereisnosuchchallenge.
In which scenarios can the asynchronous model outperform the synchronous model? If we aresimplysplittingtasksintosmallerchunks,intuitively,eventhesmallerchunkswilltakequiteanamountoftimewhenyouaddthemupintheend.
Thereisasignificantfactorwehavenotyetconsidered.Whenyouexecuteatask,youwillendupwaitingonsomething-adiskread,adatabasequery,oranetworkcall;theseareblockingoperations.Whenyouenterablockedmode,yourtasksimplywaitsinthesynchronousmodel.Takealookatthefollowingfigure:
Intheprecedingdiagram,theblackblocksarewhereataskiswaitingonsomething.Whatarethetypicaloperationsthatcancausesuchablock?A taskisperformedinaCPUandRAM.AtypicalCPUandRAMcanhandledatatransferordersofmagnitudefasterthanatypicaldiscreadoranetworkcall.
Tip
Pleaserefertoacomparison(https://gist.github.com/jboner/2841832)oflatenciesbetweenCPU,internalmemory,anddiscs.
Whenyourtaskswaiton anI/O(Input/Output)fromsuchsources,thelatencyisunpredictable.ForasynchronousprogramthatdoesalotofI/O,thisisarecipeforbadperformance.
The most important difference between the synchronous and asynchronous models is the way theyhandleblockingoperations.Intheasynchronousmodel,aprogram,whenfacedwithataskthatencountersablock,executesanothertaskwithoutwaitingfortheblockingoperationtofinish.Inaprogramwheretherearepotentialblocks,anasynchronousprogramoutperformsanequivalentsynchronousprogrambecauselesstimeisspentonwaiting.Aslightlyinaccuratevisualizationofsuchamodelwouldbeasseeninthefollowingfigure:
Withthisconceptualbackgroundoftheasynchronousmodelwithus,wecanlookatlanguage-specificconstructstosupportthismodel.
JavaScript call stackIn JavaScript, function calls form a stack of frames. Consider the following example:
functionc(z2){console.log(newError().stack);}functionb(z1){c(z1+1);}functiona(z){b(z+1);}a(1);
//atc(evalat<anonymous>)//atb(evalat<anonymous>)//ata(evalat<anonymous>)
Whenwecallfunctiona(),thefirstframeinthestackiscreatedwithargumentstothefunctionandalllocalvariablesinthea()function.Whenfunctiona()callsfunctionb(),asecondframeiscreatedandpushedtothetopofthestack.Thisgoesonforallfunctioncalls.Whenthec()function returns, the top frame from the stack is popped out, leaving functions b() and a();this goes on until the entire stack is empty. This is necessary to maintain because once the functionfinishesexecution,JavaScriptwillneedtoknowwheretoreturn.
Messagequeue
TheJavaScriptruntimecontainsamessagequeue.Thisqueuecontainsthelistofmessagestobeprocessed.ThesemessagesarequeuedinresponsetoeventssuchasclickoranHTTPresponsereceived.Eachmessageisassociatedwithacallbackfunction.
Eventloop
Abrowsertabrunsinasinglethread-aneventloop.Thisloopcontinuouslypicksmessagesfromthemessagequeueandexecutesthecallbacksassociatedwiththem.Theeventloopsimplykeepspickingtasksfromthemessagequeueswhileotherprocessesaddtaskstothemessagequeue.Otherprocessessuchastimersandeventhandlersruninparallelandkeepaddingtaskstothequeue.
Timers
ThesetTimeout()methodcreatesatimerandwaitsuntilitfires.Whenthetimerisexecuted,ataskisaddedtothemessagequeue.ThesetTimeOut()methodtakestwoarguments:acallback,andthedurationinmilliseconds.Aftertheduration,thecallbackisaddedtothemessagequeue.Oncethecallbackisaddedtothemessagequeue,theeventloopwilleventuallypickitupandexecuteit.Thereis,however,noguaranteewhenthecallbackwillbepickedupbytheeventloop.
Runtocompletion
Whentheeventlooppicksupamessagefromthequeue,theassociatedcallbackisruntocompletion.Thismeansthatamessageisprocessedcompletelybeforethenextmessagefromthequeueisprocessed.Thispropertygivestheasynchronousmodelasenseofpredictability.Asthereisnointerventiontopreemptanyofthemessagesinbetweenexecution,thismodelismuchsimplerthanothermodels,whereanyunitofexecutioncanbehaltedinbetween.However,oncethemessageispickedup,eveniftheexecutiontakestoolong,anyotherinteractiononthebrowserisblocked.
Events
Youcanregistereventhandlersforanobjectandreceiveresultsofamethodasynchronously.ThefollowingexampleshowshowwecansetupeventhandlersfortheXMLHttpRequestAPI:
varxhr=newXMLHttpRequest();xhr.open('GET','http://babeljs.io',true);xhr.onload=function(e){if(this.status==200){console.log("Works");}};xhr.send();
Intheprecedingsnippet, wearecreatingtheobjectoftheXMLHttpRequestclass.Oncetherequestobjectiscreated,wewillregistereventhandlersforit.Eventhandlers,suchasonload(),aretriggeredasynchronouslywhentheresponseisreceivedfromtheopen()method.
The send() method doesn't actually initiate the request, it adds the request to the message queuefor the event loop to pick it up and execute necessary callbacks associated with it.
Callbacks
TheNode.jsapplicationpopularizedthisstyleof receivingasynchronousdata.Acallbackisafunctionpassedasthelastargumenttotheasynchronousfunctioncall.
Toillustratetheusage,let'susethefollowingexampleofreadingafileinNode.js:
fs.readFile('/etc/passwd',(err,data)=>{if(err)throwerr;console.log(data);});
Don'tworryaboutafewdetailshere.Weareusingthefilesystemmoduleasanfsalias.ThismodulehasareadFilemethodtoreadafileasynchronously.Wewillpassthefilepathandfilenameasthefirstargumentandacallbackfunctionasthelastargumentofthefunction.Weareusingananonymousfunctionasthecallbackintheexample.
Thecallbackfunctionhastwoarguments-erroranddata.WhenthereadFile()methodissuccessful,thecallbackfunctionreceivesdata,andifitfails,theerrorargumentwillhavetheerrordetails.
Wecanalsouseaslightlyfunctionalstyletowritethesamecallback.Considerthefollowingexample:
fs.readFile('/etc/passwd',//successfunction(data){console.log(data)},//errorfunction(error){console.log(error)
});
Thisstyleofpassingcallbacksisalsocalledcontinuous-passingstyle(CPS);thenextstepofexecutionorcontinuationispassedasaparameter.ThefollowingexamplefurtherillustratestheCPSstyleofcallbacks:
console.log("1");cps("2",functioncps_step2(val2){console.log(val2);cps("3",functioncos_step3(val3){console.log(val3);})console.log("4");});console.log("5");//15243
functioncps(val,callback){setTimeout(function(){callback(val);},0);}
Wewillprovidethecontinuation(thenextcallback)toeachstep.Thisnestedcallbackstylealso
causesaproblemsometimesreferredtoascallbackhell.
CallbacksandtheCPSintroducearadicallydifferentstyleofprogramming.Althoughitiseasierto understand callbacks compared to other constructs, callbacks can create slightly difficult tounderstandcode.
PromisesES6 introduces promises as an alternate to callbacks. Like callbacks, promises are used toretrievetheresultsofanasynchronousfunctioncall.Usingpromisesiseasierthancallbacksandproducesmorereadablecode.However,toimplementpromisesforyourasynchronousfunctionsrequiresmorework.
Apromiseobjectrepresentsavaluethatmaybeavailablenoworinthefuture,orpossiblynever.Asthenamesuggests,apromisemaybefulfilledorrejected.Apromiseactsasaplaceholderfortheeventualresult.
Apromisehasthreemutuallyexclusivestates,whichareasfollows:
1. Apromiseispendingbeforetheresultisready;thisistheinitialstate.2. Apromiseisfulfilledwhentheresultisready.3. Onanerror,apromiseisrejected.
Whenapendingpromiseiseitherfulfilledorrejected,associatedcallbacks/handlersthatarequeuedupbythethen()methodofthepromiseareexecuted.
ThepurposeofpromisesistoprovideabettersyntaxfortheCPScallbacks.ThetypicalCPSstyleasynchronousfunctionslikethefollowingone:
asyncFunction(arg,result=>{//...})
Theprecedingcodecanbewrittenabitdifferentlywithpromises,asshowninthefollowinglinesofcode:
asyncFunction(arg).then(result=>{//...});
Theasynchronousfunctionnowreturnsapromise,whichistheplaceholderforaneventualresult.Callbacksregisteredwiththethen()methodarenotifiedwhentheresultisready.
Youcanchainthethen()method.Whenthethen()methodseesthatthecallbacktriggeredanotherasynchronousactionthatreturnsapromise,itreturnsthatpromise.Takealookatthefollowingexample:
asyncFunction(arg).then(resultA=>{//...returnasyncFunctionB(argB);
}).then(resultB=>{//...})
Let'sseearealexampleofhowwecanusepromises.WesawatypicalexampleofasynchronousfilereadsinNode.js;nowlet'sseewhatthatexamplewilllooklikewhenusedwithpromises.Tojogourmemories,wewrotesomethinglikethefollowing:
fs.readFile('text.json',function(error,text){if(error){console.error('Errorwhilereadingtextfile');}else{try{//...}catch(e){console.error('Invalidcontent');}
}});
Weseecallbacksascontinuationhere;nowlet'sseehowthesamefunctioncanbewrittenusingpromises:
readFileWithPromises('text.json').then(text=>{//...processtext}).catch(error=>{console.error('Errorwhilereadingtextfile');})
Nowthecallbacksareinvokedviatheresultandmethodsthen()andcatch().Theerrorhandlingismuchcleanerbecausewearenotwritingtheif...elseandtry...catchconstructsanymore.
Creatingpromises
Wesawhowwecanconsumepromises.Now,let'slookathowwecanproducethem.
Asaproducer,youcancreateaPromiseobjectandsendaresultviathePromise.Theconstructlookslikethefollowingcodesnippet:
constp=newPromise(function(resolve,reject){//(1)if(){resolve(value);//success}else{reject(reason);//failure}});
TheparametertoPromiseisanexecutorfunction.Theexecutorhandlestwostatesofthepromise,whichareasfollows:
Resolving: If the result was generated successfully, the executor sends the results back viatheresolve()method.ThismethodusuallyfulfillsthePromiseobject.Rejecting:Ifanerrorhappened,theexecutornotifiestheconsumerviathereject()method.Ifanexceptionoccurs,itisnotified viathereject()methodaswell.
Asaconsumer,youarenotifiedofeitherfulfillmentofpromiseorrejectionofpromiseviathethen()andcatch()methods.Considerthefollowingpieceofcodeasanexample:
promise.then(result=>{/*promisefulfilled*/}).catch(error=>{/*promiserejected*/});
Nowthatwehavesomebackgroundonhowtoproducepromises,let'srewriteourearlierexampleoftheasynchronousfile'sreadmethodtoproducepromises.WewilluseNode.js'sfilesystemmoduleandthereadFile()methodaswedidlasttime.Ifyoudon'tunderstandanyNode.jsspecificconstructinthefollowingsnippet,pleasedon'tworry.Considerthefollowingcode:
import{readFile}from'fs';functionreadFileWithPromises(filename){returnnewPromise(
function (resolve, reject) {readFile(filename,(error,data)=>{if(error){reject(error);}else{resolve(data);
}});});}
Intheprecedingsnippet, wearecreatinganewPromiseobjectandreturningittotheconsumer.Aswesawearlier,theparametertothePromiseobjectistheexecutorfunctionandtheexecutorfunctiontakescareoftwostatesofPromise-fulfilledandrejected.Theexecutorfunctiontakesintwoarguments,resolveandreject.ThesearethefunctionsthatnotifythestateofthePromiseobjecttotheconsumer.
Insidetheexecutorfunction,wewillcalltheactualfunction-thereadFile()method;ifthisfunctionissuccessful,wewillreturntheresultusingtheresolve()methodandifthereisanerror,wewillnotifytheconsumerusingthereject()method.
Ifanerrorhappensinoneofthethen()reactions,theyarecaughtinthesubsequentcatch()block.Takealookatthe followingcode:
readFileWithPromises('file.txt').then(result=>{'somethingcausesanexception'}).catch(error=>{'Somethingwentwrong'});
Inthiscase,thethen()reactioncausesanexceptionorerror,andthesubsequentcatch()blockcanhandlethis.
Similarly,anexceptionthrowninsideathen()orcatch()handlerispassedtothenexterrorhandler.Considerthefollowingcodesnippet:
readFileWithPromises('file.txt').then(thrownewError())
.catch(error=> {'Something went wrong'});
Promise.all()
Oneinterestingusecaseistocreateaniterableoverpromises.Let'sassumethatyouhavealistofURLsyouwanttovisitandparsetheresults.YoucancreatepromisesforeachofthefetchURLcallsandusethemindividually,oryoucancreateaniteratorwithalltheURLsandusethepromiseinonego.ThePromise.all()methodtakestheiterableofpromisesasanargument.Whenallofthepromisesarefulfilled,anarrayisfilledwiththeirresults.Considerthefollowingcodeasanexample:
Promise.all([f1(),
f2()]).then(([r1,r2])=>{//})
.catch(err=>{//..});
Metaprogrammingandproxies
Metaprogrammingreferstoamethodofprogrammingwheretheprogramisawareofitsstructureandcanmanipulateitself.Manylanguageshavesupportformetaprogrammingintheformofmacros.MacrosareimportantconstructsinfunctionallanguagessuchasLISP(Locator/IDSeparationProtocol).InlanguagessuchasJavaandC#,reflectionisaformofmetaprogrammingbecauseaprogramcanexamineinformationaboutitselfusingreflection.
InJavaScript,youcansaythatmethodsofobjectallowyoutoexaminethestructureandhence,theyoffermetaprogramming.Therearethreetypesofmetaprogrammingparadigms(TheArtoftheMetaobjectProtocol,Kiczalesetal,https://mitpress.mit.edu/books/art-metaobject-protocol):
Introspection:Thisgivesaread-onlyaccesstotheinternalsofaprogramSelf-modification:ThismakesstructuralchangespossibletotheprogramIntercession:Thischangeslanguagesemantics
TheObject.keys()methodisanexampleofintrospection.Inthefollowingexample,theprogramisexaminingitsownstructure:
constintrospection={intro(){console.log("IthinkthereforeIam");}}for(constkeyofObject.keys(introspection)){console.log(key);//intro}
Self-modificationisalsopossibleinJavaScriptbymutatingthepropertiesofanobject.
However,intercession,ortheabilitytochangelanguagesemantics,issomethingnotavailableinJavaScripttillES6.Proxiesareintroducedtoopenupthispossibility.
Proxy
Youcanuseaproxytodeterminethebehaviorofanobject,whichiscalledthetarget,wheneveritspropertiesareaccessed.Aproxyisusedtodefinecustombehaviorforbasicoperationsonanobject,suchaslookingupaproperty,functioninvocation,andassignment.
Aproxyneedstwoparameters,whichareasfollows:
Handler:Foreachoperationyouwanttocustomize,youneedahandlermethod.Thismethodinterceptstheoperationsandissometimescalledatrap.Target:Whenthehandlerdoesnotintercepttheoperation,thetargetisusedasafallback.
Let'stakealookatthefollowingexampletounderstandthisconceptbetter:
varhandler={get:function(target,name){returnnameintarget?target[name]:42;}}varp=newProxy({},handler);p.a=100;p.b=undefined;console.log(p.a,p.b);//100,undefinedconsole.log('c'inp,p.c);//false,42
Inthisexample,wearetrappingtheoperationofgettingapropertyfromtheobject.Wereturn42asadefaultpropertyvalueifthepropertydoesnotexist.Weareusingthe gethandlertotrapthisoperation.
Youcanuseproxiestovalidatevaluesbeforesettingthemonanobject.Forthis,wecantrapthesethandlerasfollows:
letageValidator={set:function(obj,prop,value){if(prop==='age'){if(!Number.isInteger(value)){thrownewTypeError('Theageisnotannumber');}if(value>100){thrownewRangeError('Youcantbeolderthan100');}}//Ifnoerror-juststorethevalueinthepropertyobj[prop]=value;}};letp=newProxy({},ageValidator);p.age=100;
console.log(p.age);//100p.age='Two';//Exceptionp.age=300;//Exception
Intheprecedingexample,wearetrappingthesethandler.Whenwesetapropertyoftheobject,wearetrappingthatoperationandintroducingvalidationofvalues.Ifthevalueisvalid,wewillsettheproperty.
Functiontraps
Therearetwooperationsthatcanbetrappedifthetargetisafunction:applyandconstruct.
Tointerceptfunctioncalls,youwillneedtotrapthegetandapplyoperations.Firstgetthefunctionandthenapplytocallthefunction.So,yougetthefunctionandreturnthefunction.
Let'sconsiderthefollowingexampletounderstandhowmethodinterceptionworks:
varcar={name:"Ford",method_1:function(text){console.log("Method_1calledwith"+text);}}varmethodInterceptorProxy=newProxy(car,{//targetistheobjectbeingproxied,receiveristheproxyget:function(target,propKey,receiver){//Ionlywanttointerceptmethodcalls,notpropertyaccessvarpropValue=target[propKey];if(typeofpropValue!="function"){returnpropValue;}else{
return function(){console.log("interceptingcallto"+propKey+"incar"+target.name);//targetistheobjectbeingproxiedreturnpropValue.apply(target,arguments);}}}});methodInterceptorProxy.method_1("Mercedes");//"interceptingcalltomethod_1incarFord"//"Method_1calledwithMercedes"
Intheprecedingexample,wearetrappingthegetoperation.Ifthetypeofthepropertybeinggetisafunction,wewilluseapplytoinvokethatfunction.Ifyouseetheoutput,wearegettingtwoconsole.logs;thefirstisfromtheproxywherewetrappedthegetoperationandthesecondisfromtheactualmethodcall.
Metaprogrammingisaninterestingconstructtouse.However,anykindofintrospectionorreflectioncomesatthecostofperformance.Careshouldbetakenwhileusingproxiesastheycanbeslow.
SummaryIn this chapter, we looked at two important concepts. ES6 proxies are useful meta programmingconstructsusedtodefinecustombehaviorforfundamentaloperations(forexample,propertylookup,assignment,enumeration,functioninvocation,andsoon).Welookedathowtousehandlers,traps,andproxytargetstointerceptandmodifythedefaultbehaviorofoperations.ThisgivesusverypowerfulmetaprogrammingcapabilitiesearlierlackinginJavaScript.
TheotherimportantconstructwediscussedinthischapterwasES6promises.Promisesareimportantbecausetheymakeasynchronousprogrammingconstructseasiertoworkwith.Apromiseactsasaproxyforavaluenotnecessarilyknownwhenthepromiseiscreated.Thisletsasynchronousmethodsreturnvalueslikesynchronousmethods-insteadofthefinalvalue,theasynchronousmethodreturnsapromiseforthevalueatsomepointinthefuture.
ThesearetwoverypowerfulconstructsinES6thatgreatlyenhancethelanguage'scorecapabilities.
Inthenextchapter,wewilllookatthefascinatingpossibilitiesaroundbrowsersandDOMmanipulationusingJavaScript.
Chapter 10. The Browser EnvironmentYou know that JavaScript programs need a host environment. Most of what you learned so far inthisbookwasrelatedtocoreECMAScript/JavaScriptandcanbeusedinmanydifferenthostenvironments.Now,let'sshiftthefocustothebrowserasthisisthemostpopularandnaturalhostenvironmentforJavaScriptprograms.Inthischapter,youwilllearnthefollowingtopics:
TheBrowserObjectModel(BOM)TheDocumentObjectModel(DOM)BrowsereventsTheXMLHttpRequestobject
Including JavaScript in an HTML pageTo include JavaScript in an HTMLpage, you will need to use the <script> tag as follows:
<!DOCTYPE><html><head><title>JStest</title><scriptsrc="somefile.js"></script></head><body><script>vara=1;a++;
</script></body></html>
Inthisexample,thefirst<script>tagincludesanexternalfile,somefile.js,whichcontainsJavaScriptcode.Thesecond<script>tagincludestheJavaScriptcodedirectlyintheHTMLcodeofthepage.ThebrowserexecutestheJavaScriptcodeinthesequenceitfindsitonthepageandallthecodeinalltagssharethesameglobalnamespace.Thismeansthatwhenyoudefineavariableinsomefile.js,italsoexistsinthesecond<script>block.
BOM and DOM - an overviewThe JavaScript code in a page has access to a number of objects. These objects can be dividedintothefollowingtypes:
CoreECMAScriptobjects:ThisconsistsofalltheobjectsmentionedinthepreviouschaptersDOM:Thisconsistsofobjectsthathavetodowiththecurrentlyloadedpage,whichisalsocalledthedocumentBOM:Thisconsistsofobjectsthatdealwitheverythingoutsidethepage-thebrowserwindowandthedesktopscreen
DOMstandsforDocumentObjectModelandBOMforBrowserObjectModel.
TheDOMisastandardgovernedbytheWorldWideWebConsortium(W3C)andhasdifferentversions,calledlevels,suchasDOMLevel1,DOMLevel2,andsoon.Browsersinusetodayhavedifferentdegreesofcompliancewiththestandard,butingeneral,theyalmostallcompletelyimplementDOMLevel1.TheDOMwasstandardizedpostfactumafterthebrowservendorshadeachimplementedtheirownwaystoaccessthedocument.ThelegacypartfrombeforetheW3CtookoverisstillaroundandisreferredtoasDOM0,although,norealDOMLevel0standardexists.SomepartsofDOM0havebecomedefactostandardsasallmajorbrowserssupportthem;someofthesewereaddedtotheDOMLevel1standard.TherestofDOM0thatdidn'tfinditswaytoDOM1istoobrowserspecificandwon'tbediscussedhere.
Historically,BOMwasnotapartofanystandard.SimilartoDOM0,ithasasubsetofobjectsthatissupportedbyallmajorbrowsers,andanothersubsetthatisbrowser-specific.TheHTML5standard codifies common behavior among browsers, and it includes common BOM objects.Additionally,mobiledevicescomewiththeirspecificobjects(andHTML5aimstostandardizethoseaswell),whichtraditionallywerenotnecessaryfordesktopcomputers,butmakesenseinamobileworld,forexample,geolocation,cameraaccess,vibration,touchevents,telephony,andSMS.
Thischapterdiscussesonlycross-browsersubsetsofBOMandDOMLevel1,unlessnotedotherwiseinthetext.Eventhesesafesubsetsconstitutealargetopic,andafullreferenceisbeyondthescopeofthisbook.Youcanalsoconsultthefollowingreferences:
MozillaDOMreference(http://developer.mozilla.org/en/docs/Gecko_DOM_Reference)Mozilla'sHTML5wiki(https://developer.mozilla.org/en-US/docs/HTML/HTML5)Microsoft'sdocumentationforInternetExplorer(http://msdn2.microsoft.com/en-us/library/ms533050(vs.85).aspx)W3C'sDOMspecifications(http://www.w3.org/DOM/DOMTR)
BOMThe BOM is a collection of objects that give you access to the browser and the computer screen.Theseobjectsareaccessiblethroughtheglobalobjectwindow.
Thewindowobjectrevisited
Asyoualreadyknow,inJavaScript,there'saglobalobjectprovidedbythehostenvironment.Inthebrowserenvironment,thisglobalobjectisaccessibleusingwindow.Allglobalvariablesarealsoaccessibleaspropertiesofthewindowobject.Forexample,takealookatthefollowingcode:
>window.somevar=1;1>somevar;1
Additionally,allthecoreJavaScriptfunctions,discussedinChapter2,PrimitiveDataTypes,Arrays, Loops, and Conditions, are methods of the global object. Consider the following piece ofcode:
>parseInt('123a456');123>window.parseInt('123a456');123
Inadditiontobeingareferencetotheglobalobject,thewindowobjectalsoservesasecondpurpose-providinginformationaboutthebrowserenvironment.There'sawindowobjectforeveryframe,iframe,popup,orbrowsertab.
Let'sseesomeofthebrowser-relatedpropertiesofthewindowobject.Again,thesecanvaryfromonebrowsertoanother,solet'sonlyconsiderthe propertiesthatareimplementedconsistentlyandreliablyacrossallmajorbrowsers.
Usingwindow.navigatorproperty
Thenavigatorisanobjectthathassomeinformationaboutthebrowseranditscapabilities.Onepropertyisnavigator.userAgent,whichisalongstringofbrowseridentification.InFirefox,you'llgetthefollowingoutput:
> window.navigator.userAgent;"Mozilla/5.0(Macintosh;IntelMacOSX10_8_3)AppleWebKit/536.28.10(KHTML,likeGecko)Version/6.0.3Safari/536.28.10"
TheuserAgentstringinMicrosoftInternetExplorerissomethingasfollows:
"Mozilla/5.0(compatible;MSIE10.0;WindowsNT6.1;Trident/6.0)"
Asthebrowsershavedifferentcapabilities,developersareusingtheuserAgentstringtoidentifythebrowserandprovidedifferentversionsofthecode.Forexample,thefollowingcodesnippetsearchesforthepresenceoftheMSIEstringtoidentifyInternetExplorer:
if(navigator.userAgent.indexOf('MSIE')!==-1){//thisisIE}else{//notIE}
It'sbetternottorelyontheuserAgentstring,buttousefeaturesniffing(alsocalledcapabilitydetection)instead.Thereasonforthisisthatit'shardtokeeptrackofallbrowsersandtheirdifferentversions.It'smucheasiertosimplycheckifthefeatureyouintendtouseisindeedavailableintheuser'sbrowser.Forexample,takealookatthefollowingcodesnippet:
if(typeofwindow.addEventListener==='function'){//featureissupported,let'suseit}else{//hmm,thisfeatureisnotsupported,willhaveto//thinkofanotherway}
AnotherreasontoavoiduserAgentsniffingisthatsomebrowsersallowuserstomodifythestringandpretendtheyareusingadifferentbrowser.
Yourconsoleisacheatsheet
Theconsoleletsyouinspectwhat'sinanobjectandthisincludesalltheBOMandDOMproperties.Justtypethefollowingcode:
>navigator;
Thenclickontheresult.Theresultisalistofpropertiesandtheirvalues,asshowninthefollowingscreenshot:
Usingwindow.locationproperty
ThelocationpropertypointstoanobjectthatcontainsinformationabouttheURLofthecurrentlyloadedpage.Forexample,location.hrefisthefullURLandlocation.hostnameisonlythedomain.Withasimpleloop,youcansee thefulllistofpropertiesofthelocationobject.
Imagineyou'reonapagewiththefollowingURL:
http://search.phpied.com:8080/search?q=java&what=script#results.
Considerthefollowingcode:
for(variinlocation){if(typeoflocation[i]==="string"){console.log(i+'="'+location[i]+'"');}}href="http://search.phpied.com:8080/search?q=java&what=script#results"hash="#results"host="search.phpied.com:8080"hostname="search.phpied.com"pathname="/search"port=<<8080>>protocol=<<http:>>search="?q=java&what=script"
Therearealsothreemethodsthatlocationpropertyprovides,namelyreload(),assign(),andreplace().
It'sinterestinghowmanydifferentwaysexistforyoutonavigatetoanotherpage.Thefollowingareafewways:
>window.location.href='http://www.packtpub.com';>location.href='http://www.packtpub.com';>location='http://www.packtpub.com';>location.assign('http://www.packtpub.com');
Thereplace()methodisalmostthesameasassign().Thedifferenceisthatitdoesn'tcreateanentryinthebrowser'shistorylistasfollows:
>location.replace('http://www.yahoo.com');
Toreloadapage,youcanusethefollowingcode:
>location.reload();
Alternatively,youcanuselocation.hreftopointittoitself,asfollows:
>window.location.href=window.location.href;
Or,simplyusethefollowingcode:
>location=location;
Usingwindow.historyproperty
Thewindow.historypropertyallowslimitedaccesstothepreviouslyvisitedpagesinthesamebrowsersession.Forexample,youcanseehowmanypagestheuserhasvisitedbeforecomingtoyourpage,asfollows:
>window.history.length;5
YoucannotseetheactualURLsthough.Forprivacyreasons,thisdoesn'twork.Seethefollowingcode:
>window.history[0];
Youcan,however,navigatebackandforththroughtheuser'ssessionasiftheuserhadclickedontheback/forwardbrowserbuttons,asfollows:
>history.forward();>history.back();
Youcanalsoskippagesbackandforthwithhistory.go().Thisisthesameascallinghistory.back().Thecodeforhistory.go()isasfollows:
>history.go(-1);
Togotwopagesback,usethefollowingcode:
>history.go(-2);
Reloadthecurrentpageusingthefollowingcode:
>history.go(0);
MorerecentbrowsersalsosupportHTML5historyAPI,whichletsyouchangetheURLwithoutreloading the page. This is perfect for dynamic pages because they can allow users to bookmark aspecificURL,whichrepresentsthestateoftheapplication,andwhentheycomeback,orsharewiththeirfriends,thepagecanrestoretheapplicationstatebasedontheURL.TogetasenseofthehistoryAPI,gotoanypageandwritethefollowinglinesofcodeintheconsole:
>history.pushState({a:1},"","hello");> history.pushState({b: 2}, "", "hello-you-too");
>history.state;
NoticehowtheURLchanges,butthepageisthesame.Now,experimentwithbackandforwardbuttonsinthebrowserandinspectthehistory.stateobjectagain.
usingwindow.framesproperty
Thewindow.framespropertyisacollectionofalloftheframesinthecurrentpage.Itdoesn'tdistinguishbetweenframesandiframes(inlineframes).Regardlessofwhetherthereareframesonthepageornot,window.framesalwaysexistsandpointstowindow,asfollows:
> window.frames === window;true
Let'sconsideranexamplewhereyouhaveapagewithoneiframe,asfollows:
<iframename="myframe"src="hello.html"/>
Inordertotellifthereareanyframesonthepage,youcancheckthelengthproperty.Incaseofoneiframe,you'llseethefollowingoutput:
>frames.length1
Eachframecontainsanotherpage,whichhasitsownglobalwindowobject.
Togetaccesstotheiframe'swindow,youcanexecuteanyofthefollowing:
>window.frames[0];>window.frames[0].window;>window.frames[0].window.frames;>frames[0].window;>frames[0];
Fromtheparentpage,youcanaccesspropertiesofthechildframeaswell.Forexample,youcanreloadtheframeasfollows:
> frames[0].window.location.reload();
Frominsidethechild,youcanaccesstheparentasfollows:
>frames[0].parent===window;true
Usingapropertycalledtop,youcanaccessthetop-mostpage-theonethatcontainsalltheotherframes-fromwithinanyframe,asfollows:
>window.frames[0].window.top===window;true>window.frames[0].window.top===window.top;true>window.frames[0].window.top===top;true
Inaddition,selfisthesameaswindow,asyoucanseeinthefollowingcodesnippet:
>self===window;true>frames[0].self==frames[0].window;true
Ifaframehasanameattribute,youcannotonlyaccesstheframebyname, butalsobyindex,asshowninthefollowingpieceofcode:
>window.frames['myframe']===window.frames[0];true
Or,alternatively,youcanusethefollowingcode:
>frames.myframe===window.frames[0];true
Usingwindow.screenproperty
Thescreenpropertyprovidesinformationabout theenvironmentoutsidethebrowser.Forexample,thescreen.colorDepthpropertycontainsthecolorbitdepth(thecolorquality)ofthemonitor.Thisismostlyusedforstatisticalpurposes.Takealookatthefollowinglineofcode:
> window.screen.colorDepth;32
Youcanalsochecktheavailablescreenrealestate(theresolution),asfollows:
>screen.width;1440>screen.availWidth;1440>screen.height;900>screen.availHeight;847
ThedifferencebetweenheightandavailHeightisthatheightisthewholescreen,whileavailHeightsubtractsanyoperatingsystemmenus,suchastheWindowstaskbar.ThesameisthecaseforwidthandavailWidth.
Somewhatrelatedisthepropertymentionedinthefollowingcode:
>window.devicePixelRatio;1
Ittellsyouthedifference(ratio)betweenphysicalpixelsanddevicepixelsintheretinadisplaysinmobiledevices,forexample,value2iniPhone.
window.open()/close()method
Havingexploredsomeofthemostcommoncross-browserpropertiesofthewindowobject,let'smovetosomeofthemethods.Onesuchmethodisopen(),whichallowsyoutoopennewbrowserwindows(popups).Variousbrowserpoliciesandusersettingsmaypreventyoufromopeningapopup(duetoabuseofthetechniqueformarketingpurposes),butgenerally,youshouldbeabletoopenanewwindowifitwasinitiatedbytheuser.Otherwise,ifyoutrytoopenapopupasthepageloads,itwillmostlikelybeblocked,becausetheuserdidn'tinitiateitexplicitly.
Thewindow.open()methodacceptsthefollowingparameters:
URLtoloadinthenewwindowNameofthenewwindowthatcanbeusedasthevalueofaform'stargetattributeComma-separatedlistoffeatures,whichisasfollows:
resizable:Shouldtheuserbeabletoresizethenewwindowwidth,height:Widthandheightofthepopupstatus:Shouldthestatusbarbevisible
Thewindow.open()methodreturnsareferencetothewindowobjectofthenewlycreatedbrowserinstance.Thefollowingisanexample:
varwin=window.open('http://www.packtpub.com','packt','width=300,height=300,resizable=yes');
Thewinvariablepointstothewindowobjectofthepopup.Youcancheckifwinhasafalsyvalue,whichmeansthatthepopupwasblocked.
Thewin.close()methodclosesthenewwindow.
It'sbesttostayawayfromopeningnewwindowsforaccessibilityandusabilityreasons.Ifyoudon'tlikesitespoppingupwindowstoyou,whydoittoyourusers?Therearelegitimatepurposes,suchasprovidinghelpinformationwhilefillingoutaform,butoften,thesamecanbeachievedwithalternativesolutions,suchasusingafloating<div>insidethepage.
window.moveTo()andwindow.resizeTo()methods
Continuingwiththeshadypracticesfromthepast,thefollowingaremoremethodstoirritateyourusers,providedtheirbrowserandpersonalsettingsallowyoutodoso:
window.moveTo(100,100):Thismovesthebrowserwindowtoscreenlocationx=100andy=100,whichiscountedfromthetop-leftcornerwindow.moveBy(10,-10):Thismovesthewindow10pixelstotherightand10pixelsupfromitscurrentlocationwindow.resizeTo(x,y)andwindow.resizeBy(x,y):Theseacceptthesameparametersasthemovemethods,buttheyresizethewindowasopposedtomovingit
Again,trytosolvetheproblemyou'refacingwithoutresortingtothesemethods.
window.alert(),window.prompt(),andwindow.confirm()methods
InChapter2,PrimitiveDataTypes,Arrays,Loops,andConditions,wetalkedaboutthealert()function.Nowyouknowthatglobalfunctionsareaccessibleasmethodsoftheglobalobject,soalert('Watchout!')andwindow.alert('Watchout!')areexactlythesame.
Thealert()functionisnotanECMAScriptfunctionbutaBOMmethod.Inadditiontoit,twootherBOMmethodsallowyoutointeractwiththeuserthroughsystemmessages.Thefollowingarethemethods:
confirm():Thisgivestheusertwooptions,OKandCancelprompt():Thiscollectstextualinput
Thisishowitworks:
>varanswer=confirm('Areyoucool?');>answer;
It presents you with a window similar to the following screenshot (the exact look depends on thebrowserandtheoperatingsystem):
You'llnoticethefollowingthings:
Nothinggetswrittentotheconsoleuntilyou closethismessage,whichmeansthatanyJavaScriptcodeexecutionfreezes,waitingfortheuser'sanswerClickingonOKreturnstrue,clickingonCancelorclosingthemessageusingtheXicon,ortheESCkey,returnsfalse
Thisishandytoconfirmuseractions,asshowninthefollowingpieceofcode:
if(confirm('Sureyouwanttodeletethis?')){//delete}else{//abort
}
MakesureyouprovideanalternativewaytoconfirmuseractionsforpeoplewhohavedisabledJavaScript,orforsearchenginespiders.
Thewindow.prompt()methodpresentstheuserwithadialogtoentertext,asfollows:
>varanswer=prompt('Andyournamewas?');>answer;
Thisresultsinthefollowingdialogbox(Chrome,MacOS):
Thevalueofanswerisoneofthefollowing:
null:ThishappensifyouclickonCancel,theXicon,orpressESCkey""(emptystring):ThishappensifyouclickonOKorpressEnterwithouttypinganythingAtextstring:ThisisifyoutypesomethingandthenclickonOKorpressEnter
Thefunctionalsotakesastringasasecondparameteranddisplaysitasadefaultvalueprefilledintotheinputfield.
Usingwindow.setTimeout()andwindow.setInterval()methods
ThesetTimeout()andsetInterval()methodsallowschedulingtheexecutionofapieceofcode.ThesetTimeout()methodattemptstoexecutethegivencodeonce,afteraspecifiednumberofmilliseconds.ThesetInterval()methodattemptstoexecuteitrepeatedlyafteraspecifiednumberofmillisecondshaspassed.
Thisshowsanalertafterapproximately2seconds(2000milliseconds).Considerthefollowingcode:
>functionboo(){alert('Boo!');}>setTimeout(boo,2000);4
Asyoucansee,thefunctionreturnedaninteger(inthiscase,4)representingtheIDofthetimeout.YoucanusethisIDtocancelthetimeoutusingclearTimeout().Inthefollowingexample,ifyou'requickenough,andclearthetimeoutbefore 2secondshavepassed,thealertwillneverbeshown,asyoucanseeinthefollowingcode:
>varid=setTimeout(boo,2000);>clearTimeout(id);
Let'schangeboo()tosomethinglessintrusive,asfollows:
>functionboo(){console.log('boo');}
Now,usingsetInterval(),youcanscheduleboo()toexecuteevery2seconds,untilyoucancelthescheduledexecutionwithclearInterval().Considerthefollowingcode:
>varid=setInterval(boo,2000);booboobooboobooboo>clearInterval(id);
Notethatbothfunctionsacceptapointertoacallbackfunctionasafirstparameter.Theycanalsoacceptastring,whichisevaluatedwitheval();however,asyouknow,eval()isevil,soitshouldbeavoided.Moreover,whatifyouwanttopassargumentstothefunction?Insuchcases,youcanjustwrapthefunctioncallinsideanother function.
Thefollowingcodeisvalid,butnotrecommended:
//badidea
varid=setInterval("alert('boo,boo')",2000);
Thisalternativeispreferred:
varid=setInterval(function(){alert('boo,boo');},2000
);
Beawarethatschedulingafunctioninsomeamountofmillisecondsisnotaguaranteethatitwillexecuteexactlyatthattime.Onereasonisthatmostbrowsersdon'thavemillisecondresolutiontime.Ifyouschedulesomethingin3milliseconds,itwillexecuteafteraminimumof15inolderIEsandsoonerinmoremodernbrowsers,butmostlikely,notin1millisecond.Theotherreasonisthatthebrowsersmaintainaqueueofwhatyourequestthemtodo.100millisecondstimeoutmeansaddtothequeueafter100milliseconds.However,ifthequeueisdelayedbysomethingslowhappening,yourfunctionwillhavetowaitandexecuteafter,say,120milliseconds.
MorerecentbrowsersimplementtherequestAnimationFrame()function.It'spreferabletothetimeoutfunctionsbecauseyou'reaskingthebrowsertocallyourfunctionwheneverithasavailableresources,notafterapredefinedtimeinmilliseconds.Tryexecutingthefollowingcodesnippetinyourconsole:
functionanimateMe(){webkitRequestAnimationFrame(function(){console.log(newDate());animateMe();});}
animateMe();
window.documentproperty
Thewindow.documentpropertyisaBOMobjectthatreferstothecurrentlyloadeddocument(page).ItsmethodsandpropertiesfallintotheDOMcategoryofobjects.Takeadeepbreath(andmaybefirstlookattheBOMexercisesattheendofthechapter)andlet'sdiveintotheDOM.
DOMThe DOM represents an XMLor an HTMLdocument as a tree of nodes. Using DOMmethods andproperties,youcanaccessanyelementonthepage,modifyorremoveelements,oraddnewones.TheDOMisalanguage-independentAPIandcanbeimplementednotonlyinJavaScript,butalsoinanyotherlanguage.Forexample,youcangeneratepagesontheserver-sidewithPHP'sDOMimplementation(http://php.net/dom).
TakealookatthisexampleHTMLpage:
<!DOCTYPEhtml><html><head><title>Mypage</title>
</head><body><pclass="opener">firstparagraph</p><p><em>second</em>paragraph</p><pid="closer">final</p><!--andthat'saboutit--></body></html>
Considerthesecondparagraph(<p><em>second</em>paragraph</p>).Youwillseethatit'sa<p>tag,andit'scontainedinthe<body>tag.Ifyouthinkintermsoffamilyrelationships,youcansaythat<body>istheparentof<p>and<p>isthechild.Thefirstandthe thirdparagraphswouldalsobechildrenofthe<body>tag,andatthesametime,siblingsofthesecondparagraph.The<em>tagisachildofthesecond<p>,so<p>isitsparent.Theparent-childrelationshipscanberepresentedgraphicallyinanancestrytree,calledtheDOMtree:
Thepreviousscreenshotshowswhatyou'llseeintheWebKitconsole'sElementstabafteryouexpandeachnode.
Youcanseehowallofthetagsareshownasexpandablenodesonthetree.Althoughnotshown,thereexiststheso-calledtextnodes,forexample,thetextinsidethe<em>tag(thewordsecond)isatextnode.Whitespaceisalsoconsideredatextnode.CommentsinsidetheHTMLcodearealsonodesinthetree,the<!-andthat'saboutit->commentintheHTMLsourceisacommentnodeinthetree.
EverynodeintheDOMtreeisanobjectandthePropertiessectionontherightlistsallofthepropertiesandmethodsyoucanusetoworkwith theseobjects,followingtheinheritancechainofhowthisobjectwascreated:
Youcanalsoseetheconstructorfunctionthatwasusedbehindthescenestocreateeachoftheseobjects.Although,thisisnottoopracticalforday-to-daytasks,itmaybeinterestingtoknowthat,forexample,<p>iscreatedbytheHTMLParagraphElement()constructor,theobjectthatrepresentstheheadtagiscreatedbyHTMLHeadElement(),andsoon.Youcannotcreateobjectsusingtheseconstructorsdirectly,though.
CoreDOMandHTMLDOM
Onelastdiversionbeforemovingontomorepracticalexamples.Asyounowknow,theDOMrepresentsbothXMLdocumentsandHTMLdocuments.Infact,HTMLdocumentsareXMLdocuments,butalittlemorespecific.Therefore,aspartofDOMLevel1,thereisaCoreDOMspecificationthatisapplicabletoallXMLdocuments,andthereisalsoanHTMLDOMspecification,whichextendsandbuildsuponthecoreDOM.Ofcourse,theHTMLDOMdoesn'tapplytoallXMLdocuments,butonlytoHTMLdocuments.Let'sseesomeexamplesofCoreDOMandHTMLDOMconstructors:
Constructor Inherits fromCore orHTML
Comment
Node Core Anynodeonthetree
Document Node Core Thedocumentobject,themainentrypointtoanyXMLdocument
HTMLDocument Document HTMLThisiswindow.documentorsimplydocument,theHTML-specificversionofthepreviousobject,whichyou'lluseextensively
Element Node CoreEverytaginthesourceisrepresentedbyanelement.That'swhyyousay-thePelementmeaningthe<p></p>tag
HTMLElement Element HTML General-purposeconstructor,allconstructorsforHTMLelementsinheritfromit
HTMLBodyElement HTMLElement HTML Elementrepresentingthe<body>tag
HTMLLinkElement HTMLElement HTML AnAelement:an<ahref="..."></a>tag
Andothersuchconstructors
HTMLElement HTML AlltherestoftheHTMLelements
CharacterData Node Core General-purposeconstructorfordealingwithtexts
Text CharacterData CoreTextnodeinside atag;in<em>second</em>,youhavetheelementnodeEMandthetextnodewithvaluesecond
Comment CharacterData Core <!--anycomment-->
Attr Node CoreRepresentsanattributeofatag;in<pid="closer">,theidattributeisaDOMobjectcreatedbytheAttr()constructor
NodeList Core Alistofnodes,anarray-likeobjectthathasalengthproperty
NamedNodeMap CoreSameasNodeList,butthenodescanbeaccessedbyname,notonlybynumericindex.
HTMLCollection HTML Similar to NamedNodeMap but specific for HTML.
Theseare,bynomeans,alloftheCoreDOMandHTMLDOMobjects.Forthefulllist,consulthttp://www.w3.org/TR/DOM-Level-1/.
NowthatthisbitoftheDOMtheoryisbehindyou,let'sfocusonthepracticalsideofworkingwith the DOM. In the following sections, you'll learn how to do the following topics:
AccessDOMnodesModifynodesCreatenewnodesRemovenodes
AccessingDOMnodes
Beforeyoucanvalidatetheuserinputinaformonapageorswapanimage,youneedtogetaccesstotheelementyouwanttoinspectormodify.Luckily,therearemanywaystogettoanyelement,eitherbynavigatingaroundtraversingtheDOMtreeorbyusingashortcut.
It'sbestifyoustartexperimentingwithallofthenewobjectsandmethods.Theexamplesyou'llseeusethesamesimpledocumentthatyousawatthebeginningoftheDOMsection,andwhichyoucanaccessathttp://www.phpied.com/files/jsoop/ch7.html.Openyourconsole,andlet'sgetstarted.
Thedocumentnode
Thedocumentnodegivesyouaccesstothecurrentdocument.Toexplore thisobject,youcanuseyourconsoleasacheatsheet.Typeconsole.dir(document)andclickontheresult:
Alternatively,youcanbrowseallofthepropertiesandmethodsofthedocumentobjectDOMpropertiesintheElementspanel:
Allnodes,includingthedocumentnode,textnodes,elementnodes,andattributenodeshavenodeType,nodeNameandnodeValueproperties:
>document.nodeType;9
Thereare12nodetypes,representedbyintegers.Asyoucansee,thedocumentnodetypeis9.Themostcommonlyusedare1(element),2(attribute),and3(text).
Nodesalsohavenames.ForHTMLtags,thenodenameisthetagname(tagNameproperty).Fortextnodes,it's#text,andfordocumentnodes,thenameisasfollows:
>document.nodeName;"#document"
Nodes can also have node values. For example, for text nodes, the value is the actual text. Thedocumentnodedoesn'thaveavalue,whichcanbeseenasfollows:
>document.nodeValue;null
documentElement
Now,let'smovearoundthetree.XMLdocumentsalwayshaveonerootnodethatwrapstherestofthedocument.ForHTMLdocuments,therootisthe<html>tag.Toaccesstheroot,youwillusethedocumentElementpropertyofthedocumentobject.
>document.documentElement;<html>...</html>
nodeTypeis1(anelementnode)whichcanbeseenasfollows:
>document.documentElement.nodeType;1
Forelementnodes,bothnodeNameandtagNamepropertiescontainthenameofthetag,asseeninthefollowingoutput:
>document.documentElement.nodeName;"HTML">document.documentElement.tagName;"HTML"
Childnodes
Inordertotellifanodehasanychildren,youwillusehasChildNodes(),asfollows:
>document.documentElement.hasChildNodes();true
TheHTMLelementhasthreechildren,theheadandthebodyelementsandthewhitespacebetweenthem(whitespaceiscountedinmost,butnotallbrowsers).YoucanaccessthemusingthechildNodesarray-likecollection,asfollows:
>document.documentElement.childNodes.length;3>document.documentElement.childNodes[0];<head>...</head>>document.documentElement.childNodes[1];#text>document.documentElement.childNodes[2];<body>...</body>
AnychildhasaccesstoitsparentthroughtheparentNodeproperty,asseeninthefollowingcode:
>document.documentElement.childNodes[1].parentNode;<html>...</html>
Let'sassignareferencetobodytoavariable,asfollows:
>varbd=document.documentElement.childNodes[2];
Howmanychildrendoesthebodyelementhave?Considerthefollowingpieceofcode
>bd.childNodes.length;9
Asarefresher,here,again,isthebodyofthedocument:
<body><pclass="opener">firstparagraph</p><p><em>second</em>paragraph</p><pid="closer">final</p><!--andthat'saboutit--></body>
Howcomebodyhas9children?Well,threeparagraphsplusonecommentmakesfournodes.Thewhitespacebetweenthesefournodesmakesthreemoretextnodes.Thismakesatotalofsevensofar.Thewhitespacebetween<body>andthefirst<p>istheeighthnode.Thewhitespacebetweenthecommentandtheclosing</body>isanothertextnode.Thismakesatotalofninechildnodes.Justtypebd.childNodesintheconsoletoinspectthemall.
Attributes
As the first child of the body is a whitespace, the second child (index 1) is the first paragraph.Refertothefollowingpieceofcode:
>bd.childNodes[1];<pclass="opener">firstparagraph</p>
YoucancheckwhetheranelementhasattributesusinghasAttributes(),asfollows:
>bd.childNodes[1].hasAttributes();true
Howmanyattributes?Inthisexample,oneistheclassattribute,whichcanbeseenasfollows:
>bd.childNodes[1].attributes.length;1
Youcanaccesstheattributesbyindexandname.YoucanalsogetthevalueusingthegetAttribute()method,whichisasfollows:
>bd.childNodes[1].attributes[0].nodeName;"class">bd.childNodes[1].attributes[0].nodeValue;"opener">bd.childNodes[1].attributes['class'].nodeValue;"opener">bd.childNodes[1].getAttribute('class');
"opener"
Accessingthecontentinsideatag
Let'stakealookatthefirstparagraph:
>bd.childNodes[1].nodeName;"P"
YoucangetthetextcontainedintheparagraphusingthetextContentproperty.Itdoesn'texistinolderIEs,butanotherpropertycalledinnerTextreturnsthesamevalue,asseeninthefollowingoutput:
>bd.childNodes[1].textContent;"firstparagraph"
ThereisalsotheinnerHTMLproperty.It'sarelativelynewadditiontotheDOMstandard,despitethefactthatitpreviouslyexistedinallmajorbrowsers.Itreturns(orsets)HTMLcodecontainedinanode.YoucanseehowthisisalittleinconsistentasDOMtreatsthedocumentasatreeofnodes,notasastringoftags.However,innerHTMLissoconvenienttousethatyou'llseeiteverywhere.Refertothefollowingcode:
>bd.childNodes[1].innerHTML;"firstparagraph"
The first paragraph contains only text, so innerHTML is the same as textContent (or innerTextin IE). However, the second paragraph does contain an em node, so you can see the difference asfollows:
>bd.childNodes[3].innerHTML;"<em>second</em>paragraph">bd.childNodes[3].textContent;"secondparagraph"
AnotherwaytogetthetextcontainedinthefirstparagraphisbyusingthenodeValuemethodofthetextnodecontainedinsidethepnode,asfollows:
>bd.childNodes[1].childNodes.length;1>bd.childNodes[1].childNodes[0].nodeName;"#text">bd.childNodes[1].childNodes[0].nodeValue;"firstparagraph"
DOMaccessshortcuts
UsingchildNodes,parentNode,nodeName,nodeValue,andattributes,youcannavigateupanddownthetreeanddoanythingwiththedocument.However,thefactthatwhitespaceisatextnodemakesthisafragilewayofworkingwiththeDOM.Ifthepagechanges,yourscriptmayno
longerworkcorrectly.Also,ifyouwanttogettoanodedeeperinthetree,itcouldtakeabitofcodebeforeyougetthere.That'swhyyouhaveshortcutmethods,namely,getElementsByTagName(),getElementsByName(),andgetElementById().
The getElementsByTagName() method takes a tag name (the name of an element node) andreturns an HTMLcollection (array-like object) of nodes with the matching tag name. Forexample,thefollowingexampleasks-givemeacountofallparagraphs,whichisgivenasfollows:
>document.getElementsByTagName('p').length;3
Youcanaccessaniteminthelistusingthebracketsnotation,ortheitem()method,andpassingtheindex(0forthefirstelement).Usingitem()isdiscouraged,asarraybracketsaremoreconsistentandalsoshortertotype.Refertothefollowingpieceofcode:
>document.getElementsByTagName('p')[0];<pclass="opener">firstparagraph</p>>document.getElementsByTagName('p').item(0);<pclass="opener">firstparagraph</p>
Gettingthecontentsofthefirstpcanbedoneasfollows:
>document.getElementsByTagName('p')[0].innerHTML;"firstparagraph"
Accessingthelastpcanbedoneasfollows:
>document.getElementsByTagName('p')[2];<pid="closer">final</p>
Toaccesstheattributesofanelement,youcanusetheattributescollectionorgetAttribute(),asshownpreviously.However,ashorterwayistousetheattributenameasapropertyoftheelementyou'reworkingwith.So,togetthevalueoftheid attribute,youwilljustuseidasaproperty,whichisasfollows:
>document.getElementsByTagName('p')[2].id;"closer"
Gettingtheclassattributeofthefirstparagraphwon'tworkthough.It'sanexceptionbecauseitjusthappenssothatclassisareservedwordinECMAScript.YoucanuseclassNameinstead,asfollows:
>document.getElementsByTagName('p')[0].className;"opener"
UsinggetElementsByTagName(),youcangetalloftheelementsonthepage,asfollows:
>document.getElementsByTagName('*').length;8
InearlierversionsofIEbeforeIE7,*isnotacceptableasatagname.Togetallelements,youcanuseIE'sproprietarydocument.allcollection,although,selectingeveryelementisrarelyneeded.
TheothershortcutmentionedisgetElementById().Thisisprobablythemostcommonwayofaccessinganelement.YoujustassignIDstotheelementsyouplantoplay withandthey'llbeeasytoaccesslateron,asseeninthefollowingcode:
>document.getElementById('closer');<pid="closer">final</p>
Additionalshortcutmethodsinmorerecentbrowsersincludethefollowing:
getElementByClassName():ThismethodfindselementsusingtheirclassattributequerySelector():ThismethodfindsanelementusingaCSSselectorstringquerySelectorAll():Thismethodisthesameasthepreviousonebutreturnsallmatchingelements,notjustthefirst
Siblings,body,first, andlastchild
ThenextSiblingandpreviousSiblingaretwootherconvenientpropertiestonavigatetheDOMtreeonceyouhaveareferencetooneelement.Considerthefollowingcode:
> var para = document.getElementById('closer');>para.nextSibling;#text>para.previousSibling;#text>para.previousSibling.previousSibling;<p>...</p>>para.previousSibling.previousSibling.previousSibling;#text>para.previousSibling.previousSibling.nextSibling.nextSibling;<pid="closer">final</p>
Thebodyelementisusedsooftenthatithasitsownshortcut,whichisasfollows:
>document.body;<body>...</body>>document.body.nextSibling;null>document.body.previousSibling.previousSibling;<head>...</head>
ThefirstChildandlastChildpropertiesarealsoconvenient.ThefirstChildpropertyisthesameaschildNodes[0]andlastChildisthesameaschildNodes[childNodes.length-
1]properties:
>document.body.firstChild;#text>document.body.lastChild;#text>document.body.lastChild.previousSibling;<!--andthat'saboutit-->>document.body.lastChild.previousSibling.nodeValue;
" and that's about it "
Thefollowingscreenshotshowsthefamilyrelationshipsbetweenthebodyandthethreeparagraphsinit.Forsimplicity,allthewhitespacetextnodesareremovedfromthescreenshot:
WalktheDOM
Towrapup,here'safunctionthattakesanynodeandwalksthroughtheDOMtreerecursively,startingfromthegivennode,asfollows:
functionwalkDOM(n){do{console.log(n);if(n.hasChildNodes()){walkDOM(n.firstChild);}}while(n=n.nextSibling);}
Youcantestthefunctionasfollows:
>walkDOM(document.documentElement);>walkDOM(document.body);
ModifyingDOMnodes
NowthatyouknowawholelotofmethodstoaccessanynodeoftheDOMtreeanditsproperties,let'sseehowyoucanmodifythesenodes:
Let'sassignapointertothelastparagraphtothevariablemy,asfollows:
>varmy=document.getElementById('closer');
Now,changingthetextoftheparagraphcanbeaseasyaschangingtheinnerHTMLvalue,whichisasfollows:
>my.innerHTML='final!!!';"final!!!"
AsinnerHTMLacceptsastringofHTMLsourcecode,youcanalsocreateanewemnodeintheDOMtreeasfollows:
>my.innerHTML='<em>my</em>final';"<em>my</em>final"
Thenewemnodebecomesapartofthetree.Letstakealookatthefollowingcode:
>my.firstChild;<em>my</em>>my.firstChild.firstChild;"my"
AnotherwaytochangetextistogettheactualtextnodeandchangeitsnodeValue,asshowninthefollowingpieceofcode:
>my.firstChild.firstChild.nodeValue='your';"your"
Modifyingstyles
Oftenyoudon'tchangethecontentofanode,butitspresentation.Theelementshaveastyleproperty,whichinturnhasapropertymappedtoeachCSSproperty.Forexample,changingthestyleoftheparagraphtoaddaredborder,asfollows:
>my.style.border="1pxsolidred";"1pxsolidred"
CSSpropertiesoftenhavedashes,butdashesarenotacceptableinJavaScriptidentifiers.Insuchcases,youskipthedashanduppercasethenextletter.So,padding-topbecomespaddingTop,margin-leftbecomesmarginLeft,andsoon.Takealookatthefollowingcode:
>my.style.fontWeight='bold';
"bold"
YoualsohaveaccesstothecssTextpropertyofstyle,whichletsyouworkwithstylesasstrings,asyoucanseeinthefollowingcodesnippet:
>my.style.cssText;"border:1pxsolidred;font-weight:bold;"
Moreover,modifyingstylesisastringmanipulation:
>my.style.cssText+="border-style:dashed;""border:1pxdashedred;font-weight:bold;border-style:dashed;"
Funwithforms
Asmentionedearlier,JavaScriptisgreatforclient-sideinputvalidationandcansaveafewround-triptotheserver.Let'spracticeformmanipulationsandplayalittlebitwithaformlocatedonapopularpage,www.google.com:
FindingthefirsttextinputusingthequerySelector()methodandaCSSselectorstringisasfollows:
>varinput=document.querySelector('input[type=text]');
Accessingthesearchbox.Considerthefollowingcode:
>input.name;"q"
Changingthesearchquerybysettingthetextcontainedinthevalueattributeisdoneasfollows:
> input.value = 'my query';"myquery"
Now,let'shavesomefunandchangethewordLuckywithTrickyinthebutton:
>varfeeling=document.querySelectorAll("button")[2];>feeling.textContent=feelingtextContent.replace(/Lu/,'Tri');"I'mFeelingTricky"
Now,let'simplementthetrickypartandmakethatbuttonshowandhideforonesecond.Youcandothiswithasimplefunction.Let'scallittoggle().Everytimeyoucallthefunction,itchecksthevalueoftheCSSpropertyvisibility,andsetsittovisibleifit'shiddenandviceversausingthefollowingcodesnippet:
functiontoggle(){varst=document.querySelectorAll('button')[2].style;st.visibility=(st.visibility==='hidden')?'visible'
: 'hidden';}
Insteadofcallingthefunctionmanually,let'ssetanintervalandcalliteverysecond:
>varmyint=setInterval(toggle,1000);
Theresult?Thebuttonstartsblinking,makingittrickiertoclick.Whenyou'retiredofchasingit,justremovethetimeoutintervalbywritingthefollowinglineofcode:
>clearInterval(myint);
Creatingnewnodes
Tocreatenewnodes,youcanusethecreateElement()andcreateTextNode()methods.Onceyouhavethenewnodes,youcanaddthemtotheDOMtreeusingappendChild(),insertBefore(),orreplaceChild().
Reloadhttp://www.phpied.com/files/jsoop/ch7.htmlandlet'splay.
CreateanewpelementandsetitsinnerHTML,asshowninthefollowingcode:
>varmyp=document.createElement('p');>myp.innerHTML='yetanother';"yetanother"
Thenewelementautomaticallygetsallthedefaultproperties,suchasstyle,whichyoucanmodifyasfollows:
>myp.style;CSSStyleDeclaration>myp.style.border='2pxdottedblue';"2pxdottedblue"
UsingappendChild(),youcanaddthenewnodetotheDOMtree.Callingthismethodonthedocument.bodynodemeanscreatingonemorechildnoderightafterthelastchild,asfollows:
>document.body.appendChild(myp);<pstyle="border:2pxdottedblue;">yetanother</p>
Here'sanillustrationofhowthepagelookslikeafterthenewnodeisappended:
DOM-onlymethod
TheinnerHTMLpropertygetsthingsdonealittlemorequicklythanusingpureDOM.InpureDOM,youwillneedtoperformthefollowingsteps:
1. Createanewtextnodecontainingyetanothertext.
2. Createanewparagraphnode.3. Appendthetextnodeasachildtotheparagraph.4. Appendtheparagraphasachildtothebody.
Thisway,youcancreateanynumberoftextnodesandelementsandnestthem,howeveryoulike.Let'ssay,youwanttoaddthefollowingHTMLtotheendofthebody:
<p>onemoreparagraph<strong>bold</strong></p>
Presentingthepreceding codeasahierarchywouldbesomethinglikethefollowingcodesnippet:
Pelementtextnodewithvalue"onemoreparagraph"STRONGelementtextnodewithvalue"bold"
Thecodethataccomplishesthisisasfollows:
//createPvarmyp=document.createElement('p');
// create text node and append to Pvarmyt=document.createTextNode('onemoreparagraph');myp.appendChild(myt);//createSTRONGandappendanothertextnodetoitvarstr=document.createElement('strong');str.appendChild(document.createTextNode('bold'));//appendSTRONGtoPmyp.appendChild(str);//appendPtoBODYdocument.body.appendChild(myp);
UsingcloneNode()method
Anotherwaytocreatenodesisbycopyingorcloningexistingones.ThecloneNode()methoddoesthisandacceptsaBooleanparameter(true=deepcopywithallthechildren,false=shallowcopy,onlythisnode).Let'stestthemethod.
Gettingareferencetotheelementyouwanttoclonecanbedoneasfollows:
>varel=document.getElementsByTagName('p')[1];
Now,elreferstothesecondparagraphonthepagethatlookslikethefollowingcode:
<p><em>second</em>paragraph</p>
Let'screateashallowcloneofelandappendittothebodyasfollows:
>document.body.appendChild(el.cloneNode(false));
Youwon'tseeadifferenceonthepagebecausetheshallowcopyonlycopiedthePnodewithoutanychildren.Thismeansthatthetextinsidetheparagraph,whichisatext nodechild,wasnotcloned.Theprecedinglinewillbeequivalenttothefollowingcodeline:
>document.body.appendChild(document.createElement('p'));
However,ifyoucreateadeepcopy,thewholeDOMsubtreestartingfromPiscopied,andthisincludestextnodesandtheEMelement.Thislinecopies(visuallytoo)the secondparagraphtotheendofthedocument.Considerthefollowinglineofcode:
>document.body.appendChild(el.cloneNode(true));
YoucanalsocopyonlytheEMifyouwant,asshowninthefollowinglinesofcode:
>document.body.appendChild(el.firstChild.cloneNode(true));<em>second</em>
Or,youcancopyonlythetextnodewithvaluesecond,asfollows:
>document.body.appendChild(el.firstChild.firstChild.cloneNode(false));"second"
UsinginsertBefore()method
UsingappendChild(),youcanonlyaddnewchildrenattheendoftheselectedelement.Formorecontrolovertheexactlocation,thereisinsertBefore().ThisisthesameasappendChild(),butacceptsanextraparameterspecifyingwhere(beforewhichelement)toinsertthenewnode.Forexample,thefollowingcodeinsertsatextnodeattheendofthebodyelement:
>document.body.appendChild(document.createTextNode('boo!'));
Moreover,thiscreatesanothertextnodeandaddsitasthefirstchildofthebodyelement:
document.body.insertBefore(document.createTextNode('firstboo!'),document.body.firstChild);
Removingnodes
ToremovenodesfromtheDOMtree,youcanusetheremoveChild()method.Again,let'sstartfreshwiththesamepagewiththebody:
<body><pclass="opener">firstparagraph</p><p><em>second</em>paragraph</p><pid="closer">final</p><!--andthat'saboutit--></body>
Here'showyoucanremovethesecondparagraph:
>varmyp=document.getElementsByTagName('p')[1];>varremoved=document.body.removeChild(myp);
Themethodreturnstheremovednodeifyouwanttouseitlater.YoucanstillusealltheDOMmethodseventhoughtheelementisnolongerinthetree.Letstakealookonthefollowingcode:
>removed;<p>...</p>>removed.firstChild;<em>second</em>
There'salsothereplaceChild()methodthatremovesanodeandputsanotheroneinitsplace.
Afterremovingthenode,thetreelooksasfollows:
<body><pclass="opener">firstparagraph</p><pid="closer">final</p><!--andthat'saboutit--></body>
Now,thesecondparagraphistheonewiththeID"closer",whichisasfollows:
>varp=document.getElementsByTagName('p')[1];>p;<pid="closer">final</p>
Let'sreplacethisparagraphwiththeoneintheremovedvariable.Considerthefollowingcode:
>varreplaced=document.body.replaceChild(removed,p);
JustlikeremoveChild(),replaceChild()returnsareferencetothenodethatisnowoutofthetree:
>replaced;<pid="closer">final</p>
Now,thebodylookslikethefollowingpieceofcode:
<body><pclass="opener">firstparagraph</p><p><em>second</em>paragraph</p><!--andthat'saboutit--></body>
Aquickwaytowipeout allofthecontentofasubtreeistosetinnerHTMLtoablankstring.Thisremovesallthechildrenofthebodyelement:
>document.body.innerHTML='';""
Testingisdoneasfollows:
>document.body.firstChild;null
RemovingwithinnerHTMLisfastandeasy.TheDOM-onlywaywillbetogooverallofthechildnodesandremoveeachoneindividually.Here'salittlefunctionthatremovesallnodesfromagivenstartnode:
functionremoveAll(n){while(n.firstChild){n.removeChild(n.firstChild);}}
Ifyouwanttodeleteallthechildrenfromthebodyelementandleavethepagewithanempty<body></body>,usethefollowingcode:
>removeAll(document.body);
HTML-onlyDOMobjects
Asyoualreadyknow,theDOMappliestobothXMLandHTMLdocuments.Whatyou'velearnedearlierabouttraversingthetreeandthenadding,removing,ormodifyingnodes,appliestoanyXMLdocument.Thereare,however,someHTML-onlyobjectsandproperties.
Thedocument.bodyisonesuchHTML-onlyobject.It'ssocommontohavea<body>taginHTMLdocuments,andit'saccessedsooften,thatitmakessensetohaveanobjectthat'sshorterandfriendlierthantheequivalentdocument.getElementsByTagName('body')[0].
Thedocument.bodyelementisoneexampleofalegacyobjectinheritedfromtheprehistoricDOMLevel0andmovedtotheHTMLextension oftheDOMspecification.Thereareotherobjectssimilartodocument.bodyelement.Forsomeofthem,thereisnocoreDOMequivalent,forothers,thereisanequivalent;however,theDOM0originalwasanywayportedforsimplicityand legacy purposes. Let's see some of those objects.
Primitivewaystoaccessthedocument
totheelementsofanHTMLdocument.Thiswasdonemainlythroughanumberofcollections,whichareasfollows:UnliketheDOM,whichgivesyouaccesstoanyelement,andevencommentsandwhitespace,initially,JavaScripthadonlylimitedaccesstotheelementsofanHTMLdocument.Thiswasdonemainlythroughanumberofcollections,whichareasfollows:
document.images:Thisisacollectionofalloftheimagesonthepage.TheCoreDOMequivalentisdocument.getElementsByTagName('img').document.applets:Thisisthesameasdocument.getElementsByTagName('applet').document.links:Thedocument.linkscollectioncontainsalistofall<ahref="..."></a>tagsonthepage,meaningthe<a>tagsthathaveanhrefattribute.document.anchors:Thedocument.anchorscollectioncontainsalllinkswithanameattribute(<aname="..."></a>).document.forms:Oneofthemostwidelyusedcollectionsisdocument.forms,whichcontainsalistof<form>elements.
Let'splaywithapagethatcontainsaformandaninput(http://www.phpied.com/files/jsoop/ch7-form.html).Thefollowinglineofcodegivesyouaccesstothefirstformonthepage:
>document.forms[0];
It'sthesameasthefollowinglineofcode:
>document.getElementsByTagName('forms')[0];
Thedocument.formscollectioncontainscollectionsofinputfieldsandbuttons,accessiblethroughtheelementsproperty.Here'showtoaccessthefirstinputofthefirstformonthepage:
>document.forms[0].elements[0];
Onceyouhaveaccessto anelement,youcanaccessitsattributesasobjectproperties.Thefirstfieldofthefirstforminthetestpageisasfollows:
<inputname="search"id="search"type="text"size="50"maxlength="255"value="Enteremail..."/>
Youcanchangethetextinthefield(thevalueofthevalueattribute)usingthefollowingcode:
>document.forms[0].elements[0].value='[email protected]';"[email protected]"
Ifyouwanttodisablethefielddynamically,usethefollowingcode:
>document.forms[0].elements[0].disabled=true;
Whenformsorformelementshaveanameattribute,youcanaccessthembynametoo,asshowninthefollowingcode:
>document.forms[0].elements['search'];//arraynotation>document.forms[0].elements.search;//objectproperty
Usingdocument.write()method
Thedocument.write()methodallowsyoutoinsertHTMLintothepagewhilethepageisbeingloaded.Youcanhavesomethinglikethefollowingcode:
<p>Itisnow<script>document.write("<em>"+newDate()+"</em>");</script></p>
ThisisthesameasifyouhadthedatedirectlyinthesourceoftheHTMLdocument,asfollows:
<p>Itisnow<em>FriApr26201316:55:16GMT-0700(PDT)</em></p>
Notethatyoucanonlyusedocument.write()methodwhilethepageisbeingloaded.Ifyoutryitafterpageload,itwill replacethecontentofthewholepage.
It'srarethatyouwouldneeddocument.write()method,andifyouthinkyoudo,tryanalternativeapproach.ThewaystomodifythecontentsofthepageprovidedbyDOMLevel1arepreferredandaremuchmoreflexible.
Cookies,title,referrer,anddomain
Thefouradditionalpropertiesofdocumentyou'llseeinthissectionarealsoportedfromDOM
Level0totheHTMLextensionofDOMLevel1. Unlikethepreviousones,fortheseproperties,therearenocoreDOMequivalents.
The document.cookie is a property that contains a string. This string is the content of thecookies exchanged between the server and the client. When the server sends a page to thebrowser,itmayincludetheSet-CookieHTTPheader.Whentheclientsendsarequesttotheserver,itsendsthecookieinformationbackwiththeCookieheader.Usingdocument.cookie,youcanalterthecookiesthebrowsersendstotheserver.Forexample,visitingcnn.comandtypingdocument.cookieintheconsolegivesyouthefollowingoutput:
>document.cookie;"mbox=check#true#1356053765|session#1356053704195-121286#1356055565;...
Thedocument.titlepropertyallowsyoutochangethetitleofthepagedisplayedinthebrowserwindow.Forexample,seethefollowingcode:
>document.title='Mytitle';"Mytitle"
Notethatthisdoesn'tchangethevalueofthe<title>element,butonlythedisplayinthebrowserwindow,soit'snotequivalenttodocument.querySelector('title').
Thedocument.referrerpropertytellsyoutheURLofthepreviouslyvisitedpage.ThisisthesamevaluethebrowsersendsintheRefererHTTPheaderwhenrequestingthepage.(NotethatRefererismisspelledintheHTTPheaders,butiscorrectinJavaScript'sdocument.referrer).Ifyou'vevisitedtheCNNpagebysearchingonYahoofirst,youcanseesomethinglikethefollowing:
>document.referrer;"http://search.yahoo.com/search?p=cnn&ei=UTF-8&fr=moz2"
Thedocument.domainpropertygivesyouaccesstothedomainnameofthecurrentlyloadedpage.Thisiscommonlyusedwhenyouneedtoperformso-calleddomainrelaxation.Imagineyourpageiswww.yahoo.com,andinsideit,youhaveaniframehostedonmusic.yahoo.comsubdomain.Thesearetwoseparatedomains,sothebrowser'ssecurityrestrictionswon'tallowthepageandtheiframetocommunicate.Toresolvethis,youcansetdocument.domainpropertyonbothpagestoyahoo.comandthey'llbeabletotalktoeachother.
Notethatyoucanonlysetthedomaintoalessspecificone,forexample,youcanchangewww.yahoo.comtoyahoo.com,butyoucannotchangeyahoo.comtowww.yahoo.com,oranyothernon-yahoodomain.Considerthefollowingcode:
>document.domain;"www.yahoo.com">document.domain='yahoo.com';"yahoo.com"
>document.domain='www.yahoo.com';Error:SecurityError:DOMException18>document.domain='www.example.org';Error:SecurityError:DOMException18
Previously,inthischapter,yousawthewindow.locationobject.Well,thesamefunctionalityisalsoavailableasdocument.locationobject:
>window.location===document.location;true
EventsImagine you are listening to a radio program and they announce, "Big event! Huge! Aliens havelandedonEarth!"Youmightthink,"Yeah,whatever";someotherlistenersmightthink"Theycomeinpeace";andsomemightthink,"We'reallgonnadie!".Similarly,thebrowserbroadcastsevents,andyourcodecanbenotifiedshoulditdecidetotuneinandlistentotheeventsastheyhappen.Someexampleeventsareasfollows:
TheuserclicksabuttonTheusertypesacharacterinaformfieldThepagefinishesloading
YoucanattachaJavaScriptfunctioncalledevent listeneroreventhandlertoaspecificeventandthebrowserwillinvokeyourfunctionassoonastheeventoccurs.Let'sseehowthisisdone.
InlineHTMLattributes
Addingspecificattributestoatagisthelaziestbuttheleastmaintainableway;takethefollowinglineofcodeasanexample:
<divonclick="alert('Ouch!')">click</div>
Inthiscase,whentheuserclickson<div>,theclickeventfiresandthestringofJavaScriptcodecontainedintheonclickattributeisexecuted.There'snoexplicitfunctionthatlistenstotheclickevent;however,behindthescenes,afunctionisstillcreated,anditcontainsthecodeyouspecifiedasavalueoftheonclickattribute.
ElementProperties
AnotherwaytohavesomecodeexecutedwhenaclickeventfiresistoassignafunctiontotheonclickpropertyofaDOMnodeelement.Forexample,takealookatthefollowingpieceofcode:
<divid="my-div">click</div><script>varmyelement=document.getElementById('my-div');myelement.onclick=function(){
alert('Ouch!');alert('Anddoubleouch!');};</script>
Thiswayisbetterbecauseithelpsyoukeepyour<div>tagcleanofanyJavaScriptcode.AlwayskeepinmindthatHTMLisforcontent,JavaScriptforbehavior,andCSSforformatting,andyoushouldkeepthesethreeseparateasmuchaspossible.
Thismethodhasthedrawbackthatyoucanattachonlyonefunctiontotheevent,asiftheradioprogramhasonlyonelistener.It'struethatyoucanhavealothappeninginsidethesamefunction,butthisisnotalwaysconvenient,asifalltheradiolistenersareinthesameroom.
DOMeventlisteners
ThebestwaytoworkwithbrowsereventsistousetheeventlistenerapproachoutlinedinDOMLevel2,whereyoucanhavemanyfunctionslisteningtoanevent.Whenaneventfires,allthefunctionsareexecuted.Allofthelistenersdon'tneedtoknowabouteachotherandcanworkindependently.Theycantuneinandoutatanytime,withoutaffectingtheotherlisteners.
Let'susethesamesimplemarkupfromtheprevioussection,whichisavailableforyoutoplaywithathttp://www.phpied.com/files/jsoop/ch7.html.Ithasthispieceofmarkup,whichisasfollows:
<pid="closer">final</p>
YourJavaScriptcodecanassignlistenerstotheclickeventusingtheaddEventListener()method.Let'sattachtwolistenersasfollows:
varmypara=document.getElementById('closer');mypara.addEventListener('click',function(){alert('Boo!');
}, false);mypara.addEventListener('click',console.log.bind(console),false);
Asyoucansee,addEventListeners
Capturingandbubbling
InthecallstoaddEventListener(),therewasathirdparameter-false. Let'sseewhatitisfor.
Let'ssayyouhavealinkinsideanunorderedlist,whichisasfollows:
<body><ul><li><ahref="http://phpied.com">myblog</a></li></ul></body>
Whenyouclickonthelink,you'reactuallyalsoclickingonthelistitem,<li>,the<ul>list,the<body>tag,andeventually,thedocumentasawhole.Thisiscalledevent propagation.Aclickonalinkcanalsobeseenasaclickonthedocument.Theprocessofpropagatinganeventcanbeimplementedinthetwofollowingways:
Eventcapturing:Thisclickhappensinthedocumentfirst,thenitpropagatesdowntothebody,thelist,thelistitem,andfinally,tothelinkEventbubbling:Thisclickhappensonthelinkandthenbubblesuptothedocument
DOMlevel2eventsspecificationsuggeststhattheeventspropagateinthreephases,namely,capturing,attarget,andbubbling.Thismeansthattheeventpropagatesfromthedocumenttothelink(target)andthenbubblesbackuptothedocument.TheeventobjectshaveaneventPhaseproperty,whichreflectsthecurrentphase:
Historically, IE and Netscape (working on their own and without a standard to follow)implementedtheexactopposites.IEimplementedonlybubblingandNetscapeonlycapturing.Today,longaftertheDOMspecification,modernbrowsersimplementall threephases.
Thepracticalimplicationsrelatedtotheeventpropagationareasfollows:
ThethirdparametertoaddEventListener()specifieswhetherornotcapturingshouldbeused.Inordertohaveyourcodemoreportableacrossbrowsers,it'sbettertoalwayssetthisparametertofalseandusebubblingonly.Youcanstopthepropagationoftheeventinyourlistenerssothatitstopsbubblingupandneverreachesthedocument.Todothis,youcancallthestopPropagation()methodoftheeventobject;thereisanexampleinthenextsection.Youcanalsouseeventdelegation.Ifyouhavetenbuttonsinside<div>,youcanalwaysattachteneventlisteners,oneforeachbutton.However,asmarterthingtodoistoattachonlyonelistenertothewrapping<div>andoncetheeventhappens,checkwhichbuttonwasthetargetoftheclick.
Asasidenote,thereisawaytouseeventcapturinginoldIEstoo(usingsetCapture()andreleaseCapture()methods)butonlyformouseevents.Capturinganyotherevents(keystrokeeventsforexample)isnotsupported.
Stoppropagation
Let'sseeanexampleofhowyoucanstoptheeventfrombubblingup.Goingbacktothetestdocument,thereisthispieceofcode:
<pid="closer">final</p>
Let'sdefineafunctionthathandlesclicksontheparagraph,asfollows:
functionparaHandler(){alert('clickedparagraph');}
Now,let'sattachthisfunctionasalistenertotheclickevent:
varpara=document.getElementById('closer');para.addEventListener('click',paraHandler,false);
Let'salsoattachlistenerstotheclickeventonthebody,thedocument,andthebrowserwindow:
document.body.addEventListener('click',function(){alert('clickedbody');},false);
document.addEventListener('click', function () {alert('clickeddoc');},false);window.addEventListener('click',function(){alert('clickedwindow');},false);
NotethattheDOMspecificationsdon'tsayanythingabouteventsonthewindow.Andwhywouldthey,asDOMdealswiththedocumentandnotthebrowser.Sobrowsersimplementwindoweventsinconsistently.
Now,ifyouclickontheparagraph,you'llseefouralertssaying:
clickedparagraphclickedbodyclickeddocclickedwindow
Thisillustrateshowthesamesingleclickeventpropagates(bubblesup)fromthetargetallthewayuptothewindow.
The opposite of addEventLister() is removeEventListener(), and it accepts exactly thesame parameters. Let's remove the listener attached to the paragraph by writing the following lineofcode:
>para.removeEventListener('click',paraHandler,false);
Ifyoutrynow,you'llseealertsonlyfortheclickeventonthebody,document,andwindow,butnotontheparagraph.
Now,let'sstopthepropagationoftheevent.Thefunctionyouaddasalistenerreceivestheeventobjectasaparameter,andyoucancallthestopPropagation()methodofthateventobjectasfollows:
functionparaHandler(e){alert('clickedparagraph');e.stopPropagation();}
Addingthemodifiedlistenerisdoneasfollows:
para.addEventListener('click',paraHandler,false);
Now,whenyouclickontheparagraph,youwillseeonlyonealertbecausetheeventdoesn'tbubbleuptothebody,thedocument,orthewindow.
Notethatwhenyouremovealistener,youhavetopassapointertothesamefunctionyoupreviouslyattached.Otherwise,doingthefollowingdoesnotworkbecausethesecondargumentisanewfunction,notthesameyoupassedwhenaddingtheeventlistener,evenifthebodyisexactlythesame.Considerthefollowingcode:
document.body.removeEventListener('click',function(){alert('clickedbody');},false);//doesNOTremovethehandler
Preventdefaultbehavior
Somebrowsereventshaveapredefinedbehavior.Forexample,clickingalinkcausesthebrowsertonavigatetoanotherpage.Youcanattachlistenerstoclicksonalink,andyoucanalsodisablethedefaultbehaviorbycallingthepreventDefault()methodontheeventobject.
Let'sseehowyoucanannoyyourvisitorsbyasking"Areyousureyouwanttofollowthislink?"everytimetheyclickalink?IftheuserclicksonCancel(causingconfirm()toreturnfalse),thepreventDefault()methodiscalled,whichisshownasfollows:
//alllinksvarall_links=document.getElementsByTagName('a');for(vari=0;i<all_links.length;i++){//loopalllinks
all_links[i].addEventListener('click',//eventtypefunction(e){//handlerif(!confirm('Sureyouwanttofollowthislink?')){e.preventDefault();}},false//don'tusecapturing);}
Notethatnotalleventsallowyoutopreventthedefaultbehavior.Mostdo,butifyouwanttobesure,youcancheckthecancellablepropertyoftheeventobject.
Cross-browsereventlisteners
Asyoualreadyknow,mostmodernbrowsersalmostfullyimplementtheDOMLevel1specification.However,theeventswerenotstandardizeduntilDOM2.Asaresult,therearequiteafewdifferencesinhowIE,beforeversion9,implementsthisfunctionalitycomparedtomodernbrowsers.
CheckoutanexamplethatcausesnodeNameofaclickedelement(thetargetelement)tobewrittentotheconsole:
document.addEventListener('click',function(e){console.log(e.target.nodeName);},false);
Now,let'stakealookathowIEisdifferent:
InIE,there'snoaddEventListener()method;although,sinceIEVersion5,thereisanequivalentattachEvent()method.Forearlierversions,youronlychoiceisaccessingthepropertydirectly,suchasonclick.TheclickeventbecomesonclickwhenusingattachEvent().Ifyoulistentoeventstheoldfashionedway (forexample,bysettingafunctionvaluetotheonclickproperty),whenthecallbackfunctionisinvoked,itdoesn'tgetaneventobjectpassedasaparameter.However,regardlessofhowyouattachthelistenerinIE,thereisalwaysaglobalobjectwindow.eventthatpointstothelatestevent.InIE,theeventobjectdoesn'tgetatargetattributetellingyoutheelementonwhichtheeventfired,butitdoeshaveanequivalentpropertycalledsrcElement.Asmentionedearlier,eventcapturingdoesn'tapplytoallevents,soonlybubblingshouldbeused.There'snostopPropagation()method,butyoucansettheIE-onlycancelBubblepropertytotrue.There'snopreventDefault()method,butyoucansettheIE-onlyreturnValuepropertytofalse.Tostoplisteningtoanevent,insteadofremoveEventListener()inIE,you'llneeddetachEvent().
So,here'stherevisedversionofthepreviouscodethatworksacrossbrowsers:
functioncallback(evt){//prepworkevt=evt||window.event;vartarget=evt.target||evt.srcElement;
//actualcallbackworkconsole.log(target.nodeName);}
//startlisteningforclickeventsif(document.addEventListener){//Modernbrowsersdocument.addEventListener('click',callback,false);}elseif(document.attachEvent){//oldIEdocument.attachEvent('onclick',callback);}else{document.onclick=callback;//ancient}
Typesofevents
Nowyouknowhowtohandlecross-browserevents.However,alloftheprecedingexamplesusedonlyclickevents.Whatothereventsarehappeningoutthere?Asyoucanprobablyguess,differentbrowsersprovidedifferentevents.Thereisasubsetofcross-browserevents,andsomebrowser-specificones.Forafulllistofevents,youshouldconsultthebrowser'sdocumentation,buthere'saselectionofcross-browserevents:
Mouseeventsmouseup,mousedown,click(thesequenceismousedown-up-click),dblclickmouseover(mouseisoveranelement),mouseout(mousewasoveranelementbutleftit),mousemove
Keyboardeventskeydown,keypress,keyup(occurinthissequence)
Loading/windoweventsload(animageorapageandallofitscomponentsaredoneloading),unload(userleavesthepage),beforeunload(thescriptcanprovidetheuserwithanoptiontostoptheunload)abort(userstopsloadingthepageoranimageinIE),error(aJavaScripterror,alsowhenanimagecannotbeloadedinIE)resize(thebrowserwindowisresized),scroll(thepageisscrolled),contextmenu(theright-clickmenuappears)
Form eventsfocus(enteraformfield),blur(leavetheformfield)change(leaveafieldafterthevaluehaschanged),select(selecttextinatextfield)reset(wipeoutalluserinput),submit(sendtheform)
Additionally,modernbrowsersprovidedragevents(dragstart,dragend,drop,andamongothers)andtouchdevicesprovidetouchstart,touchmove,andtouchend.
Thisconcludesthediscussionofevents.Refertotheexercisesectionattheendofthischapterforalittlechallengeofcreatingyourowneventutilitytohandlecross-browserevents.
XMLHttpRequestXMLHttpRequest() is an object (a constructor function) that allows you to send HTTP requestsfrom JavaScript. Historically, XHR (XMLHttpRequest) was introduced in IE and wasimplemented as an ActiveX object. Starting with IE7, it's a native browser object, the same wayasit'sintheotherbrowsers.Thecommonimplementationofthisobjectacrossbrowsersgavebirthtotheso-calledAjaxapplications,whereit'snolongernecessarytorefreshthewholepageeverytimeyouneednewcontent.WithJavaScript,youcanmakeanHTTPrequesttotheserver,gettheresponse,andupdateonlyapartofthepage.Thisway,youcanbuildmuchmoreresponsiveanddesktop-likewebpages.
AjaxstandsforAsynchronousJavaScriptandXML:
Asynchronousbecause,aftersendinganHTTPrequest,yourcodedoesn'tneedtowaitfortheresponse;however,itcandootherstuffandbenotified,throughanevent,whentheresponsearrives.JavaScriptbecauseit'sobviousthatXHRobjectsarecreatedwithJavaScript.XMLbecauseinitiallydevelopersweremakingHTTPrequestsforXMLdocumentsandwereusingthedatacontainedinthemtoupdatethepage.Thisisnolongeracommonpractice,though,asyoucanrequestdatainplaintext,inthemuchmoreconvenientJSONformat,orsimplyasHTMLreadytobeinsertedintothepage.
TherearetwostepstousingtheXMLHttpRequestobject,whichareasfollows:
Sendtherequest:ThisincludescreatinganXMLHttpRequestobjectandattachinganeventlistenerProcesstheresponse:Thishappenswhenyoureventlistenergetsnotifiedthattheresponsehasarrived,andyourcodegetsbusydoingsomethingamazingwiththeresponse
Sendingtherequest
Inordertocreateanobject,youwillsimplyusethefollowingcode(let'sdealwithbrowserinconsistenciesinjustabit):
varxhr=newXMLHttpRequest();
Thenextthingistoattachaneventlistenertothereadystatechangeeventfiredbytheobject:
xhr.onreadystatechange=myCallback;
Then,youwillneedtocalltheopen()method,asfollows:
xhr.open('GET','somefile.txt',true);
ThefirstparameterspecifiesthetypeofHTTPrequest,suchasGET,POST,HEAD,andsoon.GETandPOSTarethemostcommonones.UseGETwhenyoudon'tneedtosendmuchdatawiththerequestandyourrequestdoesn'tmodify(write)dataontheserver,otherwise,usePOST.ThesecondparameteristheURLyouarerequesting.Inthisexample,it'sthetextfilesomefile.txtlocatedinthesamedirectoryasthepage.ThelastparameterisaBooleanspecifyingwhethertherequestisasynchronous(true,alwayspreferthis)ornot(false,blocksalltheJavaScriptexecutionandwaitsuntiltheresponsearrives).
Thelaststepistofireofftherequest,whichisdoneasfollows:
xhr.send('');
Thesend()methodacceptsanydatayouwanttosendwiththerequest.ForGETrequests,thisisanemptystringbecausethedataisintheURL.ForPOSTrequest,it'saquerystringinthekey=value&key2=value2form.
Atthispoint,therequestissentandyourcodeandtheusercanmoveontoothertasks.Thecallbackfunction,myCallback,willbeinvokedwhentheresponsecomesbackfromtheserver.
Processingtheresponse
Alistenerisattachedtothereadystatechangeevent.So,whatexactlyisthereadystateandhowdoesitchange?
ThereisapropertyoftheXHRobjectcalledreadyState.Everytimeitchanges,thereadystatechangeeventfires.ThepossiblevaluesofthereadyStatepropertyareasfollows:
0-uninitialized1-loading2-loaded3-interactive4-complete
WhenreadyStategetsthevalueof4,itmeanstheresponseisbackandreadytobeprocessed.InmyCallback,afteryoumakesurereadyStateis4,theotherthingtocheckisthestatuscodeoftheHTTPrequest.Youmighthaverequestedanon-existingURL,forexample,andgota404(Filenotfound)statuscode.Theinterestingcode isthe200(OK)code,somyCallbackshouldcheckforthisvalue.ThestatuscodeisavailableinthestatuspropertyoftheXHRobject.
Oncexhr.readyStateis4andxhr.statusis200,youcanaccessthecontentsoftherequestedURLusingthexhr.responseTextproperty.Let'sseehowmyCallbackcanbeimplementedtosimplyalert()thecontentsoftherequestedURL:
functionmyCallback(){
if(xhr.readyState<4){return;//notreadyyet}if(xhr.status!==200){alert('Error!');//theHTTPstatuscodeisnotOKreturn;}
//allisfine,dotheworkalert(xhr.responseText);}
Onceyou'vereceivedthenewcontentyourequested,youcanaddittothepage,useitforsomecalculations,orforanyotherpurposeyoufindsuitable.
Overall, this two-step process (send request and process response) is the core of the wholeXHR/Ajaxfunctionality.Nowthatyouknowthebasics,youcanmoveontobuildingthenextGmail.Ohyes,let'stakealookatsomeminorbrowserinconsistencies.
CreatingXMLHttpRequestobjectsinIEpriortoVersion7
InInternetExplorer,priortoversion7,theXMLHttpRequestobjectwasanActiveXobject,socreatinganXHRinstanceisalittledifferent.Itgoesasfollows:
varxhr=newActiveXObject('MSXML2.XMLHTTP.3.0');
MSXML2.XMLHTTP.3.0istheidentifieroftheobjectyouwanttocreate.ThereareseveralversionsoftheXMLHttpRequestobject,andifyourpagevisitordoesn'thavethelatestoneinstalled,youcantrytwoolderonesbeforeyougiveup.
Forafully-cross-browsersolution,youshouldfirsttesttoseeiftheuser'sbrowsersupportsXMLHttpRequestasanativeobject,andifnot,trytheIEway.Therefore, thewholeprocessofcreatinganXHRinstancecouldbelikethefollowing:
varids=['MSXML2.XMLHTTP.3.0','MSXML2.XMLHTTP','Microsoft.XMLHTTP'];varxhr;if(XMLHttpRequest){
xhr = new XMLHttpRequest();}else{//IE:trytofindanActiveXobjecttousefor(vari=0;i<ids.length;i++){try{xhr=newActiveXObject(ids[i]);break;}catch(e){}}}
Whatisthisdoing?TheidsarraycontainsalistofActiveXprogramIDstotry.ThexhrvariablepointstothenewXHRobject.ThecodefirstcheckstoseeifXMLHttpRequestexists.Ifso,thismeansthatthebrowsersupportsXMLHttpRequest()natively,sothebrowserisrelativelymodern.Ifitisnot,thecodeloopsthroughidstryingtocreateanobject.Thecatch(e)blockquietlyignoresfailuresandtheloopcontinues.Assoonasanxhrobjectiscreated,youbreakoutoftheloop.
Asyoucansee,thisisquiteabitofcode,soit'sbesttoabstractitintoafunction.Actually,oneoftheexercisesattheendofthechapterpromptsyoutocreateyourownAjaxutility.
AisforAsynchronous
NowyouknowhowtocreateanXHRobject,giveitaURLandhandletheresponsetotherequest.Whathappenswhenyousendtworequestsasynchronously?Whatiftheresponsetothesecondrequestcomesbeforethefirst?
Intheprecedingexample,theXHRobjectwasglobalandmyCallbackwasrelyingonthepresenceofthisglobalobjectinordertoaccessitsreadyState,status,andresponseTextproperties.Anotherway,whichpreventsyoufromrelyingonglobalvariables,istowrapthecallbackinaclosure.Let'sseehow:
varxhr=newXMLHttpRequest();
xhr.onreadystatechange=(function(myxhr){returnfunction(){myCallback(myxhr);};}(xhr));
xhr.open('GET','somefile.txt',true);xhr.send('');
Inthiscase,myCallback()receivestheXHRobjectasaparameterandwillnotgolookingforitintheglobalspace.This alsomeansthatatthetimetheresponseisreceived,theoriginalxhrmightbereusedforasecondrequest.Theclosurekeepspointingtotheoriginalobject.
XisforXML
AlthoughthesedaysJSON(discussedinthenextchapter)ispreferredoverXMLasadatatransferformat,XMLisstillanoption.InadditiontotheresponseTextproperty,theXHRobjectsalsohaveanotherpropertycalledresponseXML.WhenyousendanHTTPrequestforanXMLdocument,responseXMLpointstoanXMLDOMdocumentobject.Toworkwiththisdocument,youcanuseallofthecoreDOMmethodsdiscussedpreviouslyinthischapter,suchasgetElementsByTagName(),getElementById(),andsoon.
Anexample
Let'swrapupthedifferentXHRtopicswithanexample.Youcanvisitthepagelocatedathttp://www.phpied.com/files/jsoop/xhr.htmltoworkontheexampleyourself.
Themainpage,xhr.html,isasimplestaticpagethatcontainsnothingbutthree<div>tags,whichareasfollows:
<divid="text">Textwillbehere</div><divid="html">HTMLwillbehere</div><divid="xml">XMLwillbehere</div>
Usingtheconsole,youcanwritecodethatrequeststhreefilesandloadstheirrespectivecontentsinto each <div>.
Thethreefilestoloadareasfollows:
content.txt:ThisisasimpletextfilecontainingthetextIamatextfilecontent.html:ThisisafilecontainingHTMLcodeIam<strong>formatted</strong><em>HTML</em>
content.xml:ThisisanXMLfilecontainingthefollowingcode:
<?xmlversion="1.0"?><root>I'mXMLdata.</root>
Allofthefilesarestoredinthesamedirectoryasxhr.html.
Note
Forsecurityreasons,youcanonlyusetheoriginalXMLHttpRequesttorequestfilesthatareonthesamedomain.However,modernbrowserssupportXHR2,whichletsyoumakecross-domainrequests,providedthattheappropriateAccess-Control-Allow-OriginHTTPheaderisinplace.
First,let'screateafunctiontoabstracttherequest/responsepart:
function request(url, callback) {varxhr=newXMLHttpRequest();xhr.onreadystatechange=(function(myxhr){returnfunction(){if(myxhr.readyState===4&&myxhr.status===200){callback(myxhr);}};}(xhr));xhr.open('GET',url,true);xhr.send('');
}
ThisfunctionacceptsaURLtorequestandacallbackfunctiontocalloncetheresponsearrives.Let'scallthefunctionthreetimes,onceforeachfile,asfollows:
request('http://www.phpied.com/files/jsoop/content.txt',function(o){document.getElementById('text').innerHTML=o.responseText;});request('http://www.phpied.com/files/jsoop/content.html',
function (o) {document.getElementById('html').innerHTML=o.responseText;});request('http://www.phpied.com/files/jsoop/content.xml',function(o){document.getElementById('xml').innerHTML=o.responseXML.getElementsByTagName('root')[0].firstChild.nodeValue;});
Thecallbackfunctionsaredefinedinline.Thefirsttwoareidentical.TheyjustreplacetheHTMLofthecorresponding<div>withthecontentsoftherequestedfile.ThethirdoneisalittledifferentasitdealswiththeXMLdocument.First,youwillaccesstheXMLDOMobjectaso.responseXML.Then,usinggetElementsByTagName(),youwillgetalistofallthe<root>tags(thereisonlyone).ThefirstChildof<root>isatextnodeandnodeValueisthetextcontainedinit(I'mXMLdata).Then,justreplacetheHTMLof<divid="xml">withthenewcontent.Theresultisshowninthefollowingscreenshot:
WhenworkingwiththeXMLdocument,youcanalsouseo.responseXML.documentElementtogettothe<root>elementinsteadofo.responseXML.getElementsByTagName('root')[0].RememberthatdocumentElementgivesyoutherootnodeofanXMLdocument.TherootinHTMLdocumentsisalwaysthe<html>tag.
ExercisesIn the previous chapters, the solutions to the exercises could be found in the text of the chapter.Thistime,someoftheexercisesrequireyoutodosomemorereading,orexperimentation,outsidethisbook.
1. BOM:AsaBOMexercise,trycodingsomethingwrong,obtrusive,user-unfriendly,andallinall,veryWeb1.0,theshakingbrowserwindow.Tryimplementingcodethatopensa200x200popupwindowandthenresizesitslowlyandgraduallyto400x400.Next,movethewindowaroundasifthere'sanearthquake.Allyou'llneedisoneofthemove*()functions,oneormorecallstosetInterval(),andmaybeonetosetTimeout()/clearInterval()tostopthewholething.Or,here'saneasierone-printthecurrentdate/timeindocument.titleandupdateiteverysecond,likeaclock.
2. DOM:ImplementwalkDOM()differently.Also,makeitacceptacallbackfunctioninsteadofhardcodingconsole.log().RemovingcontentwithinnerHTMLiseasy(document.body.innerHTML=''),butnotalwaysbest.Theproblemwillbewhenthereareeventlistenersattachedtotheremovedelements;theywon'tberemovedinIE,causingthebrowsertoleakmemorybecause it stores references to something that doesn't exist. Implement a general-purposefunctionthatdeletesDOMnodes,butremovesanyeventlistenersfirst.Youcanloopthroughtheattributesofanodeandcheckifthevalueisafunction.Ifitis,it'smostlikelyanattributelikeonclick.Youneedtosetittonullbeforeremovingtheelementfromthetree.Createafunctioncalledinclude()thatincludesexternalscriptsondemand.Thismeansyouneedtocreateanew<script>tagdynamically,setitssrcattribute,andappendtothedocument's<head>.Testitbyusingthefollowingcode:
>include('somescript.js');
3. Events:Createaneventutility(object)calledmyevent,whichhasthefollowingmethodsworkingcross-browser:
TheaddListener(element,event_name,callback),whereelementcanalsobeanarrayofelementsremoveListener(element,event_name,callback)getEvent(event)justtocheckforawindow.eventforolderversionsofIEgetTarget(event)stopPropagation(event)preventDefault(event)
Usageexampleisasfollows:
functionmyCallback(e){
e=myevent.getEvent(e);alert(myevent.getTarget(e).href);myevent.stopPropagation(e);myevent.preventDefault(e);}myevent.addListener(document.links,'click',myCallback);
Theresultoftheexamplecodeshouldbethatallofthelinksinthedocumentleadnowhere,butonlyalertthehrefattribute.Createanabsolutelypositioned<div>,sayatx=100px,y=100px.Writethecodetobeabletomovedivaroundthepage usingthearrowkeysortheJ(left),K(right),M(down),andI(up)keys.Reuseyourowneventutilityfrom3.1.
4. XMLHttpRequest:CreateyourownXHRutility(object)calledajax.Forexample,takealookatthefollowingcode:
functionmyCallback(xhr){alert(xhr.responseText);}ajax.request('somefile.txt','get',myCallback);ajax.request('script.php','post',myCallback,'first=John&last=Smith');
SummaryYou learned quite a bit in this chapter. You learned the following cross-browser BOM objects:
Propertiesoftheglobalwindowobject,suchasnavigator,location,history,frames,screen
MethodssuchassetInterval()andsetTimeout();alert(),confirm()andprompt();moveTo/By()andresizeTo/By()
Then,youlearnedabouttheDOM,anAPItorepresentanHTMLorXMLdocumentasatreestructure,whereeachtagortextisanodeonthetree.Youalsolearnedhowtoperformthefollowingactions:
Accessingnodes:Usingparent/childrelationshipproperties,suchasparentNode,childNodes,firstChild,lastChild,nextSibling,andpreviousSiblingUsinggetElementsById(),getElementsByTagName(),getElementsByName(),andquerySelectorAll()
Modifyingnodes:UsinginnerHTMLorinnerText/textContentUsingnodeValueorsetAttribute(),orjustusingattributesasobjectproperties
RemovingnodeswithremoveChild()orreplaceChild()AddingnewoneswithappendChild(),cloneNode(),andinsertBefore()
YoualsolearnedthefollowingDOM0(pre-standardization)properties,portedtoDOMLevel1:
Collections,suchasdocument.forms,images,links,anchors,applets.UsingthesearediscouragedasDOM1hasthemuchmoreflexiblegetElementsByTagName()method.Thedocument.bodyelement,whichgivesyouconvenientaccessto<body>.Thedocument.title,cookie,referrer,anddomain.
Next,youlearnedhowthebrowserbroadcastseventsthatyoucanlistento.It'snotstraightforwardtodothisinacross-browsermanner,butit'spossible.Eventsbubbleup,soyoucanuseeventdelegationtolistentoeventsmoreglobally.Youcanalsostopthepropagationofeventsandinterferewiththedefaultbrowserbehavior.
Finally,youlearnedabouttheXMLHttpRequestobjectthatallowsyoutobuildresponsivewebpagesthatdothefollowingtasks:
MakeHTTPrequeststotheservertogetpiecesofdataProcesstheresponsetoupdateportionsofthepage
Chapter 11. Coding and Design PatternsNow that you know all about the objects in JavaScript, mastered prototypes and inheritance, andseensomepracticalexamplesofusingbrowser-specificobjects,let'smoveforward,orrather,movealevelup.Let'stakealookatsomecommonJavaScriptpatterns.
Butfirst,what'sapattern?Inshort,apatternisagoodsolutiontoacommonproblem.Codifyingthesolutionintoapatternmakesitrepeatableaswell.
Sometimes,whenyou'refacinganewprogrammingproblem,youmayrecognizerightawaythatyou'vepreviouslysolvedanother,suspiciouslysimilarproblem.Insuchcases,it'sworthisolatingthisclassofproblemsandsearchingforacommonsolution.Apatternisaprovenandreusablesolution(oranapproach toasolution)toaclassofproblems.
Therearecaseswhereapatternisnothingmorethananideaoraname.Sometimes,justusinganame helps you think more clearly about a problem. Also, when working with other developers inateam,it'smucheasiertocommunicatewheneverybodyusesthesameterminologytodiscussaproblemorasolution.
Othertimes,youmaycomeacrossauniqueproblemthatdoesn'tlooklike anythingyou'veseenbeforeanddoesn'treadilyfitintoaknownpattern.Blindlyapplyingapatternjustforthesakeofusingapattern,isnotagoodidea.It'spreferabletonotuseanyknownpatternthantotrytotweakyourproblemsothatitfitsanexistingsolution.
Thischaptertalksabouttwotypesofpatterns,whichareasfollows:
Codingpatterns:ThesearemostlyJavaScript-specificbestpracticesDesignpatterns:Thesearelanguage-independentpatterns,popularizedbythefamousGangofFourbook
Coding patternsLet's start with some patterns that reflect JavaScript's unique features. Some patterns aim to helpyouorganizeyourcode,forexample,namespacing;othersarerelatedtoimprovingperformance,suchaslazydefinitionsandinit-timebranching;andsomemakeupformissingfeatures,suchasprivateproperties.Thepatternsdiscussedinthissectionincludethefollowingtopics:
SeparatingbehaviorNamespacesInit-timebranchingLazydefinitionConfigurationobjectsPrivatevariablesandmethodsPrivileged methodsPrivatefunctionsaspublicmethodsImmediatefunctionsChainingJSON
Separatingbehavior
Asdiscussedpreviously,thethreebuildingblocksofawebpageareasfollows:
Content (HTML)Presentation(CSS)Behavior(JavaScript)
Content
HTMListhecontentofthewebpage,theactualtext.Ideally,thecontentshouldbemarked-upusingtheleastamountof HTMLtagsthatsufficientlydescribethesemanticmeaningofthatcontent.Forexample,ifyou'reworkingonanavigationmenu,it'sagoodideatousethe<ul>and<li>tagsasanavigationmenuisinessence,justalistoflinks.
Yourcontent(HTML)shouldbefreefromanyformattingelements.VisualformattingbelongstothepresentationlayerandshouldbeachievedthroughtheuseofCSS(CascadingStyleSheets).Thismeansthefollowing:
ThestyleattributeofHTMLtagsshouldnotbeused,ifpossible.PresentationalHTMLtagssuchas<font>shouldnotbeusedatall.Tagsshouldbeusedfortheirsemanticmeaning,notbecauseofhowbrowsersrenderthembydefault.Forinstance,developerssometimesusea<div>tagwherea<p>wouldbemoreappropriate.It'salsofavorabletouse<strong>and<em>insteadof<b>and<i>asthelatterdescribethevisualpresentationratherthanthemeaning.
Presentation
Agoodapproachtokeeppresentationoutofthecontentistoresetornullifyallbrowserdefaults,forexample,usingreset.cssfromtheYahoo!UIlibrary.Thisway,thebrowser'sdefaultrenderingwon'tdistractyoufromconsciouslythinkingaboutthepropersemantictagstouse.
Behavior
Thethirdcomponentofawebpageisthebehavior.Behaviorshouldbekeptseparatefromboththecontentandthepresentation.ItisusuallyaddedbyusingJavaScriptthatisisolatedto<script>tags,andpreferablycontainedinexternalfiles.Thismeansnotusinganyinlineattributes,suchasonclick,onmouseover,andsoon.Instead,youcanusetheaddEventListener/attachEventmethodsfromthepreviouschapter.
Thebeststrategytoseparatebehaviorfromcontentisasfollows:
Minimizethenumberof<script>tagsAvoidinlineeventhandlersDonotuseCSSexpressionsTowardtheendofyourcontent,whenyouarereadytoclosethe<body>tag,insertasingle
external.jsfile
Exampleofseparatingbehavior
Let'ssayyouhaveasearchformonapage,andyouwanttovalidatetheformwithJavaScript.So,yougoaheadandkeeptheformtagsfreefromanyJavaScript,andthenimmediatelybeforeclosingthe</body>tag, youinserta<script>tagthatlinkstoanexternalfile,asfollows:
<body><formid="myform"method="post"action="server.php"><fieldset><legend>Search</legend><inputname="search"id="search"type="text"/><inputtype="submit"/></fieldset></form><scriptsrc="behaviors.js"></script></body>
Inbehaviors.jsyouattachaneventlistenertothesubmitevent.Inyourlistener,youcanchecktoseeifthetextinputfieldwasleftblankand,ifso,stoptheformfrombeingsubmitted.Thisway,youwillsavearoundtripbetweentheserverandtheclientandmaketheapplicationimmediatelyresponsive.
Thecontentofbehaviors.jsisgiveninthefollowingcode.Itassumesthatyou'vecreatedyourmyeventutilityfromtheexerciseattheendofthepreviouschapter:
//initmyevent.addListener('myform','submit',function(e){//noneedtopropagatefurthere=myevent.getEvent(e);myevent.stopPropagation(e);//validatevarel=document.getElementById('search');if(!el.value){//toobad,fieldisemptymyevent.preventDefault(e);//preventtheformsubmissionalert('Pleaseenterasearchstring');}});
AsynchronousJavaScriptloading
You noticed how the script was loaded at the end of the HTML, right before closing the body. ThereasonisthatJavaScript blockstheDOMconstructionofthepage,andinsomebrowsers,evendownloadsoftheothercomponentsthatfollow.Bymovingthescriptstothebottomofthepage,youensurethatthescriptisoutoftheway,andwhenitarrives,itsimplyenhancesthealready
usablepage.
AnotherwaytopreventexternalJavaScriptfilesfromblockingthepageistoloadthemasynchronously. This way you can start loading them earlier. HTML5 has the defer attribute forthis purpose. Consider the following line of code:
<scriptdefersrc="behaviors.js"></script>
Unfortunately,thedefer attributeisnotsupportedbyolderbrowsers,butluckily,thereisasolutionthatworksacrossbrowsers,oldandnew.ThesolutionistocreateascriptnodedynamicallyandappendittotheDOM.Inotherwords,youcanuseabitofinlineJavaScripttoloadtheexternalJavaScriptfile.Youcanhavethisscriptloadersnippetatthetopofyourdocumentsothatthedownloadhasanearlystart. Takealookatthefollowingcodeexample:
...<head><script>(function(){vars=document.createElement('script');s.src='behaviors.js';
document.getElementsByTagName('head')[0].appendChild(s);}());</script></head>...
Namespaces
Globalvariablesshouldbeavoidedinordertoreducethepossibilityofvariablenamingcollisions.Youcanminimizethenumberofglobalsbynamespacingyourvariablesandfunctions.Theideaissimple,youwillcreateonlyoneglobalobject,andallyourothervariablesandfunctionsbecomepropertiesofthatobject.
AnObjectasanamespace
Let'screateaglobalobjectcalledMYAPP:
//globalnamespacevarMYAPP=MYAPP||{};
Now,insteadofhavingaglobalmyeventutility(fromthepreviouschapter),youcanhaveitasaneventpropertyoftheMYAPPobject,asfollows:
//sub-objectMYAPP.event={};
Addingthemethodstotheeventutilityisstillthesame.Considerthefollowingexample:
//objecttogetherwiththemethoddeclarationsMYAPP.event={addListener:function(el,type,fn){//..dothething},removeListener:function(el,type,fn){//...},getEvent:function(e){//...}//...othermethodsorproperties};
Namespacedconstructors
Usinganamespacedoesn'tpreventyoufromcreatingconstructorfunctions.HereishowyoucanhaveaDOMutilitythathasanElementconstructor,whichallowsyoutocreateDOMelementseasily:
MYAPP.dom={};MYAPP.dom.Element=function(type,properties){vartmp=document.createElement(type);for(variinproperties){if(properties.hasOwnProperty(i)){tmp.setAttribute(i,properties[i]);}}
returntmp;};
Similarly,youcanhaveaTextconstructortocreatetextnodes.Considerthefollowingcodeexample:
MYAPP.dom.Text=function(txt){returndocument.createTextNode(txt);};
Usingtheconstructorstocreatealinkatthebottomofapagecanbedoneasfollows:
varlink=newMYAPP.dom.Element('a',{href:'http://phpied.com',target:'_blank'});vartext=newMYAPP.dom.Text('clickme');link.appendChild(text);document.body.appendChild(link);
Anamespace()method
Youcancreateanamespaceutilitythatmakesyourlifeeasiersothatyoucanusemoreconvenientsyntaxasfollows:
MYAPP.namespace('dom.style');
Insteadofthemoreverbosesyntaxasfollows:
MYAPP.dom={};MYAPP.dom.style={};
Here's how you can create such a namespace() method. First, you will create an array bysplitting the input string using the period (.) as a separator. Then, for every element in the newarray, you will add a property to your global object, if one doesn't already exist, as follows:
varMYAPP={};MYAPP.namespace=function(name){varparts=name.split('.');varcurrent=MYAPP;for(vari=0;i<parts.length;i++){if(!current[parts[i]]){current[parts[i]]={};}current=current[parts[i]];}};
Testingthenewmethodisdoneasfollows:
MYAPP.namespace('event');MYAPP.namespace('dom.style');
Theresultoftheprecedingcodeisthesameasifyoudidthefollowing:
varMYAPP={event:{},dom:{style:{}}};
Init-timebranching
Inthepreviouschapter,younoticedthatsometimes,differentbrowsershavedifferentimplementationsforthesameorsimilarfunctionalities.Insuchcases,youwillneedtobranchyourcode,dependingon what'ssupportedbythebrowsercurrentlyexecutingyourscript.Dependingonyourprogram,thisbranchingcanhappenfartoooftenand,asaresult,mayslowdownthescriptexecution.
Youcanmitigatethisproblembybranchingsomepartsofthecodeduringinitialization,whenthescriptloads,ratherthanduringruntime.Buildingupontheabilitytodefinefunctionsdynamically,youcanbranchanddefinethesamefunctionwith adifferentbody,dependingonthebrowser.Let'sseehow.
First,let'sdefineanamespaceandplaceholdermethodfortheeventutility:
varMYAPP={};MYAPP.event={addListener:null,removeListener:null};
Atthispoint,themethodstoaddorremovealistenerarenotimplemented.Basedontheresultsfromfeaturesniffing,thesemethodscanbedefineddifferently,asfollows:
if(window.addEventListener){MYAPP.event.addListener=function(el,type,fn){el.addEventListener(type,fn,false);};MYAPP.event.removeListener=function(el,type,fn){el.removeEventListener(type,fn,false);
};}elseif(document.attachEvent){//IEMYAPP.event.addListener=function(el,type,fn){el.attachEvent('on'+type,fn);};MYAPP.event.removeListener=function(el,type,fn){el.detachEvent('on'+type,fn);};}else{//olderbrowsersMYAPP.event.addListener=function(el,type,fn){el['on'+type]=fn;};MYAPP.event.removeListener=function(el,type){el['on'+type]=null;};}
Afterthisscriptexecutes,youhavetheaddListener()andremoveListener()methodsdefinedinabrowser-dependentway.Now,everytimeyouinvokeoneofthesemethods,there'snomore
feature-sniffing,anditresultsinlessworkandfasterexecution.
Onethingtowatchoutforwhensniffingfeaturesisnottoassumetoomuchaftercheckingforonefeature. In the previous example, this rule is broken because the code only checks foraddEventListenersupport,butthendefinesbothaddListener()andremoveListener().Inthiscase,it'sprobablysafetoassumethatifabrowserimplementsaddEventListener(),italsoimplementsremoveEventListener().However,imaginewhathappensifabrowserimplementsstopPropagation()butnotpreventDefault(),andyouhaven'tcheckedfortheseindividually.YouhaveassumedthatbecauseaddEventListener()isnotdefined,thebrowsermustbeanoldIEandwriteyourcodeusingyourknowledgeandassumptionsofhowIEworks.Rememberthatallofyourknowledgeisbasedonthewayacertainbrowserworkstoday,butnotnecessarilythewayitwillworktomorrow.So,toavoidmanyrewritesofyourcodeasnewbrowserversionsareshipped,it'sbesttoindividuallycheckforfeaturesyouintendtouseanddon'tgeneralizeonwhatacertainbrowsersupports.
Lazydefinition
Thelazydefinitionpatternissimilartothepreviousinit-timebranchingpattern.Thedifferenceisthatthebranchinghappensonlywhenthefunctioniscalledforthefirsttime.Whenthefunctioniscalled,itredefinesitself withthebestimplementation.Unliketheinit-timebranching,wheretheifhappensonce,duringloading,hereitmaynothappenatall,incaseswhenthefunctionisnevercalled.Thelazydefinitionalsomakestheinitializationprocesslighterasthere'snoinit-timebranchingworktobedone.
Let'sseeanexamplethatillustratesthisviathedefinitionofanaddListener()function.Thefunctionisfirstdefinedwithagenericbody.Itcheckswhichfunctionalityissupportedbythebrowserwhenit'scalledforthefirsttimeandthenredefinesitselfusingthemostsuitableimplementation.Attheendofthefirstcall,thefunctioncallsitself,sothattheactualeventattachingisperformed.Thenexttimeyoucallthesamefunction,itwillbedefinedwithitsnewbodyandbereadyforuse,sonofurtherbranchingisnecessary.Thefollowingisthecodesnippet:
varMYAPP={};MYAPP.myevent={addListener:function(el,type,fn){if(el.addEventListener){MYAPP.myevent.addListener=function(el,type,fn){el.addEventListener(type,fn,false);};}elseif(el.attachEvent){MYAPP.myevent.addListener=function(el,type,fn){el.attachEvent('on'+type,fn);};}else{MYAPP.myevent.addListener=function(el,type,fn){el['on'+type]=fn;
};}MYAPP.myevent.addListener(el,type,fn);}};
Configurationobject
Thispatternisconvenientwhenyouhaveafunctionormethodthatacceptsalotofoptionalparameters.It'suptoyoutodecidehowmanyconstitutesalot.Butgenerally,afunctionwithmorethanthreeparametersisnotconvenienttocall,becauseyouhavetoremembertheorderoftheparameters,anditisevenmoreinconvenientwhensomeoftheparametersareoptional.
Insteadofhavingmanyparameters,youcanuseoneparameterandmakeitanobject.Thepropertiesoftheobjectaretheactualparameters.Thisissuitabletopassconfigurationoptionsbecausethesetendtobenumerousandoptional(withsmartdefaults).Thebeautyofusingasingleobjectasopposedtomultipleparametersisdescribedasfollows:
Theorderdoesn'tmatterYoucaneasilyskip parametersthatyoudon'twanttosetIt'seasytoaddmoreoptionalconfigurationattributesItmakesthecodemorereadablebecausetheconfigurationobject'spropertiesarepresentinthecallingcodealongwiththeirnames
ImagineyouhavesomesortofUIwidgetconstructoryouusetocreatefancybuttons.Itacceptsthetexttoputinsidethebutton(thevalueattributeofthe<input>tag)andanoptionalparameterofthetypeofbutton.Forsimplicity,let'ssaythefancybuttontakesthesameconfigurationasaregularbutton.Takealookatthefollowingcode:
//aconstructorthatcreatesbuttonsMYAPP.dom.FancyButton=function(text,type){varb=document.createElement('input');b.type=type||'submit';b.value=text;returnb;
};
Usingtheconstructorissimple;youjustgiveitastring.Then,youcanaddthenewbuttontothebodyofthedocumentasfollows:
document.body.appendChild(newMYAPP.dom.FancyButton('puuush'));
Thisisallwellandworksfine,butthenyoudecideyoualsowanttobeabletosetsomeofthestylepropertiesofthebutton,suchascolorsandfonts.Youcanendupwithadefinitionlikethefollowing:
MYAPP.dom.FancyButton=function(text,type,color,border,font){//...
};
Now,usingtheconstructorcanbecomealittleinconvenient,especiallywhenyouwanttosetthethirdandfifthparameter,butnotthesecondorthefourth.Considerthefollowingexample:
newMYAPP.dom.FancyButton('puuush',null,'white',null,'Arial');
Abetterapproachistouseoneconfigobjectparameterforallthesettings.Thefunctiondefinitioncanbecomesomethinglikethefollowingcodesnippet:
MYAPP.dom.FancyButton=function(text,conf){vartype=conf.type||'submit';varfont=conf.font||'Verdana';//...};
Usingtheconstructorisshownasfollows:
varconfig={font:'Arial,Verdana,sans-serif',color:'white'
};newMYAPP.dom.FancyButton('puuush',config);
Anotherusageexampleisasfollows:
document.body.appendChild(newMYAPP.dom.FancyButton('dude',{color:'red'}));
Asyoucansee,it'seasytosetonlysomeoftheparametersandtoswitcharoundtheirorder.Inaddition,thecodeisfriendlierandeasiertounderstandwhenyouseethenamesoftheparametersatthesameplacewhereyoucallthemethod.
Adrawbackofthispatternisthesameasitsstrength.It'strivialtokeepaddingmoreparameters,whichmeanstrivialtoabusethetechnique.Onceyouhaveanexcusetoaddtothisfree-for-allbagofproperties,youwillfindittemptingtokeepaddingsomethatarenotentirelyoptional,orsomethataredependentonotherproperties.
Asaruleofthumb,allthesepropertiesshouldbeindependentandoptional.Ifyouhavetocheckallpossiblecombinationsinsideyourfunction("oh,Aisset,butAisonlyusedifBisalsoset"),thisisarecipeforalargefunctionbody,whichquicklybecomesconfusinganddifficult,ifnotimpossible,totest,becauseofallthecombinations.
Privatepropertiesandmethods
JavaScriptdoesn'thavethenotionofaccessmodifiers,whichsettheprivilegesofthepropertiesinanobject.Otherlanguagesoftenhaveaccessmodifiers,asfollows:
Public:AllusersofanobjectcanaccessthesepropertiesormethodsPrivate:OnlytheobjectitselfcanaccessthesepropertiesProtected:Onlyobjectsinheritingtheobjectinquestioncanaccesstheseproperties
JavaScriptdoesn'thaveaspecialsyntaxtodenoteprivatepropertiesormethods,butasdiscussedinChapter3,Functions, youcanuselocalvariablesandmethodsinsideafunctionandachievethesamelevelofprotection.
ContinuingwiththeexampleoftheFancyButtonconstructor,youcanhavelocalvariablestylesthatcontainsallthedefaults,andalocalsetStyle()function.Theseareinvisibletothecodeoutsideoftheconstructor.Here'showFancyButtoncanmakeuseofthelocalprivateproperties:
varMYAPP={};MYAPP.dom={};MYAPP.dom.FancyButton=function(text,conf){varstyles={font:'Verdana',border:'1pxsolidblack',color:'black',background:'grey'};functionsetStyles(b){vari;for(iinstyles){if(styles.hasOwnProperty(i)){b.style[i]=conf[i]||styles[i];}}
}conf=conf||{};varb=document.createElement('input');b.type=conf.type||'submit';b.value=text;setStyles(b);returnb;};
Inthisimplementation,stylesisaprivatepropertyandsetStyle()isaprivatemethod.Theconstructorusestheminternally(andtheycanaccessanythinginsidetheconstructor),buttheyarenotavailabletocodeoutsideofthefunction.
Privilegedmethods
Privilegedmethods(this termwascoinedbyDouglasCrockford)arenormalpublicmethodsthatcanaccessprivatemethodsorproperties.Theycanactlikeabridgeinmakingsomeoftheprivatefunctionalityaccessible,butinacontrolledmanner,wrappedinaprivilegedmethod.
Privatefunctionsaspublicmethods
Let'ssayyou'vedefinedafunctionthatyouabsolutelyneedtokeepintact, soyoumakeitprivate.However,youalsowanttoprovideaccesstothesamefunction,sothatoutsidecodecanalsobenefitfromit.Inthiscase,youcanassigntheprivatefunctiontoapubliclyavailableproperty.
Let'sdefine_setStyle()and_getStyle()asprivatefunctions,butthenassignthemtothepublicsetStyle()andgetStyle(),considerthefollowingexample:
varMYAPP={};MYAPP.dom=(function(){var_setStyle=function(el,prop,value){
console.log('setStyle');};var_getStyle=function(el,prop){console.log('getStyle');};return{setStyle:_setStyle,getStyle:_getStyle,yetAnother:_setStyle};}());
Now,whenyoucallMYAPP.dom.setStyle(),itinvokestheprivate_setStyle()function.YoucanalsooverwritesetStyle()fromtheoutsideasfollows:
MYAPP.dom.setStyle=function(){alert('b');};
Now,theresultisasfollows:
MYAPP.dom.setStylepointstothenewfunctionMYAPP.dom.yetAnotherstillpointsto_setStyle()_setStyle()isalwaysavailablewhenanyotherinternalcodereliesonittobeworkingasintended,regardlessoftheoutsidecode
Whenyouexposesomethingprivate,keepinmindthatobjects(functionsandarraysareobjectstoo)arepassedbyreferenceand,therefore,canbemodifiedfromtheoutside.
Immediatefunctions
Anotherpatternthathelpsyoukeeptheglobalnamespacecleanistowrapyourcodeinananonymousfunctionandexecutethatfunctionimmediately.Thisway,anyvariablesinsidethefunctionarelocal,aslongasyouusethevarstatement,andaredestroyedwhenthefunctionreturns,iftheyaren'tpartofaclosure.ThispatternwasdiscussedinmoredetailinChapter3,Functions.Takealookatthefollowingcode:
(function(){//codegoeshere...}());
Thispatternisespeciallysuitableforon-offinitializationtask,performedwhenthescriptloads.
Theimmediateself-executingfunctionpatterncanbeextendedtocreateandreturnobjects.Ifthecreationoftheseobjectsismorecomplicatedandinvolvessomeinitializationwork,thenyoucandothisinthefirstpartoftheself-executablefunctionandreturnasingleobjectthatcanaccessandbenefitfromanyprivatepropertiesatthetopportion,asfollows:
varMYAPP={};MYAPP.dom=(function(){//initializationcode...function_private(){//...
}return{getStyle:function(el,prop){console.log('getStyle');_private();},setStyle:function(el,prop,value){console.log('setStyle');}};}());
Modules
Combiningseveralofthepreviouspatternsgives youanewpattern,commonlyreferredtoasamodulepattern.Theconceptofmodulesinprogrammingisconvenientasitallowsyoutocodeseparatepiecesorlibrariesandcombinethemas needed,justlikepiecesofapuzzle.
Themodulepatternincludesthefollowing:
NamespacestoreducenamingconflictsamongmodulesAnimmediatefunctiontoprovideaprivatescopeandinitializationPrivatepropertiesandmethods
Note
ES5doesn'thaveabuilt-inconceptofmodules.Thereisthemodulespecificationfromhttp://www.commonjs.org,whichdefinesarequire()functionandanexportsobject.ES6,however,supportsmodules.Chapter8,ClassesandModuleshascoveredmodulesindetail.
Returninganobject thathasthepublicAPIofthemoduleasfollows:
namespace('MYAPP.module.amazing');MYAPP.module.amazing=(function(){//shortnamesfordependenciesvaranother=MYAPP.module.another;//local/privatevariablesvari,j;//privatefunctionsfunctionhidden(){}//publicAPIreturn{hi:function(){return"hello";}
};}());
And,youcanusethemoduleinthefollowingway:
MYAPP.module.amazing.hi();//"hello"
Chaining
Chainingisapatternthatallowsyoutoinvokemultiplemethodsononelineasifthemethodsarethelinksinachain.Thisisconvenientwhencallingseveralrelatedmethods.Youinvokethenextmethodontheresultofthepreviouswithouttheuseofanintermediatevariable.
Sayyou'vecreatedaconstructorthathelpsyouworkwithDOMelements.Thecodetocreateanew<span>tagthatisaddedtothe<body>tagcanlooksomethinglikethefollowing:
varobj=newMYAPP.dom.Element('span');obj.setText('hello');obj.setStyle('color','red');obj.setStyle('font','Verdana');document.body.appendChild(obj);
Asyouknow,constructorsreturntheobjectreferredtoasthiskeywordthattheycreate.Youcanmakeyourmethods,suchassetText()andsetStyle(),alsoreturnthiskeyword,whichallowsyoutocallthenextmethodontheinstancereturnedbythepreviousone.Thisway,youcanchainmethodcalls,asfollows:
varobj=newMYAPP.dom.Element('span');obj.setText('hello').setStyle('color','red').setStyle('font','Verdana');document.body.appendChild(obj);
Youdon'tevenneedtheobjvariableifyoudon'tplanonusingitafterthe newelementhasbeenaddedtothetree,sothecodelookslikethefollowing:
document.body.appendChild(newMYAPP.dom.Element('span').setText('hello').setStyle('color','red').setStyle('font','Verdana'));
Adrawbackofthispatternisthatitmakesitalittlehardertodebugwhenanerroroccurssomewhereinalongchain,andyoudon'tknowwhichlinkistoblamebecausetheyareallonthesameline.
JSON
Let'swrapupthecoding patternssectionofthischapterwithafewwordsaboutJSON.JSONisnottechnicallyacodingpattern,butyoucansaythatusingitisagoodpattern.
JSONisapopularlightweightformattoexchangedata.It'softenpreferredoverXMLwhenusingXMLHttpRequest()toretrievedatafromtheserver.There'snothingspecificallyinterestingaboutJSONotherthanthefactthatit'sextremelyconvenient.TheJSONformatconsistsofdatadefinedusingobjectandarrayliterals.HereisanexampleofaJSONstringthatyourservercanrespondwithafteranXHRrequest:
{'name':'Stoyan','family':'Stefanov','books':['OOJS','JSPatterns','JS4PHP']}
AnXMLequivalentofthiswillbesomethinglikethefollowingpieceofcode:
<?xmlversion="1.1"encoding="iso-8859-1"?><response><name>Stoyan</name><family>Stefanov</family><books><book>OOJS</book><book>JSPatterns</book><book>JS4PHP</book></books></response>
First,youcanseehowJSONislighterintermsofthenumberofbytes.However,themainbenefitisnotthesmallerbytesize,butthefactthatit'strivialtoworkwithJSONinJavaScript.Let'ssay,you'vemadeanXHRrequestandhavereceivedaJSONstringintheresponseTextpropertyoftheXHRobject.YoucanconvertthisstringofdataintoaworkingJavaScriptobjectbysimplyusingeval().Considerthefollowingexample:
//warning:counter-examplevarresponse=eval('('+xhr.responseText+')');
Now,youcanaccessthe datainobjasobjectpropertiesasfollows:
console.log(response.name);//"Stoyan"console.log(response.books[2]);//"JS4PHP"
Theproblemisthateval()isinsecure,soit'sbestifyouusetheJSONobjecttoparsetheJSONdata(afallbackforolderbrowsersisavailableathttp://json.org/).CreatinganobjectfromaJSONstringisstilltrivialasfollows:
varresponse=JSON.parse(xhr.responseText);
Todotheopposite,thatis,toconvertanobjecttoaJSONstring,youcanusethestringify()method,asfollows:
varstr=JSON.stringify({hello:"you"});
Due to its simplicity, JSON has quickly become popular as a language-independent format toexchangedata,andyoucaneasilyproduceJSON ontheserversideusingyourpreferredlanguage.InPHP,forexample,therearethejson_encode()andjson_decode()functionsthatletyouserializeaPHParrayorobjectintoaJSONstring,andviceversa.
Higherorderfunctions
Functionalprogrammingwasconfinedtoalimitedsetoflanguagessofar.Withmorelanguagesaddingfeaturestosupportfunctionalprogramming,interestintheareaisgainingmomentum.JavaScriptisevolvingtosupportcommonfeaturesoffunctionalprogramming.Youwillgraduallyseealotofcodewritteninthisstyle.Itisimportanttounderstandthefunctionalprogrammingstyle,evenifyoudon'tfeelinclinedjustyettouseitinyourcode.
Higherorderfunctionsareoneoftheimportantmainstaysoffunctionalprograming.Higherorderfunctionisafunctionthatdoesatleastoneofthefollowing:
TakesoneormorefunctionsasargumentsReturnsafunctionasaresult
AsfunctionsarefirstclassobjectsinJavaScript, passingandreturningfunctionstoandfromafunctionisaprettyroutineaffair.Callbacksarehigherorderfunctions.Let'stakealookathowwecantakethesetwoprinciplestogetherandwriteahigherorderfunction.
Let'swriteafilterfunction;thisfunctionfiltersoutvaluesfromanarraybasedonacriteriadeterminedbyafunction.Thisfunctiontakestwoarguments-afunction,whichreturnsaBooleanvalue,trueforkeepingthiselement.
Forexample,withthisfunction,wearefilteringalloddvaluesfromanarray.Considerthefollowinglinesofcode:
console.log([1,2,3,4,5].filter(function(ele){returnele%2==0;}));//[2,4]
Wearepassingananonymousfunctiontothefilterfunctionasthefirstargument.ThisfunctionreturnsaBooleanbasedonaconditionthatchecksiftheelementisoddoreven.
ThisisanexampleofoneoftheseveralhigherorderfunctionsaddedtoECMAScript5.ThepointwearetryingtomakehereisthatyouwillincreasinglyseesimilarpatternsofusageinJavaScript.Youmustfirstunderstandhowhigher orderfunctionsworkandlater,onceyouarecomfortable with the concept, try to incorporate them in your code as well.
WithES6functionsyntaxchanges,itisevenmoreeleganttowritehigherorderfunctions.Let'stakeasmallexampleinES5andseehowthattranslatesintoitsES6equivalent:
functionadd(x){returnfunction(y){returny+x;};}varadd3=add(3);
console.log(add3(3));//=>6console.log(add(9)(10));//=>19
Theaddfunctiontakesx andreturnsafunctionthattakesyasanargumentandthenreturnsvalueofexpressiony+x.
Whenwelookedatarrowfunctions,wediscussedthatarrowfunctionsreturnresultsofasingleexpressionimplicitly.So,theprecedingfunctioncanbeturnedintoanarrowfunctionbymakingthebodyofthearrowfunctionanotherarrowfunction.Takealookatthefollowingexample:
constadd=x=>y=>y+x;
Here,wehaveanouterfunction,x=>[innerfunctionwithxasargument],andwehaveaninnerfunction,y=>y+x.
Thisintroductionwillhelpyougetfamiliarwiththeincreasingusageofhigherorderfunctions,andtheirincreasedimportanceinJavaScript.
Design patternsThe second part of this chapter presents a JavaScript approach to a subset of the design patternsintroducedbyDesignPatterns:ElementsofReusableObject-OrientedSoftware,aninfluentialbookmostcommonlyreferredtoastheBookofFour,theGangofFour,orGoF(afteritsfourauthors).ThepatternsdiscussedintheGoFbook aredividedintothethreefollowinggroups:
Creationalpatternsthatdealwithhowobjectsarecreated(instantiated)StructuralpatternsthatdescribehowdifferentobjectscanbecomposedinordertoprovidenewfunctionalityBehavioralpatternsthatdescribewaysforobjectstocommunicatewitheachother
Thereare23patternsintheBookofFour,andmorepatternshavebeenidentifiedsincethebook'spublication.It'swaybeyondthescopeofthisbooktodiscussallofthem,sotheremainderofthechapterdemonstratesonlyfour,alongwithexamplesoftheirimplementationinJavaScript.Rememberthatthepatternsaremoreaboutinterfacesandrelationshipsratherthanimplementation.Onceyouhaveanunderstandingofadesignpattern,it'softennotdifficulttoimplementit,especiallyinadynamiclanguagesuchasJavaScript.
Thepatternsdiscussedthroughtherestofthechapterareasfollows:
SingletonFactoryDecoratorObserver
Singletonpattern
Singletonisacreationaldesignpattern,meaningthatitsfocusisoncreatingobjects.Ithelpsyouwhenyouwanttomakesurethereisonlyoneobjectofagivenkindorclass.Inaclassicallanguage,thiswouldmeanthataninstanceofaclassisonlycreatedonce,andanysubsequentattemptstocreatenewobjectsofthesameclasswouldreturntheoriginalinstance.
InJavaScript,becausetherearenoclasses,asingletonisthedefaultandmostnaturalpattern.Everyobjectisasingletonobject.
The most basic implementation of the singleton in JavaScript is the object literal. Take a look atthefollowinglineofcode:
varsingle={};
Thatwaseasy,right?
Singleton2pattern
Ifyouwanttouseaclass-likesyntaxandstillimplementthesingletonpattern,thingsbecomeabitmoreinteresting.Let'ssay,youhaveaconstructorcalledLogger(),andyouwanttobeabletodosomethinglikethefollowing:
varmy_log=newLogger();my_log.log('someevent');
//...1000linesofcodelaterinadifferentscope...
varother_log=newLogger();other_log.log('somenewevent');console.log(other_log===my_log);//true
Theideaisthat,althoughyouusenew,onlyoneinstanceneedstobecreated,andthisinstanceisthenreturnedinconsecutivecalls.
Globalvariable
Oneapproachistouseaglobalvariabletostorethesingleinstance.Yourconstructorcouldlooklikethefollowingcodesnippet:
functionLogger(){if(typeofglobal_log==="undefined"){global_log=this;}returnglobal_log;}
Usingthisconstructorgivestheexpectedresult,whichisasfollows:
vara=newLogger();varb=newLogger();console.log(a===b);//true
Thedrawbackis,obviously,theuseofaglobalvariable.Itcanbeoverwrittenatanytime,evenaccidentally,andyoucanlosetheinstance.Theopposite,yourglobalvariableoverwritingsomeoneelse'sisalsopossible.
Propertyoftheconstructor
Asyouknow,functionsareobjectsandtheyhaveproperties.Youcanassignthesingleinstancetoapropertyoftheconstructorfunction,asfollows:
functionLogger(){if(!Logger.single_instance){Logger.single_instance=this;}
returnLogger.single_instance;}
Ifyouwritevara=newLogger(),apointstothenewlycreatedLogger.single_instanceproperty.Asubsequentvarb=newLogger()callresultsinbpointingtothesameLogger.single_instanceproperty,whichisexactlywhatyouwant.
Thisapproachcertainlysolvestheglobalnamespaceissuebecausenoglobalvariablesarecreated.TheonlydrawbackisthatthepropertyoftheLoggerconstructor ispubliclyvisible,soitcanbeoverwrittenatanytime.Insuchcases,thesingleinstancecanbelostormodified.Ofcourse,youcanonlyprovidesomuchprotectionagainstfellowprogrammersshootingthemselvesinthefoot.Afterall,ifsomeonecanmesswiththesingle-instanceproperty,theycanmessuptheLoggerconstructordirectlyaswell.
Inaprivateproperty
Thesolutiontotheproblemofoverwritingthepubliclyvisiblepropertyisnottouseapublicproperty,butaprivateone.Youalreadyknowhowtoprotectvariableswithaclosure,soasanexercise,youcanimplementthisapproachtothesingletonpattern.
Factorypattern
Thefactoryisanothercreationaldesignpattern,asitdealswithcreatingobjects.Thefactorycanhelpyouwhenyouhavesimilartypesofobjectsandyoudon'tknowinadvancewhichoneyouwanttouse.Basedonuserinputorothercriteria,yourcodedeterminesthetypeofobjectitneedsonthefly.
Let'ssayyouhavethreedifferentconstructorsthatimplementsimilarfunctionality.AlltheobjectstheycreatetakeaURLbutdodifferentthingswithit.OnecreatesatextDOMnode;thesecondcreatesalink;andthethird,animage,asfollows:
varMYAPP={};MYAPP.dom={};MYAPP.dom.Text=function(url){this.url=url;this.insert=function(where){vartxt=document.createTextNode(this.url);where.appendChild(txt);};};MYAPP.dom.Link=function(url){this.url=url;this.insert=function(where){varlink=document.createElement('a');link.href=this.url;link.appendChild(document.createTextNode(this.url));where.appendChild(link);};};MYAPP.dom.Image=function(url){
this.url = url;this.insert=function(where){varim=document.createElement('img');im.src=this.url;where.appendChild(im);};};
Usingthethreedifferentconstructorsisexactlythesame-passtheurlvariableandcalltheinsert()method,asfollows:
varurl='http://www.phpied.com/images/covers/oojs.jpg';
varo=newMYAPP.dom.Image(url);o.insert(document.body);
varo=newMYAPP.dom.Text(url);o.insert(document.body);
varo=newMYAPP.dom.Link(url);
o.insert(document.body);
Imagineyourprogramdoesn'tknowinadvancewhichtypeofobjectisrequired.Theuserdecides,duringruntime,byclickingonabuttonforexample.Iftypecontainstherequiredtypeofobject,you'llneedtouseaniforaswitchstatement,andwritesomethinglikethefollowingpieceofcode:
varo;if(type==='Image'){o=newMYAPP.dom.Image(url);}if(type==='Link'){o=newMYAPP.dom.Link(url);}if(type==='Text'){o=newMYAPP.dom.Text(url);
}o.url='http://...';o.insert();
Thisworksfine;however,ifyouhavealotofconstructors,thecodebecomestoolengthyandhardtomaintain.Also,ifyouarecreatingalibraryoraframeworkthatallowsextensionsorplugins,youdon'tevenknowtheexactnamesofalltheconstructorfunctionsinadvance.Insuchcases,it'sconvenienttohaveafactoryfunctionthattakescareofcreatinganobjectofthedynamicallydeterminedtype.
Let'saddafactorymethodtotheMYAPP.domutility:
MYAPP.dom.factory=function(type,url){returnnewMYAPP.dom[type](url);};
Now,youcanreplacethethreeiffunctionswiththesimplercode,asfollows:
varimage=MYAPP.dom.factory("Image",url);image.insert(document.body);
Theexamplefactory()methodinthepreviouscodewassimple;however,inareal-lifescenario,you'dwanttodosomevalidationagainstthetypevalue(forexample,checkifMYAPP.dom[type]exists)andoptionallydosomesetupworkcommontoallobjecttypes(forexample,setuptheURLallconstructorsuse).
Decoratorpattern
Thedecoratordesignpatternisastructuralpattern;itdoesn'thavemuchtodowithhowobjectsarecreated,butratherhowtheirfunctionalityisextended.Insteadofusinginheritance,whereyouextendinalinearway(parent-child-grandchild),youcanhaveonebaseobjectandapoolofdifferentdecoratorobjectsthatprovideextrafunctionality.Yourprogramcanpickandchoosewhichdecoratorsitwants,andinwhichorder.Foradifferentprogramor codepath,youmayhaveadifferentsetofrequirementsandpickdifferentdecoratorsoutofthesamepool.Takealookatthefollowingcodesnippettoseehowtheusagepartofthedecoratorpatterncanbeimplemented:
varobj={doSomething:function(){console.log('sure,asap');
}//...};obj=obj.getDecorator('deco1');obj=obj.getDecorator('deco13');obj=obj.getDecorator('deco5');obj.doSomething();
YoucanseehowyoucanstartwithasimpleobjectthathasadoSomething()method.Then,youcanpickoneofthedecoratorobjectsyouhavelyingaroundandwhichcanbeidentifiedbyname.AlldecoratorsprovideadoSomething()methodthatfirstcallsthesame methodofthepreviousdecoratorandthenproceedswithitsowncode.Everytimeyouaddadecorator,youoverwritethebaseobjwithanimprovedversionofit.Intheend,whenyouarefinishedaddingdecorators,youcalldoSomething().Asaresult,allofthedoSomething()methodsofallthedecoratorsareexecutedinsequence.Let'sseeanexample.
Decoratingachristmastree
Let'sillustratethedecoratorpatternwithanexampleofdecoratingaChristmastree.Youcanstartwiththedecorate()methodasfollows:
vartree={};tree.decorate=function(){alert('Makesurethetreewon'tfall');};
Now,let'simplementagetDecorator()methodthataddsextradecorators.Thedecoratorswillbeimplementedasconstructorfunctions,andthey'llallinheritfromthebasetreeobjectasfollows:
tree.getDecorator=function(deco){tree[deco].prototype=this;returnnewtree[deco];
};
Now,let'screatethefirstdecorator,RedBalls(),asapropertyoftree,inordertokeeptheglobalnamespacecleaner.Theredballobjectsalsoprovideadecorate()method,buttheymakesuretheycalltheirparent'sdecorate()first.Forexample,takealookatthefollowingcode:
tree.RedBalls=function(){this.decorate=function(){this.RedBalls.prototype.decorate();alert('Putonsomeredballs');};
};
Similarly,implementBlueBalls()andAngel()decoratorsasfollows:
tree.BlueBalls=function(){this.decorate=function(){this.BlueBalls.prototype.decorate();alert('Addblueballs');};
};tree.Angel=function(){this.decorate=function(){this.Angel.prototype.decorate();alert('Anangelonthetop');};};
Now,let'saddallofthedecoratorstothebaseobject,asshowninthefollowingcodesnippet:
tree=tree.getDecorator('BlueBalls');tree=tree.getDecorator('Angel');tree=tree.getDecorator('RedBalls');
Finally,runthedecorate()methodasfollows:
tree.decorate();
Thissinglecallresultsinthefollowingalerts,specificallyinthisorder:
1. Makesurethetreewon'tfall.2. Addtheblueballs.3. Addanangelatthetop.4. Addsomeredballs.
Asyousee,thisfunctionalityallowsyoutohaveasmanydecoratorsasyoulike,andtochooseandcombinetheminanywayyoulike.
Observerpattern
Theobserverpattern,alsoknownasthesubscriber-publisherpattern,isabehavioralpattern,whichmeansthatitdealswithhowdifferentobjectsinteractandcommunicatewitheachother.Whenimplementingtheobserverpattern,youhavethefollowingobjects:
Oneormorepublisherobjectsthatannouncewhentheydosomethingimportant.Oneormoresubscriberstunedintooneormorepublishers.Theylistentowhatthepublishersannounceandthenactappropriately.
Theobserverpatternmaylookfamiliartoyou.Itsoundssimilartothebrowsereventsdiscussedinthepreviouschapter,andrightlyso,becausethebrowsereventsareoneexampleapplicationofthispattern.Thebrowseristhepublisher;itannouncesthefactthatanevent,suchasaclick,hashappened.Youreventlistenerfunctionsthataresubscribedtolistentothistypeofeventwillbenotifiedwhenithappens.Thebrowser-publishersendsaneventobjecttoallofthesubscribers.Inyourcustomimplementations,youcansendanytypeofdatayoufindappropriate.
Therearetwosubtypesoftheobserverpattern:pushandpull.Pushiswherethepublishersareresponsibletonotifyeachsubscriber,andpulliswherethesubscribersmonitorforchangesinapublisher'sstate.
Let'stakealookatanexampleimplementationofthepushmodel.Let'skeeptheobserver-relatedcodeinaseparateobjectandthenusethisobjectasamix-in,addingitsfunctionalitytoanyotherobjectthatdecidestobeapublisher.Inthisway,anyobjectcanbecomeapublisherandanyfunctioncanbecomeasubscriber.Theobserverobjectwillhavethefollowingpropertiesandmethods:
AnarrayofsubscribersthatarejustcallbackfunctionsTheaddSubscriber()andremoveSubscriber()methodsthataddto,andremovefrom,thesubscriberscollectionApublish()methodthattakesdataandcallsallsubscribers,passingthedatatothemAmake()methodthattakesanyobjectandturnsitintoapublisherbyaddingallofthemethodsmentionedpreviouslytoit
Here'stheobservermix-inobjectthatcontainsallthesubscription-relatedmethodsandcanbeusedtoturnanyobjectintoapublisher:
varobserver={addSubscriber:function(callback){if(typeofcallback==="function"){this.subscribers[this.subscribers.length]=callback;}},removeSubscriber:function(callback){for(vari=0;i<this.subscribers.length;i++){if(this.subscribers[i]===callback){
deletethis.subscribers[i];}}},publish:function(what){for(vari=0;i<this.subscribers.length;i++){if(typeofthis.subscribers[i]==='function'){this.subscribers[i](what);}}},make:function(o){//turnsanobjectintoapublisherfor(variinthis){if(this.hasOwnProperty(i)){o[i]=this[i];o.subscribers=[];}}}};
Now,let'screatesomepublishers.Apublishercanbeanyobjectanditsonlydutyistocallthepublish()methodwheneversomethingimportantoccurs.Here'sabloggerobjectthatcallspublish()everytimeanewblogpostingisready:
varblogger={writeBlogPost:function(){varcontent='Todayis'+newDate();this.publish(content);}};
AnotherobjectcanbetheLATimesnewspaperthatcallspublish()whenanewnewspaperissueisout.Considerthefollowinglinesofcode:
varla_times={newIssue:function(){varpaper='MartianshavelandedonEarth!';this.publish(paper);}};
Youcanturntheseobjectsintopublishersasfollows:
observer.make(blogger);observer.make(la_times);
Now,let'shavethefollowingtwosimpleobjects,jackandjill:
varjack={read:function(what){console.log("Ijustreadthat"+what)
}};varjill={gossip:function(what){console.log("Youdidn'thearitfromme,but"+what)}};
Thejackandjillobjectscansubscribetothebloggerobjectbyprovidingthecallbackmethodstheywanttocallwhensomethingispublished,asfollows:
blogger.addSubscriber(jack.read);blogger.addSubscriber(jill.gossip);
Whathappensnow,whenthebloggerobjectwritesanewpost?Theresultisthatjackandjillwillgetnotified:
>blogger.writeBlogPost();IjustreadthatTodayisFriJan04201319:02:12GMT-0800(PST)Youdidn'thearitfromme,butTodayisFriJan04201319:02:12GMT-
0800(PST)
Atanytime,jillmaydecidetocancelhersubscription.Then,whenwritinganotherblogpost,theunsubscribedobjectisnolongernotified.Considerthefollowingcodesnippet:
>blogger.removeSubscriber(jill.gossip);>blogger.writeBlogPost();IjustreadthatTodayisFriJan04201319:03:29GMT-0800(PST)
ThejillobjectmaydecidetosubscribetoLATimes,asanobjectcanbeasubscribertomanypublishers,asfollows:
>la_times.addSubscriber(jill.gossip);
Then,whenLATimespublishesanewissue,jillgetsnotifiedandjill.gossip()isexecuted,asfollows:
>la_times.newIssue();Youdidn'thearitfromme,butMartianshavelandedonEarth!
SummaryIn this chapter, you learned about common JavaScript coding patterns and learned how to makeyourprogramscleaner,faster,andbetteratworkingwithotherprogramsandlibraries.Then,yousawadiscussionandsampleimplementationsofahandfulofthedesignpatternsfromtheBookofFour.YoucanseehowJavaScriptisafullyfeatureddynamicprogramminglanguage,andthatimplementingclassicalpatternsinadynamiclooselytypedlanguageisprettyeasy.Thepatternsare,ingeneral,alargetopic,andyoucanjointheauthorofthisbookinafurtherdiscussionoftheJavaScriptpatternsatJSPatterns.com,ortakealookattheJavaScriptPatternsbook.Thenextchapterfocusesontestinganddebuggingmethodologies.
Chapter 12. Testing and DebuggingAs you write JavaScript applications, you will soon realize that having a sound testing strategy isindispensable.Infact,notwritingenoughtestsisalmostalwaysabadidea.Itisessentialtocoverallnontrivialfunctionalityofyourcodetomakesureofthefollowingpoints:
ExistingcodebehavesasperthespecificationsAnynewcodedoesnotbreakthebehaviordefinedbythespecifications
Both these points are very important. Many engineers consider only the first point as the solereasontocoveryourcodewithenoughtests.Themostobviousadvantage oftestcoverageistoreallymakesurethatthecodebeingpushedtoproductionsystemismostlyerrorfree.Writingtestcasestosmartlycovermaximumfunctionalareas ofthecode,generallygivesgoodindicationaroundtheoverallqualityofthecode.Thereshouldbenoargumentsorcompromisesaroundthispoint.Although,itisunfortunatethatmanyproductionsystemsarestillbereftofadequatecodecoverage.Itisveryimportanttobuildanengineeringculturewheredevelopersthinkaboutwritingtestsasmuchastheythinkaboutwritingcode.
Thesecondpointisevenmoreimportant.Legacysystemsareusuallyverydifficulttomanage.Whenyouareworkingoncode,eitherwrittenbysomeoneelseorwritten byalargedistributedteam,itisfairlyeasytointroducebugsandbreakthings.Eventhebestengineersmakemistakes.Whenyouareworkingonalargecodebaseyouareunfamiliarwith,ifthereisnosoundtestcoveragetohelpyou,youwillintroducebugs.Asyouwon'thavetheconfidenceinthechangesyouaremaking,becausetherearenotestcasestoconfirmyourchanges,yourcodereleaseswillbeshaky,slow,andobviouslyfullofhiddenbugs.
You will refrain from refactoring or optimizing your code, because you won't be really sure whatchangestothecodebasewouldpotentiallybreaksomething(again,becausetherearenotestcasetoconfirmyourchanges);allthisisaviciouscircle.It'slikeacivilengineersaying-althoughIhaveconstructedthisbridge,Ihavenoconfidenceonthequalityoftheconstruction.Itmaycollapseimmediatelyornever.Althoughthismaysoundlikeanexaggeration,Ihaveseenalotofhighimpactproductioncodebeingpushedwithnotestcoverage.Thisisriskyandshouldbeavoided.Whenyouarewritingenoughtestcasestocovermajorityofyourfunctionalcode,whenyoumakechangetothosepieces,youwillimmediatelyrealizeifthereisaproblemwiththisnewchange.Ifyourchangesmakethetestcasefail,youwillrealizetheproblem.Ifyourrefactorbreaksthetestscenario,youwillrealizetheproblem;allofthishappensmuchbeforethecodeispushedtoproduction.
Inrecentyears,ideasliketest-drivendevelopmentandself-testingcodearegainingprominence,especiallyinagilemethodology.Thesearefundamentallysoundideasandwillhelpyouwriterobustcode-thecodeyouareconfidentof.Wewilldiscussalltheseideasinthischapter.WewillunderstandhowtowritegoodtestcasesinmodernJavaScript.Wewillalsolookatseveraltoolsandmethodstodebugyourcode.JavaScriptwastraditionallyabitdifficulttotestand
debug,primarilyduetolackoftools,butmoderntoolsmakebothoftheseeasyandnatural.
Unit testingWhen we talk about test cases, we mostly mean unit tests. It is incorrect to assume that the unit wewanttotestisalwaysafunction.Theunit,orunitofwork,isalogicalunitthatconstitutessinglebehavior.Thisunitshouldbeabletobeinvokedviaapublicinterfaceandshouldbetestableindependently.
Thus,aunittestcanperformthefollowingfunctions:
It tests a single logical functionItcanrunwithoutaspecificorderofexecutionIttakescareofitsowndependenciesandmockdataItalwaysreturnsthesameresultforthesameinputItshouldbeself-explanatory,maintainable,andreadable
MartinFowleradvocatestheTestPyramid(http://martinfowler.com/bliki/TestPyramid.html)strategytomakesurewehaveahighnumberofunitteststoensuremaximumcodecoverage.Therearetwoimportanttestingstrategiesthatwewilldiscussinthischapter.
TestDrivenDevelopment
Testdrivendevelopment(TDD)hasgainedalotofprominenceinthelastfewyears.Theconceptwasfirstproposedaspartoftheextremeprogrammingmethodology.Theideaistohaveshortrepetitivedevelopmentcycleswherethefocusisonwritingthetestcasesfirst.Thecyclelookslikethefollowing:
1. Addatestcaseasperthespecificationsforthespecificunitofcode.2. Runexistingsuiteoftestcasestoseeifthenewtestcaseyouwrotefails;itshould,becausethereisnocodefor thatunityet.Thisstepensuresthatthecurrenttestharnessworkswell.
3. Writethecodethatmainlyservestoconfirmtothetestcase.Thiscodeisnotoptimized,refactored,orevenentirelycorrect.However,thisisfineatthismoment.
4. Reruntestsandseeifallthetestcasespass.Afterthisstep,youareconfidentthatthenewcodeisnotbreakinganything.
5. Refactorcodetomakesureyouareoptimizingtheunitandhandlingallcornercases
Thesestepsarerepeatedforanynewcodeyouadd.Thisisanelegantstrategythatworksreallywellforagilemethodology.TDDwillbesuccessfulonlyifthetestableunitsofcodearesmallandconfirmsonlytothetestcase.
BehaviorDrivenDevelopment
AverycommonproblemwhiletryingtofollowTDDisvocabularyandthedefinitionofcorrectness.BDDtriestointroduceaubiquitouslanguagewhilewritingthetestcaseswhenyouarefollowingTDD.Thislanguagemakessurethatboththebusinessandtheengineeringaretalkingaboutthesamething.
WewilluseJasmineastheprimaryBDDframeworkandexplorevarioustestingstrategies.
Note
YoucaninstallJasminebydownloadingthestandalonepackagefromhttps://github.com/jasmine/jasmine/releases/download/v2.3.4/jasmine-standalone-2.3.4.zip.
Whenyouunzipthispackage,youwillseethefollowingdirectorystructure:
ThelibdirectorycontainstheJavaScriptfilesthatyouneedinyourprojecttostartwritingJasminetestcases.IfyouopenSpecRunner.html,youwillfindthefollowingJavaScriptfilesincludedinit:
<scriptsrc="lib/jasmine-2.3.4/jasmine.js"></script><scriptsrc="lib/jasmine-2.3.4/jasmine-html.js"></script><scriptsrc="lib/jasmine-2.3.4/boot.js"></script>
<!--includesourcefileshere...--><scriptsrc="src/Player.js"></script><scriptsrc="src/Song.js"></script>
<!--includespecfileshere...--><scriptsrc="spec/SpecHelper.js"></script><scriptsrc="spec/PlayerSpec.js"></script>
ThefirstthreeareJasmine'sownframeworkfiles.Thenextsectionincludesthesourcefileswewanttotestandtheactualtestspecifications.
Let'sexperimentwithJasmineviaaveryordinaryexample.Createabigfatjavascriptcode.jsfileandplaceitinthesrc/directory.Thefunctionwewilltestisasfollows:
functioncapitalizeName(name){returnname.toUpperCase();}
Thisisasimplefunctionthatdoesonesinglething.Itreceivesastringandreturnsacapitalizedstring.Wewilltestvariousscenariosaroundthisfunction.Thisistheunit ofcode,whichwediscussedearlier.
Next,createthetestspecifications.CreateoneJavaScriptfile,test.spec.js,andplaceitinthespec/directory.YouwillneedtoaddthefollowingtwolinesintoyourSpecRunner.html:Thefileshouldcontainthefollowing:
<scriptsrc="src/bigfatjavascriptcode.js"></script><scriptsrc="spec/test.spec.js"></script>
Theorderofthisinclusiondoesnotmatter.WhenwerunSpecRunner.html,youwillseesomethinglikethefollowingimage:
ThisistheJasminereportthatshowsdetailsaboutthenumberofteststhatwereexecutedandthecountoffailuresandsuccesses.Now,let'smakethetestcasefail.Wewanttotestacasewhereanundefined variable is passed to the function. Let's add one more test case, as follows:
it("canhandleundefined",function(){varstr=undefined;expect(capitalizeName(str)).toEqual(undefined);});
Now,whenyourunSpecRunner,youwillseethefollowingresult:
Asyoucansee,thefailureisdisplayedforthistestcaseinadetailederrorstack.Now,wewillgoaboutfixingthis.InyouroriginalJScode,handleundefinedasfollows:
functioncapitalizeName(name){if(name){returnname.toUpperCase();}}
Withthischange,yourtestcasewillpass,andyouwillseethefollowingresultintheJasminereport:
Thisisverysimilartowhatatest-drivendevelopmentwouldlooklike.Youwritetestcasesandthenfillthenecessarycodetoconfirmtothespecificationsandrerunthetestsuite.Let'sunderstandthestructureoftheJasminetests.
Ourtestspecificationlookslikethefollowingpieceofcode:
describe("TestStringUtilities",function(){it("convertstocapital",function(){varstr="albert";expect(capitalizeName(str)).toEqual("ALBERT");});it("canhandleundefined",function(){varstr=undefined;expect(capitalizeName(str)).toEqual(undefined);});});
Thedescribe("TestStringUtilities"iswhatatestsuiteis.Thenameofthetestsuiteshoulddescribetheunit-of-codewearetesting;thiscanbeafunctionoragroupofrelatedfunctionality.Inside the specs, you will call the global Jasmine function,it, to which you will pass the title ofthe spec and the function that validates the condition of the testcase This function is the actual testcase.Youcancatchoneormoreassertionsorthegeneralexpectationsusingtheexpectfunction.Whenallexpectationsaretrue,yourspecispassed.YoucanwriteanyvalidJavaScriptcodeinsidedescribeanditfunctions.Thevaluesyouverifyaspartoftheexpectationsarematchedusingamatcher.Inourexample,toEqualisthematcherthatmatchestwovaluesforequality.Jasminecontainsarichsetofmatchestosuitmostofthecommonusecases.SomecommonmatcherssupportedbyJasmineareasfollows:
toBe:Thismatcher checksiftwoobjectsbeingcomparedareequal. Thisissameasthe===comparison.Forexample,checkoutthefollowingcodesnippet:
vara={value:1};varb={value:1};expect(a).toEqual(b);//success,sameas==comparisonexpect(b).toBe(b);//failure,sameas===comparisonexpect(a).toBe(a);//success,sameas===comparison
not:Youcannegateamatcherwithanotprefix.Forexample,expect(1).not.toEqual(2);willnegatethematchmadebytoEqual().toContain:Thischecksifanelementispartofanarray.ItisnotanexactobjectmatchastoBe.Forexample,takealookatthefollowinglinesofcode:
expect([1,2,3]).toContain(3);expect("astronomy is a science").toContain("science");
toBeDefinedandtoBeUndefined:Thesetwomatchesarehandytocheckwhetheravariableisundefinedornot.toBeNull:Thischecksifavariable'svalue isnull.toBeGreaterThanandtoBeLessThan:Thesematcherperformsnumericcomparison(worksonstringstoo).Forexample,considerthefollowingpieceofcode:
expect(2).toBeGreaterThan(1);expect(1).toBeLessThan(2);expect("a").toBeLessThan("b");
AninterestingfeatureofJasmineisthespies.Whenyouarewritingalargesystem,itisnotpossibletomakesurethatallsystemsarealwaysavailableandcorrect.Atthesametime,youdon't want your unit tests to fail due to a dependency that may be broken or unavailable. Tosimulateasituationwherealldependenciesareavailableforaunitofcodewewanttotest,wewillmockthisdependencytoalwaysgivetheresponseweexpect.Mockingisanimportantaspectoftesting,andmosttestingframeworksprovidesupportformocking.JasmineallowsmockingusingafeaturecalledaSpy.Jasminespiesessentiallystubsthefunctionswemaynothavereadyatthetimeof writingthetestcase,butaspartofthefunctionality,wewillneedtotrackthatweareexecutingthosedependenciesandnotignoringthem.Considerthefollowingexample:
describe("mockingconfigurator",function(){varcofigurator=null;varresponseJSON={};
beforeEach(function(){configurator={submitPOSTRequest:function(payload){//Thisisamockservicethatwilleventuallybereplaced//byarealserviceconsole.log(payload);
return{"status":"200"};}};spyOn(configurator,'submitPOSTRequest').and.returnValue({"status":"200"});configurator.submitPOSTRequest({"port":"8000","client-encoding":"UTF-8"});});
it("thespywascalled",function(){expect(configurator.submitPOSTRequest).toHaveBeenCalled();});
it("theargumentsofthespy'scallaretracked",function(){expect(configurator.submitPOSTRequest).toHaveBeenCalledWith({"port":"8000","client-encoding":"UTF-8"});});});
Inthisexample,whilewearewritingthistestcase,weeitherdon'thavetherealimplementationofthedependency,configurator.submitPOSTRequest(),orsomeoneisfixingthisparticulardependency;inanycase,wedon'thaveitavailable.Forourtesttowork,wewillneedtomockit.Jasminespiesallowustoreplaceafunctionwithitsmockandallowsustotrackitsexecution.
Inthiscase,wewillneedtoensurethatwecalledthedependency.Whentheactualdependencyisready,wewillrevisitthistestcasetomakesureitfitsthespecifications;however,atthistime,allweneedtoensurethatthedependencyiscalled.JasminefunctiontohaveBeenCalled()letsustracktheexecutionofafunctionthatmaybeamock.WecanusetoHaveBeenCalledWith(),whichallowsustodetermineifthestubfunctionwascalledwithcorrectparameters.ThereareseveralotherinterestingscenariosyoucancreateusingJasminespies.Thescopeofthischapterwon'tpermitustocoverthemall,butIwouldencourageyoutodiscoverthoseareasonyourown.
Mocha,ChaiandSinon
ThoughJasmineisthemostprominentJavaScripttestingframework,mochaandchaiaregainingprominenceintheNode.jsenvironment:
Mocha is the testing framework used to describe and run test casesChaiistheassertionlibrarysupportedbyMochaSinoncomesinhandywhilecreatingmocksandstubsforyourtests
Wewon'tdiscusstheseframeworksinthisbook;however,experienceonJasminewillbehandyifyouwanttoexperimentwiththeseframeworks.
JavaScript debuggingIf you are not a completely new programmer, I am sure you must have spent some amount of timedebuggingyourorsomeoneelse'scode.Debuggingisalmostlikeanartform.Everylanguagehasdifferentmethodsandchallengesaroundthedebugging.JavaScriptistraditionallyadifficultlanguagetodebug.Ihavespentdaysandnightsinmisery,tryingtodebugbadlywrittenJavaScriptcodeusingalert()functions.Fortunately,modernbrowsers,suchasMozilla,Firefox,andGoogleChrome,haveexcellentDeveloperToolstohelpdebugJavaScriptinthebrowser.ThereareIDEslikeIntelliJIDEAandWebStormwithgreatdebuggingsupportforJavaScriptandNode.js.Inthischapter,wewillfocusprimarilyonGoogleChrome'sbuilt-indevelopertool.FirefoxalsosupportsFirebugextensionandhasexcellent built-indevelopertools,butastheybehave moreorlessthesameasGoogleChrome'sDeveloperTools,wewilldiscusscommondebuggingapproachesthatworkinbothofthesetools.
Beforewetalkaboutthe specificdebuggingtechniques,let'sunderstandthetypeoferrorswewouldbeinterestedinwhilewetrytodebugourcode.
Syntaxerrors
WhenyourcodehassomethingthatdoesnotconfirmtotheJavaScriptlanguagegrammar,theinterpreterrejectsthatpieceofcode.TheseareeasytocatchifyourIDEishelpingyouwithsyntaxchecking.MostmodernIDEshelpwiththeseerrors.Earlier,wediscussedtheusefulnessoftools,suchasJSLintandJSHint,aroundcatchingsyntaxissueswithyourcode.Theyanalyzethecodeandflagerrorsinthesyntax.TheJSHintoutputcanbeveryilluminating.Forexample,thefollowingoutputshowsupsomanythingswe canchangeinthecode.Thefollowingcodesnippetisfromoneofmyexistingprojects:
tempgit:(dev_branch)Xjshinttest.jstest.js:line1,col1,Usethefunctionformof"usestrict".test.js:line4,col1,'destructuringexpression'isavailableinES6(useesnextoption)orMozillaJSextensions(usemoz).test.js:line44,col70,'arrowfunctionsyntax(=>)'isonlyavailableinES6(useesnextoption).test.js:line61,col33,'arrowfunctionsyntax(=>)'isonlyavailableinES6(useesnextoption).test.js:line200,col29,Expected')'tomatch'('fromline200andinsteadsaw':'.test.js:line200,col29,'functionclosureexpressions'isonlyavailableinMozillaJavaScriptextensions(usemozoption).test.js:line200,col37,Expected'}'tomatch'{'fromline36andinsteadsaw')'.test.js:line200,col39,Expected')'andinsteadsaw'{'.test.js:line200,col40,Missingsemicolon.
Usingstrict
Webrieflydiscussedthe strictmodeinearlierchapters.Whenyouenablestrictmode,JavaScriptstopsbeingacceptingofsyntacticalerrorsinyourcode.Ratherthansilentlyfailing,strictmodemakesthesefailurethrowerrorsinstead.Italsohelpsyouconvertmistakesintoactualerrors.Therearetwowaysofenforcingstrictmode.Ifyouwantthestrictmodefortheentirescript,youcanjustaddtheusestrictstatement(withthequotes)asthefirstlineofyourJavaScriptprogram.Ifyouwantaspecificfunctiontoconfirmtostrictmode,youcanaddthedirectiveasthefirst line of a function. For example, take a look at the following code snippet:
functionstrictFn(){//ThislinemakesEVERYTHINGunderthisscrictmode'usestrict';...functionnestedStrictFn(){//Everythinginthisfunctionisalsonested...}}
Runtimeexceptions
Theseerrorsappearwhenyouexecutethecode,trytorefertoanundefinedvariable,ortrytoprocessanull.Whenaruntimeexceptionoccurs,anycodeafterthatparticularline,whichcausedtheexception,doesnotgetexecuted.Itisessentialtocorrectlyhandlesuchexceptionalscenariosinthecode.Whileexceptionhandlingcanhelppreventcrashes,theyalsoaidindebugging.Youcanwrapthecodethatmayencounteraruntimeexceptionintoatry{}block.Whenanycodeinsidethisblockgeneratesaruntimeexception,acorrespondinghandlercapturesit.Thehandlerisdefinedbyacatch(exception){}block.Let'sclarifythisusingthefollowingexample:
try{vara=doesnotexist;//throwsaruntimeexception}catch(e){console.log(e.message);//handletheexception//prints-"doesnotexistisnotdefined"}
Inthisexample,thevara=doesnotexistlinetriestoassignanundefinedvariable,doesnotexist,toanothervariablea.Thiscausesaruntimeexception.Whenwewrapthisproblematiccodeintotry{}catch(){}blockorwhentheexceptionoccurs(oristhrown),theexecutionstopsinthetry{}blockandgoesdirectlytothecatch(){}handler.Thecatchhandlerisresponsibleforhandlingtheexceptionalscenario.Inthiscase,wearedisplayingtheerrormessageontheconsolefordebuggingpurposes.Youcanexplicitlythrowanexceptiontotriggeranunhandledscenariointhecode.Considerthefollowingexample:
functionengageGear(gear){if(gear==="R"){console.log("Reversing");}if(gear==="D"){console.log("Driving");}if(gear==="N"){console.log("Neutral/Parking");}thrownewError("InvalidGearState");}try{engageGear("R");//ReversingengageGear("P");//InvalidGearState}catch(e){console.log(e.message);}
Inthisexample,wearehandlingvalidstatesofagearshift:R,N,andD;however,whenwereceiveaninvalidstate,weareexplicitlythrowinganexceptionclearlystatingthereason.Whenwecallthefunctionwhichwethinkmaythrowanexception,wewillwrapthecodeinthetry{}blockandattachacatch(){}handlerwithit.Whentheexceptioniscaughtbythecatch()block,wewillhandletheexceptionalconditionappropriately.
Console.logandasserts
Displayingthestateofexecutiononconsolecanbeveryusefulwhiledebugging.Although,moderndevelopertoolsallowyoutoputbreakpointsandhaltexecutiontoinspectaparticularvalueduringruntime.Youcanquicklydetectsmallissuesbyloggingsomevariablestateontheconsole.
Withtheseconceptswithus,let'sseehowwecanuseChromeDeveloperToolstodebugJavaScriptcode.
ChromeDeveloperTools
You can start Chrome Developer Tools by clickingmenu |More tools |Developer Tools. Take alookatthefollowingscreenshot:
Chromedevelopertoolopensuponthelowerpaneofyourbrowserandhasabunchofveryusefulsections.Considerthefollowingscreenshot:
TheElementspanelhelpsyouinspectandmonitortheDOMtreeandassociatedstylesheetforeachofthesecomponents.
TheNetworkpanelisusefultounderstandnetworkactivity.Forexample,youcanmonitortheresourcesbeingdownloadedoverthenetworkinrealtime.
ThemostimportantpaneforusistheSourcespane.ThispaneiswheretheJavaScriptandthedebuggeraredisplayed.Let'screateasampleHTMLwiththefollowingcontent:
<!DOCTYPEhtml><html><head><metacharset="utf-8"><title>Thistest</title><scripttype="text/javascript">functionengageGear(gear){if(gear==="R"){console.log("Reversing");}if(gear==="D"){console.log("Driving");}if(gear==="N"){console.log("Neutral/Parking");}thrownewError("InvalidGearState");}try{engageGear("R");//ReversingengageGear("P");//InvalidGearState}catch(e){console.log(e.message);
}</script></head><body></body></html>
SavethisHTMLfileandopenitinGoogleChrome.OpenDeveloperToolsinthebrowser,andyouwillseethefollowingscreen:
ThisistheviewoftheSourcespanel.YoucanseetheHTMLandembeddedJavaScriptsourceinthispanel.YoucanseetheConsolewindowaswell,andyoucanseethatthefileisexecutedandtheoutputisdisplayedonconsole.
Ontherightside,youwillseethedebuggerwindow,asshowninthefollowingscreenshot:
IntheSourcespanel,clickonthelinenumbers8and15toaddabreakpoint.Thebreakpointsallowyoutostopexecutionofthescriptatthespecifiedpoint.Considerthefollowingscreenshot:
Inthedebugpane,youcanseeallexistingbreakpoints.Takealookatthefollowingscreenshot:
Now,whenyourerunthesamepage,youwillseethattheexecutionstopsatthedebugpoint.Considerthefollowingscreenshot:
This window now has all the action. You can see that the execution is paused on line 15. In thedebugwindow,youcanseewhichbreakpointisbeingtriggered.YoucanalsoseetheCallStackandresumeexecutioninseveralways.Thedebugcommandwindowhasabunchofactions.Takealookatthefollowingscreenshot:
Youcanresumeexecution,whichwillexecuteuntilthenextbreakpoint,byclickingonthefollowingbutton:
Whenyoudothat,theexecutioncontinuesuntilthenextbreakpointisencountered.Inourcase,wewillhaltatline8.Considerthefollowingscreenshot:
YoucanobservethattheCallStackwindowshowshowwearrivedatline8.TheScopepanelshowstheLocalscopewhereyoucanseethevariablesinthescopewhenthebreakpointwasarrivedat.YoucanalsoStep-IntoorStep-overthenextfunction.
ThereareotherveryusefulmechanismstodebugandprofileyourcodeusingChromeDeveloperTools.Iwouldsuggestyoutoexperimentwiththetoolandmakeitapartofyourregulardevelopmentflow.
SummaryBoth testing and debugging phases are essential to developing robust JavaScript code. TDD andBDDareapproachescloselyassociatedwiththeagilemethodologyandiswidelyembracedbytheJavaScriptdevelopercommunity.Inthischapter,wereviewedbestpracticesaroundTDDandtheusageofJasmineasthetestingframework.Additionally,wesawvariousmethodsofdebuggingJavaScriptusingChromeDeveloperTools.
Inthenextchapter,wewillexplorethenewandexcitingworldofES6,DOMManipulationandcross-browserstrategies.
Chapter 13. Reactive Programming andReactAlongwithES6,severalnewideasareemerging.Thesearepowerfulideasandcanhelpyoubuildpowerfulsystemswithmorestreamlinedcodeanddesign.Inthischapter,wewillintroduceyoutotwosuchideas-reactiveprogrammingandreact.Althoughtheysoundsimilar,theyareverydifferent.Thischapterdoesnotgointopracticaldetailsoftheseideasbutgivesyounecessaryinformationtobecomeawareofwhattheseideasarecapableof.Withthatinformation,youcanstartincorporatingtheseideasandframeworksintoyourprojects.Wewilldiscussthebasicideaofreactiveprogrammingandtakeabitmoredetailedlookatreact.
Reactive programmingReactive programming is getting a lot of focus lately. This idea is relatively new and, like manynewideas,haslotsofconfusing,andsometimescontradictoryinformationfloatingaround.Wediscussedasynchronousprogrammingearlierinthisbook.JavaScripttakesasynchronousprogrammingtonewheightsbyprovidingfirstclasslanguageconstructsthatsupportit.
Reactiveprogrammingisessentiallyprogrammingwithasynchronouseventstreams.Aneventstreamisasequenceofeventshappeningovertime.Considerthefollowingdiagram:
Intheprecedingdiagram,timepassesfromlefttorightanddifferenteventsoccurovertime.Astheeventhappensovertime,wecanaddaneventlistenertothiswholesequence.Wheneveraneventhappens,wecanreacttoitbydoingsomething.
AnothertypeofsequenceinJavaScriptisanarray.Forexample,considerthefollowinglinesofcode:
vararr=[1,1,13,'Rx',0,0];console.log(arr);>>>[1,1,13,"Rx",0,0]
Inthiscase,theentiresequencelivesinmemoryatthesametime.However,incaseofeventstream,eventshappenovertimeandthereisnostateatthispointoftime.Considerthefollowinglinesofcode:
vararr=Rx.Observable.interval(500).take(9).map(a=>[1,1,13,'Rx',0,0][a]);
varresult=arr;result.subscribe(x=>console.log(x));
Don'tworrytoomuchaboutwhatisgoingoninthisexamplejustyet.Here,eventsarehappeningovertime.Insteadofhavingafixedbunchofelementsinanarray,heretheyarehappeningovertime,after500ms.
Wewilladdaneventlistenertothearreventstream,andwhenaneventhappens,wewillprinttheelementonconsole.Youcanseeasimilaritybetweenthemethodsinarraysandtheeventstreams.Now,toexpandonthissimilarity,let'ssay,youwanttofilterallnon-numbersfromthislist.Youcanusethemapfunctiontothiseventstream,justlikeyouwoulduseitonanarray,andthenyouwouldwanttofiltertheresultstoshowonlyintegers.Considerthefollowinglinesofcode:
vararr=[1,1,13,'Rx',0,0];varresult=arr.map(x=>parseInt(x)).filter(x=>!isNan(x));console.log(result);
Interestingly,thesamemethodsworkforeventstreamsaswell.Takealookatthefollowingcodeexample:
vararr=Rx.Observable.interval(500).take(9).map(a=>[1,1,13,'Rx',0,0][a]);varresult=arr.map(x=>parseInt(x)).filter(x=>!isNaN(x));result.subscribe(x=>console.log(x));
Thesearesimplerexamplesjusttomakesureyoustartseeinghoweventstreamsflowovertime.Pleasedon'tbotheraboutthesyntaxandconstructjustyet.Beforewecanlookatthem,wewillneedtomakesureweunderstandhowtothinkinreactiveprogramming.Eventstreamsarefundamentaltoreactiveprogramming;theyallow youtodefinethedynamicbehaviorofavalueatdeclarationtime(definitiontakenfromAndreStaltz'sblog).
Let'ssayyouhaveanavariable,whichhasinitiallythevalue3.Then,youhaveabvariable,whichis10*a.Ifweconsolelogoutb,wewillsee30.Considerthefollowinglinesofcode:
leta=3;let b = a * 10;
console.log(b);//30a=4;console.log(b);//Still30
Weknowtheresultisverystraightforward.Whenwechangethevalueofato4,thevalueofbwillnotchange.Thisishowstaticdeclarationworks.Whenwetalkaboutreactiveprogrammingandeventstreams,thisistheareawherepeoplefinddifficultyinunderstandinghoweventsflow.Ideally, we want to create a formula, b=a*10, and over time, whenever the value of a changes,the changed value is reflected in the formula.
Thatiswhatwecanaccomplishwitheventstreams.Let'ssayaisaneventstreamofjustthevalue3.Then,wehavestreamB,whichisstreamAmapped.Eachoftheseavalueswillbemappedto10*a.
IfweaddaneventlistenertothatstreamB,andweconsolelog,wewillseebbeing30.Takealookatthefollowingexample:
varstreamA=Rx.Observable.of(3,4);varstreamB=streamA.map(a=>10*a);streamB.subscribe(b=>console.log(b));
Ifwedothis,wehaveaneventstreamthatsimplyhasjusttwoevents.Ithasevent3,andthenithasevent4,andbwillchangeaccordinglywheneverachanges.Ifwerunthis,weseebbeing30and40.
Nowthatwehavespentsometimeingettingthebasicsofreactiveprogrammingsorted,youmayaskthefollowingquestion.
Whyshouldyouconsiderreactiveprogramming?
AswewritehighlyresponsiveandinteractiveUIapplicationsonmodernwebandmobile,thereisastrongneedtofinda waytodealwithreal-timeeventswithoutstoppingtheuserinteractionsontheUI.WhenyouaredealingwithmultipleUI andservereventsbeingfired,youwillbespendingmostofyourtimewritingcodetodealwiththeseevents.Thisistedious.Reactiveprogramminggivesyouastructuredframeworktodealwithasynchronouseventswithminimalcodewhileyoufocusonthebusinesslogicforyourapplication.
ReactiveprogrammingisnotlimitedtoJavaScript.Reactiveextensionsareavailableinmanyplatformsandlanguages,suchasJava,Scala,Clojure,Ruby,Python,andObjectC/Cocoa.Rx.jsandBacon.jsarepopularJavaScriptlibrariesthatprovidereactiveprogrammingsupport.
AdeepdiveintoRx.jsisnottheintentionofthischapter.Theideawastointroduceyoutotheideaofreactiveprogramming.Ifyouarekeenonadoptingreactiveprogrammingforyourprojects,youshouldtakelookatAndreStaltz'sexcellentintroduction(https://gist.github.com/staltz/868e7e9bc2a7b8c1f754).
ReactReact is taking the JavaScript world by storm. Facebook created the react framework to solve anage-oldproblem-howtodealefficientlywiththeviewpartofthetraditionalModel-View-Controllerapplications.
Reactprovidesadeclarativeandflexiblewaytobuilduserinterfaces.Themostimportantthingtorememberaboutreactisthatitdealswithonlyonething-theview,ortheUI.Reactdoesnotdealwithdata,databindings,oranythingelse.Therearecompleteframeworks,suchasAngular,thatdealwithdata,bindings,andUI;Reactisnotthat.
ReactgivesatemplatelanguageandasmallsetoffunctionstorenderHTML.Reactcomponentscanstoretheirownstateinmemory.Tobuildafull-fledgedapplication,youwillneedotherpiecesaswell;Reactisjusttohandletheviewpartofthatapplication.
A big challenge when writing complex UI is to manage state of the UI elements when the modelchanges.ReactprovidesadeclarativeAPIsothatyoudon'thavetoworryaboutexactlywhatchangesoneveryupdate.Thismakeswritingapplicationsaloteasier.ReactusesVirtualDOManddiffingalgorithm,sothatcomponentupdatesarepredictablewhilebeingfastenoughforhigh-performanceapps.
Virtual DOMLet's take a moment to understand what is a Virtual DOM. We discussed DOM (document objectmodel),atreestructureofHTMLelementonawebpage.DOMisdefacto,andtheprimaryrenderingmechanismoftheweb.TheDOMAPIs,suchasgetElementById(),allowtraversingandmodificationoftheelementsintheDOMtree.DOMisatreeandthisstructureworksprettywellwithtraversalandupdatingofelements.However,boththetraversingandupdatingofDOMisnotveryquick.Foralargepage,theDOMtreecanbeprettybig.When youwantacomplexUIthathasbunchofuserinteractions,updatingDOMelementscanbetediousandslow.WehavetriedjQueryandotherlibrariestoreducethetedioussyntaxforfrequentDOMmodifications,butDOMasastructureitselfisquitelimited.
Whatifwedon'thavetotraversetheDOMoverandoveragaintomodifyelements?Whatifyoujustdeclarehowacomponentshouldlooklikeandletsomeonehandlethelogicofhowtorenderthatcomponent?reactdoesexactlythat.ReactletsyoudeclarehowyouwantyourUIelementtolooklikeandabstractsoutlow-levelDOMmanipulationAPIs.Apartfromthisveryusefulabstraction,reactdoessomethingprettysmarttosolvetheperformanceproblemaswell.
ReactusessomethingcalledaVirtualDOM.AvirtualDOMisalightweightabstractionoftheHTMLDOM.Youcanthinkofitasalocalin-memorycopyoftheHTMLDOM.Reactusesittodo all computations necessary to render the state of a UI component.
Youcanfindmoredetailsofthisoptimizationathttps://facebook.github.io/react/docs/reconciliation.html.
React'sprimarystrength,however,isnotjustVirtualDOM.Reactisafantasticabstractionthatmakescomposition,unidirectionaldataflow,andstaticmodelingeasierwhiledevelopinglargeapplications.
Installing and running reactFirst, let's install react. Earlier, installing and getting react set up on your machine needed a bunchofdependenciestobetakencareof.However,wewillusearelativelyfasterwaytogetreactupandrunning.Wewillusecreate-react-apptowhichwecaninstallreactwithoutanybuildconfiguration.Installationisdonevianpmwhichisasfollows:
npminstall-gcreate-react-app
Here,weareinstallingthecreate-react-appnodemoduleglobally.Oncecreate-react-appisinstalled,youcansetupthedirectoryforyourapplication.Considerthefollowingcommands:
create-react-appreact-appcdreact-app/
npm start
Then,openhttp://localhost:3000/toseeyourapp.Youshouldseesomethinglikethefollowingscreenshot:
Ifyouopenthedirectory inaneditor,youwillseeseveralfilescreatedforyou,asyoucanseein
thefollowingscreenshot:
Inthisproject,node_modulesarethedependenciesrequiredtorunthisprojectanddependenciesofreactitself.Theimportantdirectoryissrc,wherethesourcecodeiskept.Forthisexample,let'skeeponlytwofiles-App.jsandindex.js.The/public/index.htmlfileshouldcontainjusttherootdiv,whichwillbeusedasatargetforourreactcomponents.Considerthefollowingcodesnippet:
<!doctypehtml><html lang="en">
<head><title>ReactApp</title></head><body><divid="root"></div></body></html>
Themomentyoumakethischange,youwillseethefollowingerror:
Beauty of developing with react is that the code changes are live-reloaded, and you can getimmediatefeedback.
Next,clearoffallcontentofApp.js,andreplaceitwiththefollowinglinesofcode:
importReactfrom'react';constApp=()=><h1>HelloReact</h1>exportdefaultApp
Now,gotoindex.jsandremovetheimport./index.css;line.Withoutyoudoinganything,suchasrestartingserverandrefreshingbrowser,youwillseethemodifiedpageonthebrowser.Considerthefollowingscreenshot:
BeforewecreateaHelloWorldreactcomponent,acoupleofimportantthingstonoticesofar.
InApp.jsandindex.js,weareimportingtwolibrariesnecessarytocreatereactcomponents.Considerthefollowinglinesofcode:
importReactfrom'react';importReactDOMfrom'react-dom';
Here,we'reimportingReact,whichisthelibrarythatallowsustobuildreactcomponents.We'realsoimportingReactDOM,whichisthelibrarythatallowsustoplaceourcomponentsandworkwiththeminthecontextoftheDOM.Then,we'reimportingthecomponentthatwejustworkedon-theAppcomponent.
We also created our first component in App.js. Consider the following line of code:
constApp=()=><h1>HelloReact</h1>
Thisisastatelessfunctioncomponent.Theotherwaytocreateacomponentistocreateaclasscomponent.Wecanreplacetheprecedingcomponentwiththefollowingclasscomponent:
classAppextendsReact.Component{render(){return<h1>HelloWorld</h1>}}
Thereareabunchofinterestingthingsgoingonhere.First,wearecreatingaclasscomponentwiththeclasskeywordthatextendsfromthesuperclassReact.Component.
OurcomponentAppisareactcomponentclassorreactcomponenttype.Acomponenttakesinparameters,alsocalledprops,andreturnsahierarchyofviewstodisplayviatherenderfunction.
Therendermethodreturnsadescriptionofwhatyouwanttorender,andthenreacttakesthatdescriptionandrendersittothescreen.Inparticular,renderreturnsareactelement,whichisalightweightdescriptionofwhattorender.MostreactdevelopersuseaspecialsyntaxcalledJSX,whichmakesiteasiertowritethesestructures.The<div/>syntaxistransformedatbuildtimetoReact.createElement('div').TheJSXexpression,<h1>HelloWorld</h1>,istransformedatbuildtimeintothefollowing:
returnReact.createElement('h1',null,'HelloWorld');
Thedifferencebetweentheclasscomponentandstatelessfunctioncomponentisthattheclasscomponentcancontaina statewhilethestateless(hencethename)functioncomponentcannot.
Therendermethodofthereactcomponentisallowedtoreturnonlyasinglenode.Ifyoudosomethinglikethefollowing:
return<h1>HelloWorld</h1><p>ReactRocks</p>
Youwillgetthefollowingerror:
Errorin./src/App.jsSyntaxerror:AdjacentJSXelementsmustbewrappedinanenclosingtag(4:31)
ThisisbecauseyouareessentiallyreturningtwoReact.createElement functions,andthatisnotvalidJavaScript.Whilethismayseemlikeadealbreaker,thisiseasytosolve.Wecanwrapournodesintoaparentnodeandreturnthatparentnodefromtherenderfunction.Wecancreateaparentdivandwrapothernodesunderit.Considerthefollowingexample:
render(){return(<div><h1>HelloWorld</h1><p>ReactRocks</p></div>)}
Componentsandprops
ComponentscanbeconceptuallyconsideredasJavaScriptfunctions.Theytakearbitrarynumberofinputslikenormalfunctions.Theseinputsarecalledprops.Toillustratethis,let'sconsiderthefollowingfunction:
functionGreet(props){return<h1>Hello,{props.name}</h1>;}
Thisisanormalfunctionandalsoavalidreactcomponent.IttakesaninputcalledpropsandreturnsavalidJSX.Wecanusethepropsinside JSXusingcurlybracesandpropertiessuchasnameusingastandardobjectnotation.NowthatGreetisafirstclassreactcomponent,let'suseitintherender()functionasfollows:
render(){return(return<Greetname="Joe"/>)}
WearecallingGreet()asanormalcomponentandpassingthis.propstoit.Itisrequiredtocapitalizeyourowncomponents.ReactconsiderscomponentnamesstartingwithalowercaseasstandardHTMLtagsandexpectscustomcomponentnamestostartwithacapitalletter.Aswesawearlier,wecancreateaclasscomponentusingES6class.ThiscomponentisasubclassofReact.component.AnequivalentcomponenttoourGreetfunctionisasfollows:
classGreetextendsReact.Component{render(){return<h1>Hello,{this.props.name}</h1>}}
Forallpracticalpurposes,wewillusethismethodofcreatingcomponents.Wewillsoonknowwhy.
Oneimportantpointtonoteisthatacomponentcannotmodifyitsownprops.Thismayseemlikealimitationbecause,inalmostallnon-trivialapplications,youwillwantuserinteractionswheretheUIcomponentstateischangedinreact,forexample,updatedateofbirthinaform,propsareread-onlybutthereisamuchrobustmechanismtohandleUIupdates.
State
Stateissimilartoprops,butitisprivateandfullycontrolledbythecomponent.Aswesawearlierthatbothfunctionalandclasscomponentsareequivalentinreact,oneimportantdistinctionisthatthestateisavailableonlyinclasscomponents.Hence,forallpracticalpurposes,wewilluseclasscomponents.
Wecanchangeourexistinggreetingexampletousestate,andwheneverthestatechanges,wewillupdateourGreetcomponenttoreflectthechangedvalue.
First,wewillsetupthestateinsideourApp.js,asfollows:
classGreetextendsReact.Component{constructor(props){super(props);
this.state={greeting:"thisisdefaultgreetingtext"}
}render(){return<h1>{this.state.greeting},{this.props.name}</h1>}}
Thereareafewimportantthingstonoticeinthisexample.First,wearecallingclassconstructortoinitializethis.state.Wealsocallthebaseclassconstructor,super(),andpasspropstoit.Aftercallingsuper(),weinitializeourdefaultstatebysettingthis.statetoanobject.Forexample,weassignagreetingpropertywithavaluehere.Intherendermethod,wewillusethispropertyusing{this.state.greeting}.Havingsetupourinitialstate,wecanaddUIelementstoupdatethisstate.Let'saddaninputbox,andonchangeofthatinputbox,wewillupdateourstateandthegreetingelement.Considerthefollowinglinesofcode:
classGreetextendsReact.Component{constructor(props){super(props);this.state={greeting:"thisisdefaultgreetingtext"}}
updateGreeting(event){this.setState({greeting:event.target.value,})}render(){
return(<div><inputtype="text"onChange={this.updateGreeting.bind(this)}/><h1>{this.state.greeting},{this.props.name}</h1></div>)}}
Here,weaddaninputboxandupdatethestateofthecomponentwhentheonChangemethodoftheinputboxisinvoked. WeuseacustomupdateGreeting()methodtoupdatethestatebycallingthis.setStateandupdatingtheproperty.Whenyourunthisexample,youwillnoticethatasyoutypesomethingonthetextbox,onlythegreetingelementisupdatedandnotthename.Takealookatthefollowingscreenshot:
Animportantfeatureofreactisthefactthatareactcomponentcanoutputorrenderotherreactcomponents.We'vegotaverysimplecomponenthere.Ithasastatewithavalueoftext.It'sgotanupdatemethodwhichwillupdatethatvalueoftextfromanevent.Whatwe'lldoiscreateanewcomponent.Thiswillbeastatelessfunctioncomponent.We'llcallitwidget.Itwilltakeinprops.We'llreturnthisJSXinputrighthere.Considerthefollowingcodesnippet:
render(){return(<div><Widgetupdate={this.updateGreeting.bind(this)}/>
<Widget update={this.updateGreeting.bind(this)} />
<Widgetupdate={this.updateGreeting.bind(this)}/><h1>{this.state.greeting},{this.props.name}</h1></div>)}
}constWidget=(props)=><inputtype="text"onChange={props.update}/>
First,weextractourinputelementintoastatelessfunctioncomponentandcallitaWidget.Wepasspropstothiscomponent.Then,wechangeonChangetouseprops.update.Now,insideourrendermethod,weusetheWidgetcomponentandpassapropupdatethatbindstheupdateGreeting()method.NowthatWidgetisacomponent,wecanreuseitanywhereintheGreetcomponent.WearecreatingthreeinstancesoftheWidget,andwhenanyoftheWidgetisupdated,thegreetingtextisupdated,asshowninthefollowingscreenshot:
Lifecycleevents
Whenyouhaveabunchofcomponentswithseveralstatechangesandevents,thehousekeepingbecomesimportant.Reactprovidesyouwithseveralcomponentlifecyclehookstohandlelifecycleeventsofcomponents.Understandingthecomponentlifecyclewillenableyoutoperformcertainactionswhenacomponentiscreatedordestroyed.Furthermore,itgivesyoutheopportunitytodecideifacomponentshouldbeupdatedinthefirstplace,andtoreacttopropsorstatechangesaccordingly.
Therearethreephasesthatthecomponentgoesthrough-mounting,updating,andunmouting.Foreachofthesestages,wehavehooks.Takealookatthefollowingdiagram:
Twomethodsarecalledwhenacomponentisinitiallyrendered,getDefaultPropsandgetInitialState,and, astheirnamessuggest,wecansetdefaultprops andinitialstateofacomponentinthesemethods.
ThecomponentWillMountiscalledbeforetherendermethodisexecuted.Wealreadyknowrendertobetheplacewherewereturnthecomponenttoberendered.Assoonastherendermethodfinishes,thecomponentDidMountmethodisinvoked.YoucanaccessDOMinthismethod,anditisrecommendedtoperformanyDOMinteractionsinthismethod.
Statechangesinvokeafewmethods.TheshouldComponentUpdatemethodisinvokedbeforetherendermethod,anditletsusdecideifweshouldallowrerenderingorskipit.Thismethodisnevercalledontheinitialrendering.ThecomponentWillUpdatemethod getscalledimmediatelyoncetheshouldComponentUpdatemethodreturnstrue.ThecomponentDidUpdatemethodisrenderedafterrenderfinishes.
Anychangetothepropsobjecttriggerssimilarmethodsasastatechange.OneadditionalmethodcallediscomponentWillReceiveProps;itiscalledonlywhenthepropshavechanged,anditisnotinitialrendering.Youcanupdatestatebasedonnewandoldpropsinthismethod.
WhenacomponentisremovedfromDOM,componentWillUnmountiscalled.Thisisausefulmethodtoperformcleanups.
Greatthingaboutreactisthatwhenyoustartusingit,theframeworkfeels verynaturaltoyou.Thereareveryfewmovingpartsyouwillneedtolearn,andtheabstractionisjustright.
SummaryThis chapter was aimed at some of the important new ideas that are gaining a lot of prominencelately.Bothreactiveprogrammingandreactcansignificantlyboostprogrammerproductivity.ReactisdefinitelyoneofthemostimportantemergingtechnologiesbackedbythelikesofFacebookandNetflix.
Thischapterwasintendedtogiveyouanintroductiontoboththesetechnologiesandhelpyoustartexploringtheminmoredetail.
Appendix A. Reserved WordsThis appendix provides two lists of reserved keywords as defined in ECMAScript 5 (ES5). Thefirstoneisthecurrentlistofwords,andthesecondisthelistofwordsreservedforfutureimplementations.
There'salsoalistofwordsthatarenolongerreserved,althoughtheyusedtobeinES3.
Youcannotusereservedwordsasvariablenames:
varbreak=1;//syntaxerror
Ifyouusethesewordsasobjectproperties,youhavetoquotethem:
varo={break:1};//OKinmanybrowsers,errorinIEvaro={"break":1};//AlwaysOKalert(o.break);//errorinIEalert(o["break"]);//OK
KeywordsThe list of words currently reserved in ES5 is as follows:
break
case
catch
continue
debugger
default
delete
do
else
finally
for
function
if
in
instanceof
new
return
switch
this
throw
try
typeof
var
void
while
with
ES6 reserved wordsThe following keywords are reserved in ES6:
class
const
enum
export
extends
implements
import
interface
let
package
private
protected
public
static
super
yield
Futurereservedwords
Thesekeywordsarenotusedcurrentlybuttheyarereservedforthefutureversions:
enum
await
Previously reserved wordsThe following words are no longer reserved starting with ES5, but it's best to stay away fromthemforthesakeofolderbrowsers:
abstract
boolean
byte
char
double
final
float
goto
int
long
native
short
synchronized
throws
transient
volatile
Appendix B. Built-in FunctionsThis appendix contains a list of the built-in functions (methods of the global object), discussed inChapter3,Functions:
Function Description
parseInt()
Takestwoparameters:aninputobjectandradix;thentriestoreturnanintegerrepresentationoftheinput.Doesn'thandleexponentsintheinput.Thedefaultradixis10(adecimalnumber).ReturnsNaNonfailure.Omittingtheradixmayleadtounexpectedresults(forexampleforinputssuchas08),soit'sbesttoalwaysspecifyit:
>parseInt('10e+3');10 >parseInt('FF');NaN>parseInt('FF',16);255
parseFloat()
Takesaparameterandtriestoreturnafloating-pointnumberrepresentationofit.Understandsexponentsintheinput:
>parseFloat('10e+3');10000>parseFloat('123.456test');123.456
isNaN()
Abbreviatedfrom"IsNotaNumber".Acceptsaparameterandreturnstrueiftheparameterisnotavalidnumber,falseotherwise.Attemptstoconverttheinputtoanumberfirst:
>isNaN(NaN);true>isNaN(123);false>isNaN(parseInt('FF'));true>isNaN(parseInt('FF',16));false
isFinite()
Returnstrueiftheinputisanumber(orcanbeconvertedtoanumber),butifitisnotanumberInfinityor-Infinity.Returnsfalseforinfinityornon-numericvalues:
>isFinite(1e+1000);false>isFinite(-Infinity);false>isFinite("123");true
encodeURIComponent()
ConvertstheinputintoaURL-encodedstring.FormoredetailsonhowURLencodingworks,refertotheWikipediaarticleathttp://en.wikipedia.org/wiki/Url_encode:
>encodeURIComponent('http://phpied.com/');"http%3A%2F%2Fphpied.com%2F">encodeURIComponent('somescript?key=v@lue');
"some%20script%3Fkey%3Dv%40lue"
decodeURIComponent()
TakesanURL-encodedstringanddecodesit:
>decodeURIComponent('%20%40%20');"@"
encodeURI()
URL-encodestheinput,butassumesafullURLisgiven,soreturnsavalidURLbynotencodingtheprotocol(forexample,http://)andhostname(forexample,www.phpied.com):
> encodeURI('http://phpied.com/');"http://phpied.com/">encodeURI('somescript?key=v@lue');"some%20script?key=v@lue"
decodeURI()
OppositeofencodeURI():
>decodeURI("some%20script?key=v@lue");"somescript?key=v@lue"
eval()
AcceptsastringofJavaScriptcodeandexecutesit.Returnstheresultofthelastexpressionintheinputstring.
Tobeavoidedwherepossible:
>eval('1+2');3 >eval('parseInt("123")');123>eval('newArray(1,2,3)');[1,2,3]>eval('newArray(1,2,3);1+2;');3
Appendix C. Built-in ObjectsThis appendix lists the built-in constructor functions outlined in the ECMAScript (ES) standard,togetherwiththepropertiesandmethodsoftheobjectscreatedbytheseconstructors.ES5-specificAPIsarelistedseparately.
ObjectObject() is a constructor that creates objects, for example:
>varo=newObject();
Thisisthesameasusingtheobjectliteral:
>varo={};//recommended
Youcanpassanythingto theconstructoranditwilltrytoguesswhatitisanduseamoreappropriateconstructor.Forexample,passingastringtonewObject()willbethesameasusingthenewString()constructor.Thisisnotarecommendedpractice(it'sbettertobeexplicitthanletguessescreepin),butstillpossible:
>varo=newObject('something');>o.constructor;functionString(){[nativecode]}>varo=newObject(123);>o.constructor;functionNumber(){[nativecode]}
Allotherobjects,built-inorcustom,inheritfrom Object.Sothepropertiesandmethodslistedinthefollowingsectionsapplytoalltypesofobjects.
MembersoftheObjectconstructor
FollowingarethemembersoftheObjectconstructor:
Property/method Description
Object.prototype
Theprototypeofallobjects(alsoanobjectitself).Anythingyouaddtothisprototypewillbeinheritedbyallotherobjects,sobecareful:
>vars=newString('noodles');>Object.prototype.custom=1;1>s.custom;1
TheObject.prototypemembers
Insteadofsaying"membersoftheObjectscreatedbytheObjectconstructor",let'ssayheadings"Object.prototypemembers".ItisthesameforArray.prototypeandsoonfurtherinthisappendix:
Property/method Description
constructor
Pointsbacktotheconstructorfunctionusedtocreatetheobject,inthiscase,Object:
>Object.prototype.constructor===Object;true>varo=newObject();>o.constructor===Object;true
toString(radix)
Returnsastringrepresentationoftheobject.IftheobjecthappenstobeaNumberobject,theradixparameterdefinesthebaseofthereturnednumber.Thedefaultradixis10:
>varo={prop:1};>o.toString();"[objectObject]">varn=newNumber(255);>n.toString();"255">n.toString(16);"ff"
toLocaleString()
SameastoString(),butmatchingthecurrentlocale.Meanttobecustomizedbyobjects,suchasDate(),Number(),andArray()andprovidelocale-specificvalues,suchasdifferentdateformatting.InthecaseofObject()instancesaswithmostothercases,itjustcallstoString().
Inbrowsers,youcanfigureoutthelanguageusingthepropertylanguage(oruserLanguageinIE)ofthenavigatorBOMobject:
>navigator.language;"en-US"
valueOf()
Returnsaprimitiverepresentationofthis,ifapplicable.Forexample,NumberobjectsreturnaprimitivenumberandDateobjectsreturnatimestamp.Ifnosuitableprimitivemakessense,itsimplyreturnsthis:
>varo={};>typeofo.valueOf();"object">o.valueOf()===o;true>varn=newNumber(101);
> typeof n.valueOf();"number">n.valueOf()===n;false>vard=newDate();>typeofd.valueOf();"number">d.valueOf();1357840170137
hasOwnProperty(prop)
Returnstrueifapropertyisanownpropertyoftheobject,orfalseifitwasinheritedfromtheprototypechain.Alsoreturnsfalseifthepropertydoesn'texist:
>varo={prop:1};>o.hasOwnProperty('prop');true>o.hasOwnProperty('toString');false>o.hasOwnProperty('fromString');false
isPrototypeOf(obj)
Returnstrueifanobjectisusedasaprototypeofanotherobject.Anyobjectfromtheprototypechaincanbetested,notonlythedirectcreator:
>vars=newString('');>Object.prototype.isPrototypeOf(s);true>String.prototype.isPrototypeOf(s);true>Array.prototype.isPrototypeOf(s);false
propertyIsEnumerable(prop)
Returnstrueifapropertyshowsupinafor...inloop:
>vara=[1,2,3];>a.propertyIsEnumerable('length');false>a.propertyIsEnumerable(0);true
ECMAScript5additionstoobjects
InECMAScript3allobjectpropertiescanbechanged,added,ordeletedatanytime,exceptforafewbuilt-inproperties(forexample,Math.PI).InES5youhavetheabilitytodefinepropertiesthatcannotbechangedordeleted,aprivilegepreviouslyreservedforbuilt-ins.ES5introducestheconceptofpropertydescriptorsthatgiveyoutightercontroloverthepropertiesyoudefine.
Thinkofapropertydescriptorasanobjectthatspecifiesthefeaturesofaproperty.Thesyntaxtodescribethesefeaturesisaregularobjectliteral, sopropertydescriptorshavepropertiesandmethodsoftheirown,butlet'scallthemattributestoavoidconfusion.Theattributesare:
value-whatyougetwhenyouaccessthepropertywritable-canyouchangethispropertyenumerable-shoulditappearinfor...inloopsconfigurable-canyoudeleteitset()-afunctioncalledanytimeyouupdatethevalueget()-calledwhenyouaccessthevalueoftheproperty
Furtherthere'sadistinctionbetweendatadescriptors(youdefinethepropertiesenumerable,configurable,value,andwritable)andaccessordescriptors(youdefineenumerable,configurable,set(),andget()).Ifyoudefineset()orget(),thedescriptorisconsideredanaccessorandattemptingtodefinevalueorwritablewillraiseanerror.
DefiningaregularoldschoolES3-styleproperty:
varperson={};person.legs=2;
SameusinganES5datadescriptor:
varperson={};Object.defineProperty(person,"legs",{value:2,writable:true,configurable:true,enumerable:true
});
Thevalueofvalueifsettoundefinedbydefault,allothersarefalse.Soyouneedtosetthemtotrueexplicitlyifyouwanttobeabletochangethispropertylater.
OrthesamepropertyusinganES5accessordescriptor:
varperson={};Object.defineProperty(person,"legs",{set:function(v){this.value=v;},get:function(v){returnthis.value;},
configurable:true,enumerable:true});person.legs=2;
Asyoucanseepropertydescriptorsarealotmorecode,soyouonlyusethemifyoureallywanttopreventsomeonefrommanglingyourproperty,andalsoyouforgetaboutbackwardscompatibilitywithES3browsersbecause,unlikeadditionstoArray.prototypeforexample,youcannot"shim"thisfeatureinoldbrowsers.
Andthepowerofthedescriptorsinaction(defininganonmalleableproperty):
>varperson={};>Object.defineProperty(person,'heads',{value:1});>person.heads=0;0>person.heads;1>deleteperson.heads;false>person.heads;
1
ThefollowingisalistofallES5additionstoObject:
Property/method Description
Object.getPrototypeOf(obj)
WhileinES3youhavetoguesswhatistheprototypeofagivenobjectisusingthemethodObject.prototype.isPrototypeOf(),inES5 youcandirectlyask"Whoisyourprototype?"
>Object.getPrototypeOf([])===Array.prototype;true
Object.create(obj,descr)
DiscussedinChapter7,Inheritance.Createsanewobject,setsitsprototypeanddefinespropertiesofthatobjectusingpropertydescriptors(discussedearlier):
>varparent={hi:'Hello'};>varo=Object.create(parent,{prop:{value:1}});>o.hi;"Hello"
Itevenletsyoucreateacompletelyblankobject,somethingyoucannotdoinES3:
> var o = Object.create(null);>typeofo.toString;"undefined"
Allowsyoutoinspecthowapropertywasdefined.Youcanevenpeekintothebuilt-ins and see all these previously hidden attributes:
>Object.getOwnProperty
Object.getOwnPropertyDescriptor(obj,property)
Descriptor(Object.prototype,'toString');Objectconfigurable:trueenumerable:falsevalue:functiontoString(){[nativecode]}writable:true
Object.getOwnPropertyNames(obj)
Returnsanarrayofallownpropertynames(asstrings),enumerableornot.UseObject.keys()togetonlyenumerableones:
>Object.getOwnPropertyNames(Object.prototype);["constructor","toString","toLocaleString","valueOf",...
Object.defineProperty(obj,descriptor)
Definesapropertyofanobjectusingapropertydescriptor.Seethediscussionprecedingthistable.
Object.defineProperties(obj,descriptors)
SameasdefineProperty(),butletsyoudefinemultiplepropertiesatonce:
>varglass=Object.defineProperties({},{"color":{value:"transparent",writable:true
},"fullness":{value:"half",writable:false}});>glass.fullness;"half"
Object.preventExtensions(obj)
Object.isExtensible(obj)
preventExtensions()disallowsaddingfurtherpropertiestoanobjectandisExtensible()checkswhetheryoucanaddproperties:
>vardeadline={};>Object.isExtensible(deadline);true>deadline.date="yesterday";"yesterday">Object.preventExtensions(deadline);>Object.isExtensible(deadline);false>deadline.date="today";
"today">deadline.date;"today"
Attemptingtoaddpropertiestoanon-extensibleobjectisnotanerror,butsimplydoesn'twork:
>deadline.report=true;>deadline.report;undefined
Object.seal(obj)
Object.isSealed(obj)
seal()doesthesameaspreventExtensions()andadditionallymakesallexistingpropertiesnon-configurable.
Thismeansyoucanchangethevalueofanexistingproperty,butyoucannotdeleteitorreconfigureit(usingdefineProperty()won'twork).Soyoucannot,forexample,makeanenumerablepropertynon-enumerable.
Object.freeze(obj)
Object.isFrozen(obj)
Everythingthatseal()doespluspreventschangingthevaluesofproperties:
>vardeadline=Object.freeze({date:"yesterday"});>deadline.date="tomorrow";>deadline.excuse="lame";
> deadline.date;"yesterday">deadline.excuse;undefined>Object.isSealed(deadline);true
Object.keys(obj)
Analternativetoafor...inloop.Returnsonlyownproperties(unlikefor...in).Thepropertiesneedtobeenumerableinordertoshowup(unlikeObject.getOwnPropertyNames()).Thereturnvalueisanarrayofstrings.
>Object.prototype.customProto=101;>Object.getOwnPropertyNames(
Object.prototype);
["constructor","toString",...,"customProto"]
>Object.keys(Object.prototype);
["customProto"]>varo={own:202};>o.customProto;101>Object.keys(o);"own"]
ES6 addition to objectsES6 has a few interesting object definition and property syntax. This new syntax is to makeworkingwithobjectseasierandconcise.
Propertyshorthand
ES6providesashortersyntaxforcommonobjectdefinition.
ES5:obj={x:x,y:y};
ES6:obj={x,y};
Computedpropertynames
Itispossibletocompute propertynamesinthenewES6objectdefinitionsyntax:
let obj = {foo:"bar",["baz"+q()]:42}
Herethepropertynameiscomputedwhere"baz"isconcatenatedwiththeresultofthefunctioncall.
Object.assign
TheObject.assign()methodisusedtocopythevaluesofallenumerableownpropertiesfromoneormoresourceobjectstoatargetobject:
var dest = { quux: 0 }varsrc1={foo:1,bar:2}varsrc2={foo:3,baz:4}Object.assign(dst,src1,src2)
ArrayThe Array constructor creates array objects:
>vara=newArray(1,2,3);
Thisisthesameasthearrayliteral:
>vara=[1,2,3];//recommended
WhenyoupassonlyonenumericvaluetotheArrayconstructor,it'sassumedtobethearraylength:
>varun=newArray(3);>un.length;3
Yougetanarraywiththedesiredlengthandifyouaskforthevalueofeachofthearrayelements,you get undefined:
>un;[undefined,undefined,undefined]
Thereisasubtledifferencebetweenanarrayfullofelementsandarraywithnoelements,butjustlength:
>'0'ina;true>'0'inun;false
ThisdifferenceintheArray()constructor'sbehaviorwhenyouspecifyoneversusmoreparameterscanleadtounexpectedbehavior.Forexample,thefollowinguseofthearrayliteralisvalid:
>vara=[3.14];>a;[3.14]
However, passing the floating-point number to the Array constructor is an error:
>vara=newArray(3.14);RangeError:invalidarraylength
TheArray.prototypemembers
ThefollowingarethelistofalltheelementsofanArray:
Property/method Description
length
Thenumberofelementsinthearray:
>[1,2,3,4].length;4
concat(i1,i2,i3,...)
Mergesarraystogether:
>[1,2].concat([3,5],[7,11]);[1,2,3,5,7,11]
join(separator)
Turnsanarrayintoastring.Theseparatorparameterisastringwithcommaasthedefaultvalue:
>[1,2,3].join();"1,2,3">[1,2,3].join('|');"1|2|3">[1,2,3].join('islessthan');"1islessthan2islessthan3"
pop()
Removesthelastelementofthearrayand returnsit:
>vara=['une','deux','trois'];>a.pop();"trois">a; ["une","deux"]
push(i1,i2,i3,...)
Appendselementstotheendofthearrayandreturnsthelengthofthemodifiedarray:
>vara=[];>a.push('zig','zag','zebra','zoo');4
reverse()
Reversestheelementsofthearrayandreturnsthemodifiedarray:
>vara=[1,2,3];>a.reverse();[3,2,1]>a; [3,2,1]
shift()
Likepop()butremovesthefirstelement,notthelast:
>vara=[1,2,3];>a.shift();1>a; [2,3]
slice(start_index,end_index)
Extractsapieceofthearrayandreturnsit asanewarray,withoutmodifyingthesourcearray:
>vara=['apple','banana','js','css','orange'];>a.slice(2,4);["js","css"]>a; ["apple","banana","js","css","orange"]
sort(callback)
Sortsanarray.Optionallyacceptsacallbackfunctionforcustomsorting.Thecallbackfunctionreceivestwoarrayelementsasargumentsandshouldreturn0iftheyareequal,apositivenumberifthefirstisgreater,andanegativenumberifthesecondisgreater.
Anexampleofacustomsortingfunctionthatdoesapropernumericsort(sincethedefaultischaractersorting):
functioncustomSort(a,b){ if(a>b)return1; if(a<b)return-1; return0;}Exampleuseofsort():>vara=[101,99,1,5];>a.sort();[1, 101,5,99]>a.sort(customSort);[1,5,99,101]>[7,6,5,9].sort(customSort);[5,6,7,9]
splice(start,delete_count,i1,i2,i3,...)
Removesandaddselementsatthesametime.Thefirstparameteriswheretostartremoving,thesecondishowmanyitemstoremoveandtherestoftheparametersarenewelementstobeinsertedintheplaceoftheremovedones:
>vara=['apple','banana','js','css','orange'];>a.splice(2,2,'pear','pineapple');["js","css"]>a; ["apple","banana","pear","pineapple","orange"]
unshift(i1,i2,i3,...)
Likepush()butaddstheelementsatthebeginningofthearrayasopposedtotheend.Returnsthelengthofthemodifiedarray:
>vara=[1,2,3];>a.unshift('one','two');5>a; ["one","two",1,2,3]
ECMAScript5additionstoArray
FollowingaretheECMAScript5additionstoArray:
Property/method Description
Array.isArray(obj)
Tellsifanobjectisanarraybecausetypeofisnotgoodenough:
>vararraylike={0:101,length:1};>typeofarraylike;"object">typeof[];"object"
Neitherisduck-typing(ifitwalkslikeaduckandquackslikeaduck,itmustbeaduck):
typeofarraylike.length;"number"
InES3youneedtheverbose:
>Object.prototype.toString.call([])==="[objectArray]";true>Object.prototype.toString.call(arraylike)==="[objectArray]";false
InES5yougettheshorter:
Array.isArray([]);trueArray.isArray(arraylike);false
Array.prototype.indexOf(needle,idx)
Searchesthearrayandreturnstheindexofthefirstmatch.Returns-1ifthere'snomatch.Optionallycansearchstartingfromaspecifiedindex:
>varar=['one','two','one','two'];>ar.indexOf('two');1>ar.indexOf('two',2);3>ar.indexOf('toot');-1
Array.prototype.lastIndexOf(needle,idx)
LikeindexOf()onlysearchesfromtheend:
>varar=['one','two','one','two'];>ar.lastIndexOf('two');3>ar.lastIndexOf('two',2);1>ar.indexOf('toot');-1
Array.prototype.forEach(callback,this_obj)
Analternativetoaforloop.Youspecifyacallbackfunctionthatwillbecalledforeachelementofthe array.Thecallbackfunctiongetsthearguments:theelement,itsindexandthewholearray:
>varlog=console.log.bind(console);>varar=['itsy','bitsy','spider'];>ar.forEach(log);itsy0["itsy","bitsy","spider"]bitsy1["itsy","bitsy","spider"]spider2["itsy","bitsy","spider"]
Optionally,youcanspecifyasecondparameter:theobjecttobeboundtothisinsidethecallbackfunction.Sothisworkstoo:
>ar.forEach(console.log,console);
Array.prototype.every(callback,this_obj)
Youprovideacallbackfunctionthattestseachelementofthearray.YourcallbackisgiventhesameargumentsasforEach()anditmustreturntrueorfalsedependingonwhetherthegivenelementsatisfiesyourtest.
Ifallelementssatisfyyourtest,every()returnstrue.Ifatleastonedoesn't,every()returnsfalse:
> function hasEye(el, idx,ar){returnel.indexOf('i')!==-1;}>['itsy','bitsy','spider'].every(hasEye);true>['eency','weency','spider'].every(hasEye);false
Ifatsomepointduringtheloopitbecomesclearthattheresultwillbefalse,theloopstopsandreturnsfalse:
>[1,2,3].every(function(e){console.log(e);returnfalse;});1false
Array.prototype.some(callback,this_obj)
Likeevery(),onlyitreturnstrueifatleastoneelementsatisfiesyourtest:
>['itsy','bitsy','spider'].some(hasEye);true>['eency','weency','spider'].some(hasEye);true
Array.prototype.filter(callback,this_obj)
Similartosome()andevery()butitreturnsanewarrayofallelementsthatsatisfyyourtest:
>['itsy','bitsy','spider'].filter(hasEye);["itsy","bitsy","spider"]>['eency','weency','spider'].filter(hasEye);["spider"]
Array.prototype.map(callback,this_obj)
SimilartoforEach()becauseitexecutesacallbackforeachelement,butadditionallyitconstructsanewarraywiththereturnedvaluesofyourcallbackandreturnsit.Let'scapitalizeallstringsinanarray:
>functionuc(element,index,array){returnelement.toUpperCase();}>['eency','weency','spider'].map(uc);["EENCY","WEENCY","SPIDER"]
Array.prototype.reduce(callback,start)
Executesyourcallbackforeachelementofthearray.Yourcallbackreturnsavalue.Thisvalueispassedbacktoyourcallbackwiththenextiteration.Thewholearrayiseventuallyreducedtoasinglevalue:
>functionsum(res,element,idx,arr){returnres+element;}>[1,2,3].reduce(sum);6
Optionallyyoucanpassastartvaluewhichwillbeusedbythefirstcallbackcall:
>[1,2,3].reduce(sum,100);106
Array.prototype.reduceRight(callback,start)
Likereduce()butloopsfromtheendofthearray:
>functionconcat(result_so_far,el){
return "" + result_so_far+el;}>[1,2,3].reduce(concat);"123">[1,2,3].reduceRight(concat);"321"
ES6additiontoarrays
Followingaretheadditiontoarrays:
Array.from(arrayLike, mapFunc?,thisArg?)
TheArray.from()method'sbasicfunctionalityistoconverttwokindsofvaluestoarrays-arrayLikevaluesandIterablevalues:
constarrayLike={length:2,0:'a',1:'b'};constarr =Array.from(arrayLike);for(constxofarr){//OK,iterableconsole.log(x);}//Output://a//b
Array.of(...items)
Createsanarrayoutoftheitemspassedtothemethod
leta=Array.of(1,2,3,'foo');console.log(a);//[1,2,3,"foo"]
Array.prototype.entries()
Array.prototype.keys()
Array.prototype.values()
The result of these methods is a sequence of values. These methods returns aniteratorofkeys,valuesandentriesrespectively.
leta=Array.of(1,2,3,'foo');letk,v,e;for(kof a.keys()){
console.log(k); //0 1 2 3}for(vof a.values()){console.log(v);//123foo}for(eof a.entries()){console.log(e);}//[[0,1],[1,2],[2,3][3,'foo']]
Array.prototype.find(predicate,thisArg?)
Returnsthefirstarrayelementforwhichthecallbackfunctionreturnstrue.Ifthereisnosuchelement,itreturnsundefined:
[1,-2,3].find(x=>x<0)//-2
Array.prototype.findIndex(predicate,thisArg?)
Returnstheindexofthefirstelementforwhichthecallbackfunctionreturnstrue.Ifthereisnosuchelement,itreturns-1:
[1, -2, 3].find(x => x < 0)//1
Itfillsanarraywiththegivenvalue:
Array.prototype.fill(value:any,start=0,end=this.length):This
constarr=['a','b','c'];arr.fill(7)[7,7,7]Youcanspecifystartandendranges.['a','b','c'].fill(7,1,2)['a',7,'c' ]
FunctionJavaScript functions are objects. They can be defined using the Function constructor, like so:
varsum=newFunction('a','b','returna+b;');
Thisisa(generallynotrecommended)alternativetothefunctionliteral(alsoknownasfunctionexpression):
varsum=function(a,b){returna+b;};
Or,themorecommonfunctiondefinition:
functionsum(a,b){returna+b;}
TheFunction.prototypemembers
FollowingarethelistofmembersoftheFunctionconstructor:
Property/Method Description
apply(this_obj,params_array)
Allowsyoutocallanotherfunctionwhileoverwritingtheotherfunction'sthisvalue.Thefirstparameterthatapply()acceptsistheobjecttobeboundtothisinsidethefunctionandthesecondisanarrayofargumentstobesenttothefunctionbeingcalled:
functionwhatIsIt(){returnthis.toString();}>varmyObj={};>whatIsIt.apply(myObj);"[objectObject]">whatIsIt.apply(window);
"[object Window]"
call(this_obj,p1,p2,p3,...) Sameasapply()butacceptsargumentsonebyone,asopposedtoasonearray.
length
Thenumberofparametersthefunctionexpects:
>parseInt.length;2
Ifyouforgetthedifferencebetweencall()andapply():
>Function.prototype.call.length;1>Function.prototype.apply.length;2
Thecall() property'slengthis1becauseallargumentsexceptthefirstoneareoptional.
ECMAScript5additionstoaFunction
FollowingaretheECMAScript5additiontoaFunctionconstructor:
Property/method Description
Function.prototype.bind()
Whenyouwanttocallafunctionthatusesthisinternallyandyouwanttodefinewhatthisis.Themethodscall()andapply()invokethefunctionwhilebind()returnsanewfunction.Usefulwhenyouprovideamethodasacallbacktoamethodofanotherobjectandandyouwantthistobeanobjectofyourchoice:
>whatIsIt.apply(window); "[objectWindow]"
ECMAScript6additionstoaFunction
FollowingaretheECMAScript6additiontoaFunctionconstructor:
ArrowFunctions
Anarrowfunctionexpressionhasashortersyntaxcomparedtofunctionexpressionsanddoesnot binditsownthis,arguments,super,ornew.target.Arrowfunctionsarealwaysanonymous.
()=>{...}//noparameterx=>{...}//oneparameter,anidentifier(x,y)=>{...}//severalparametersconstsquares=[1,2,3].map(x=>x*x);
StatementBodiesaremoreexpressiveandconciseclosuresyntax
arr.forEach(v=>{if(v%5===0)filtered:ist.push(v)})
BooleanThe Boolean constructor creates Boolean objects (not to be confused with Boolean primitives).The Boolean objects are not that useful and are listed here for the sake of completeness.
>varb=newBoolean();>b.valueOf();false>b.toString();"false"
ABooleanobjectisnotthesameasaBooleanprimitivevalue.Asyouknow,allobjectsaretruthy:
>b===false;false>typeofb;"object"
Booleanobjectsdon'thaveanypropertiesotherthantheonesinheritedfromObject.
NumberThis creates number objects:
>varn=newNumber(101);>typeofn;"object">n.valueOf();101
TheNumberobjectsarenotprimitiveobjects,butifyouuseanyNumber.prototypemethodonaprimitivenumber,theprimitivewillbeconvertedtoaNumberobjectbehindthescenesandthecodewillwork.
>varn=123;>typeofn;"number">n.toString();"123"
Usedwithoutnew,theNumberconstructorreturnsaprimitivenumber.
>Number("101");101>typeofNumber("101");"number">typeofnewNumber("101");"object"
MembersoftheNumberconstructor
ConsiderthefollowingmembersoftheNumberconstructor:
Property/method Description
Number.MAX_VALUE
Aconstantproperty(cannotbechanged)thatcontainsthemaximumallowednumber:
>Number.MAX_VALUE; 1.7976931348623157e+308
Number.MIN_VALUE
ThesmallestnumberyoucanworkwithinJavaScript:
>Number.MIN_VALUE; 5e-324
Number.NaN
ContainstheNotANumbernumber.SameastheglobalNaN:
>Number.NaN; NaN
NaNisnotequaltoanythingincludingitself:
>Number.NaN===Number.NaN; false
Number.POSITIVE_INFINITY SameastheglobalInfinitynumber.
Number.NEGATIVE_INFINITY Sameas-Infinity.
TheNumber.prototypemembers
FollowingarethemembersoftheNumberconstructor:
Property/method Description
toFixed(fractionDigits)
Returnsastringwiththefixed-pointrepresentationofthenumber.Roundsthereturnedvalue:
>varn=newNumber(Math.PI);>n.valueOf();3.141592653589793>n.toFixed(3);"3.142"
toExponential(fractionDigits)
Returnsastringwithexponentialnotationrepresentationofthenumberobject.Roundsthereturnedvalue:
>varn=newNumber(56789);>n.toExponential(2);"5.68e+4"
toPrecision(precision)
Stringrepresentationofanumberobject,eitherexponentialorfixed-point,dependingonthenumberobject:
>varn=newNumber(56789);>n.toPrecision(2);"5.7e+4">n.toPrecision(5);"56789">n.toPrecision(4);"5.679e+4">varn=newNumber(Math.PI);>n.toPrecision(4);"3.142"
StringThe String() constructor creates string objects. Primitive strings are turned into objects behindthe scenes if you call a method on them as if they were objects. Omitting new gives you primitivestrings.
Creatingastringobjectandastringprimitive:
>vars_obj=newString('potatoes');>vars_prim='potatoes';>typeofs_obj;"object">typeofs_prim;"string"
Theobjectandtheprimitivearenotequalwhencomparedbytypewith===,buttheyarewhencomparedwith==whichdoestypecoercion:
>s_obj===s_prim;false>s_obj==s_prim;true
lengthisapropertyofthestringobjects:
>s_obj.length;8
Ifyouaccesslengthonaprimitivestring,theprimitiveisconvertedtoanobjectbehindthescenesandtheoperation issuccessful:
>s_prim.length;8
Stringliteralsworkfinetoo:
>"giraffe".length;7
MembersoftheStringconstructor
FollowingarethemembersoftheStringconstructor:
Property/method Description
String.fromCharCode(code1,code2,code3,...)
ReturnsastringcreatedusingtheUnicodevaluesoftheinput:
>String.fromCharCode(115,99,114,105,112,116);"script"
TheString.prototypemembers
ConsiderthefollowingString.prototypemembers:
Property/method Description
length
Thenumberofcharactersinthestring:
> newString('four').length;4
charAt(position)
Returnsthecharacteratthespecifiedposition.Positionsstartat0:
> "script".charAt(0);"s"
SinceES5,it'salsopossibletousearraynotationforthesamepurpose.(Thisfeaturehasbeenlongsupported in many browsers before ES5, but not IE):
> "script"[0];"s"
charCodeAt(position)
Returnsthenumericcode(Unicode)ofthecharacteratthespecifiedposition:
> "script".charCodeAt(0);115
concat(str1,str2,....)
Returnsanewstringgluedfromtheinputpieces:
> "".concat('zig','-','zag');"zig-zag"
indexOf(needle,start)
Iftheneedlematchesapartofthestring,thepositionofthematchisreturned.Theoptionalsecondparameterdefineswherethesearchshouldstartfrom.Returns-1ifnomatchisfound:
> "javascript".indexOf('scr');4 > "javascript".indexOf('scr',5);-1
lastIndexOf(needle,start)
Same as indexOf() but starts the search from the end of the string. The last occurrence of a:
> "javascript".lastIndexOf('a');3
localeCompare(needle)
Comparestwostringsinthecurrentlocale.Returns0ifthetwostringsareequal,1iftheneedlegetssortedbeforethestringobject,-1otherwise:
> "script".localeCompare('crypt');1 > "script".localeCompare('sscript');-1> "script".localeCompare('script');0
match(regexp)
Acceptsaregularexpressionobjectandreturnsanarrayofmatches:
> "R2-D2andC-3PO".match(/[0-9]/g);["2","2","3"]
replace(needle,replacement)
Allowsyoutoreplacethematchingresultsofaregexppattern.Thereplacementcanalsobeacallbackfunction.Capturinggroupsareavailableas$1,$2,...$9:
> "R2-D2".replace(/2/g,'-two');"R-two-D-two"> "R2-D2".replace(/(2)/g,'$1$1');"R22-D22"
search(regexp)
Returns the position of the first regular expression match:
> "C-3PO".search(/[0-9]/);2
slice(start,end)
Returnsthepartofastringidentifiedbythestartandendpositions.Ifstartisnegative,thestartpositionislength+start,similarlyiftheendparameterisnegative,theendpositionislength+end:
> "R2-D2andC-3PO".slice(4,13);"2andC-3"> "R2-D2andC-3PO".slice(4,-1);"2andC-3P"
split(separator,limit)
Turnsastringintoanarray.Thesecondparameter,limit,isoptional.Aswithreplace(),search(),andmatch(),theseparatorisaregularexpressionbutcanalsobeastring:
> "1,2,3,4".split(/,/);["1","2","3","4"]> "1,2,3,4".split(',',2);["1","2"]
substring(start,end)
Similartoslice().Whenstartorendarenegativeorinvalid,theyareconsidered0.Iftheyaregreaterthanthestringlength,theyareconsideredtobethelength.Ifendisgreaterthanstart,theirvaluesareswapped.
> "R2-D2andC-3PO".substring(4,13);"2 and C-3"
> "R2-D2andC-3PO".substring(13,4);"2andC-3"
toLowerCase()
toLocaleLowerCase()
Transformsthestringtolowercase:
> "Java".toLowerCase();"java"
toUpperCase()
toLocaleUpperCase()
Transformsthestringtouppercase:
> "Script".toUpperCase();"SCRIPT"
ECMAScript5additionstoString
FollowingaretheECMAScript5additionstoString:
Property/method Description
String.prototype.trim()
Insteadofusingaregularexpressiontoremovewhitespacebeforeandafterastring(asinES3),youhaveatrim()methodinES5.
>"\tbeard\n".trim(); "beard" OrinES3:
> " \t beard \n".replace(/\s/g, ""); "beard"
ECMAScript6additionstoString
FollowingarethelistofalltheECMAScript6additionstoString:
TemplateLiteralsareusedtointerpolatesingleormulti-linestrings.
Templateliteralsareenclosedbytheback-tick(``)(graveaccent)characterinsteadofdoubleorsinglequotes.Templateliteralscancontainplaceholders.TheseareindicatedbytheDollarsignandcurlybraces(${expression}).Theexpressionsintheplaceholdersandthetextbetweenthemgetpassedtoafunction.Thedefaultfunctionjustconcatenatesthepartsintoasinglestring.
vara=5;varb=10;console.log(`Fifteenis${a+b}`);
String.prototype.repeat-thismethodallowsyoutorepeatastringnnumberoftimes"".repeat(4*depth)"foo".repeat(3)
String.prototype.startsWith
String.prototype.endsWith
String.prototype.includes
Thesearenewstringsearchingmethods
"hello".startsWith("ello",1)//true"hello".endsWith("hell",4)//true"hello".includes("ell")//true"hello".includes("ell",1)//true"hello".includes("ell",2)//false
DateThe Date constructor can be used with several types of input:
Youcanpassvaluesforyear,month,dateof themonth,hour,minute,second,andmillisecond,likeso:
>newDate(2015,0,1,13,30,35,505);ThuJan01201513:30:35GMT-0800(PST)
Youcanskipanyoftheinputparameters,inwhichcasetheyareassumedtobe0.Notethatmonthvaluesarefrom0(January)to11(December),hoursarefrom0to23,minutesandseconds0to59,andmilliseconds0to999.Youcanpassatimestamp:
>newDate(1420147835505);ThuJan01201513:30:35GMT-0800(PST)
Ifyoudon'tpassanything,thecurrentdate/timeisassumed:
>newDate();FriJan11201312:20:45GMT-0800(PST)
Ifyoupassastring, it'sparsedinanattempttoextractapossibledatevalue:
>newDate('May4,2015');MonMay04201500:00:00GMT-0700(PDT)
Omittingnewgivesyouastringversionofthecurrentdate:
>Date()===newDate().toString();true
MembersoftheDateconstructor
FollowingarethemembersoftheDateconstructor:
Property/method Description
Date.parse(string)
SimilartopassingastringtonewDate()constructor,thismethodparsestheinputstringinattempttoextractavaliddatevalue.Returnsatimestamponsuccess,NaNonfailure:
>Date.parse('May5, 2015');1430809200000>Date.parse('4th');
NaN
Date.UTC(year,month,date,hours,minutes,seconds,ms)
ReturnsatimestampbutinUTC(CoordinatedUniversalTime),notinlocaltime.
>Date.UTC(2015,0,1,13,30, 35,505);1420119035505
TheDate.prototypemembers
FollowingarethelistofDate.prototypemembers:
Property/method Description/example
toUTCString()
SameastoString()butinuniversaltime.Here'showPacificStandard(PST)localtimediffersfromUTC:
>vard=newDate(2015,0,1);>d.toString();"ThuJan01201500:00:00GMT-0800(PST)">d.toUTCString();"Thu,01Jan201508:00:00GMT"
toDateString()
ReturnsonlythedateportionoftoString():
>newDate(2015,0,1).toDateString();"ThuJan012010"
toTimeString()
ReturnsonlythetimeportionoftoString():
>newDate(2015,0,1).toTimeString();"00:00:00GMT-0800(PST)"
toLocaleString()
toLocaleDateString()
toLocaleTimeString()
EquivalenttotoString(),toDateString(),andtoTimeString()respectively,butinafriendlierformat, according to the current user's locale:
>newDate(2015,0,1).toString();"ThuJan01201500:00:00GMT-0800(PST)">newDate(2015,0,1).toLocaleString();"1/1/201512:00:00AM"
getTime()
setTime(time)
Getorsetthetime(usingatimestamp)ofadateobject.Thefollowingexamplecreatesadateandmovesitonedayforward:
>vard=newDate(2015,0,1);>d.getTime();1420099200000>d.setTime(d.getTime()+ 1000*60*60*24);1420185600000
> d.toLocaleString();"FriJan02201500:00:00 GMT-0800(PST)"
getFullYear()
getUTCFullYear()
setFullYear(year,month,date)
setUTCFullYear(year,month,date)
GetorsetafullyearusinglocalorUTCtime.ThereisalsogetYear()butitisnotY2Kcompliant,souse getFullYear() instead:
>vard=newDate(2015,0,1);>d.getYear();115>d.getFullYear();2015>d.setFullYear(2020);1577865600000>d;
WedJan01202000:00:00GMT-0800 (PST)
getMonth()
getUTCMonth()
setMonth(month,date)
setUTCMonth(month,date)
Getorsetmonth,startingfrom0(January):
>vard=newDate(2015,0,1);>d.getMonth();0>d.setMonth(11);1448956800000>d.toLocaleDateString();"12/1/2015"
getDate()
getUTCDate()
setDate(date)
setUTCDate(date)
Getorsetdateofthemonth.
>vard=newDate(2015,0,1);>d.toLocaleDateString();"1/1/2015">d.getDate();1>d.setDate(31);1422691200000>d.toLocaleDateString();"1/31/2015"
getHours()
getUTCHours()
setHours(hour,min,sec,ms)
setUTCHours(hour,min,sec,ms)
getMinutes()
getUTCMinutes()
setMinutes(min,sec,ms)
setUTCMinutes(min,sec,ms)
getSeconds()
getUTCSeconds()
setSeconds(sec,ms)
setUTCSeconds(sec,ms)
getMilliseconds()
getUTCMilliseconds()
setMilliseconds(ms)
setUTCMilliseconds(ms)
Get/Sethour,minutes,seconds,milliseconds,allstartingfrom0:
>vard=newDate(2015,0,1);> d.getHours() + ':' + d.getMinutes();
"0:0">d.setMinutes(59);1420102740000>d.getHours()+':'+d.getMinutes();"0:59"
Returnsthedifferencebetweenlocalanduniversal(UTC)time,measuredinminutes.ForexamplethedifferencebetweenPST(PacificStandardTime)andUTC:
>newDate().getTimezoneOffset();
getTimezoneOffset() 480>420/60;//hours8
getDay()
getUTCDay()
Returns the day of the week, starting from 0 (Sunday):
>vard=newDate(2015,0,1);>d.toDateString();"ThuJan012015">d.getDay();4>vard=newDate(2015,0,4);>d.toDateString();"SatJan042015">d.getDay();0
ECMAScript5additionstoDate
FollowingaretheadditionstotheDateconstructor:
Property/method Description
Date.now()
Aconvenientwaytogetthecurrenttimestamp:
>Date.now()===newDate().getTime();true
Date.prototype.toISOString()
YetanothertoString():
>vard=newDate(2015,0,1);>d.toString();"ThuJan01201500:00:00GMT-0800(PST)">d.toUTCString();"Thu,01Jan201508:00:00GMT">d.toISOString();"2015-01-01T00:00:00.000Z"
Date.prototype.toJSON()
UsedbyJSON.stringify()(refertotheendofthisappendix)andreturnsthesameastoISOString():
>vard=newDate();>d.toJSON()===d.toISOString();true
MathMath is a different from the other built-in objects because it cannot be used as a constructor tocreate objects. It's just a collection of static functions and constants. Some examples to illustratethedifferenceareasfollows:
>typeofDate.prototype;"object">typeofMath.prototype;"undefined">typeofString;"function">typeofMath;"object"
MembersoftheMathobject
FollowingarethemembersoftheMathobject:
Property/method Description
Math.E
Math.LN10
Math.LN2
Math.LOG2E
Math.LOG10E
Math.PI
Math.SQRT1_2
Math.SQRT2
Thesearesomeusefulmathconstants,allread-only.Herearetheirvalues:
>Math.E;2.718281828459045>Math.LN10;2.302585092994046>Math.LN2;0.6931471805599453
> Math.LOG2E;1.4426950408889634>Math.LOG10E;0.4342944819032518>Math.PI;3.141592653589793>Math.SQRT1_2;0.7071067811865476>Math.SQRT2;1.4142135623730951
Math.acos(x)
Math.asin(x)
Math.atan(x)
Math.atan2(y,x)
Math.cos(x)
Math.sin(x)
Math.tan(x)
Trigonometricfunctions
Math.round(x)
Math.floor(x)
Math.ceil(x)
round()givesyouthenearestinteger,ceil()roundsup,andfloor()roundsdown:
>Math.round(5.5);6>Math.floor(5.5);5>Math.ceil(5.1);
6
Math.max(num1,num2,num3,...)
Math.min(num1,num2,num3,...)
max()returnsthelargestandmin()returnsthesmallestofthenumberspassedtothemasarguments.IfatleastoneoftheinputparametersisNaN,theresultisalsoNaN:
>Math.max(4.5,101,Math.PI);101>Math.min(4.5,101,Math.PI);3.141592653589793
Absolutevalue:
>Math.abs(-101);
Math.abs(x) 101>Math.abs(101);101
Math.exp(x)
Exponentialfunction:Math.Etothepowerofx:
>Math.exp(1)===Math.E;true
Math.log(x)
Naturallogarithmofx:
>Math.log(10)===Math.LN10;true
Math.sqrt(x)
Squarerootofx:
>Math.sqrt(9);3>Math.sqrt(2)===Math.SQRT2;true
Math.pow(x,y)
xtothepowerofy:
>Math.pow(3,2);9
Math.random()
Randomnumberbetween0and1(including0).
>Math.random();0.8279076443185321
For an random integer in a range,saybetween10and100:>Math.round(Math.random()*90+10);79
RegExpYou can create a regular expression object using the RegExp() constructor. You pass theexpression pattern as the first parameter and the pattern modifiers as the second:
>varre=newRegExp('[dn]o+dle','gmi');
Thismatches"noodle","doodle","doooodle",andsoon.It'sequivalenttousingtheregularexpressionliteral:
>varre=('/[dn]o+dle/gmi');//recommended
Chapter4,ObjectsandAppendixD,RegularExpressionscontainmoreinformationonregularexpressionsandpatterns.
TheRegExp.prototypemembers
FollowingaretheRegExp.prototypemembers:
Property/method Description
global Read-onlytrueifthegmodifierwassetwhencreatingtheregexpobject.
ignoreCase Read-only.trueiftheimodifierwassetwhencreatingtheregexpobject.
multiline Read-only.trueifthemmodifierwassetwhencreatingtheregexpobject
lastIndex
Containsthepositioninthestringwherethenextmatchshouldstart.test()andexec()setthispositionafterasuccessful match.Onlyrelevantwhentheg(global)modifierwasused:
>varre=/[dn]o+dle/g;>re.lastIndex;0>re.exec("noodledoodle");["noodle"]>re.lastIndex;6>re.exec("noodledoodle");["doodle"]>re.lastIndex;13>re.exec("noodledoodle");null>re.lastIndex;0
source
Read-only.Returnstheregularexpressionpattern(withoutthemodifiers):
>varre=/[nd]o+dle/gmi;>re.source;"[nd]o+dle"
exec(string)
Matchestheinputstringwiththeregularexpression.Onasuccessfulmatchreturnsanarraycontainingthematchandanycapturinggroups.Withthegmodifier,itmatchesthefirstoccurrenceandsetsthelastIndexproperty. Returns null when there's no match:
>varre=/([dn])(o+)dle/g;>re.exec("noodledoodle");["noodle","n","oo"]>re.exec("noodledoodle");["doodle","d","oo"]
Thearraysreturnedbyexec()havetwoadditionalproperties:index(ofthematch)andinput(theinputstringbeingsearched).
test(string)
Sameasexec()butonlyreturnstrueorfalse:
>/noo/.test('Noodle');false>/noo/i.test('Noodle');true
Error objectsError objects are created either by the environment (the browser) or by your code:
>vare=newError('jaavcsritpis_not_howyouspellit');>typeofe;"object"
OtherthantheErrorconstructor,sixadditionalonesexistandtheyallinheritError:
EvalErrorRangeErrorReferenceErrorSyntaxErrorTypeErrorURIError
TheError.prototypemembers
FollowingaretheError.prototypemembers:
Property Description
name
Thenameoftheerrorconstructorusedtocreatetheobject:
>vare=newEvalError('Oops');>e.name;"EvalError"
message
Additionalerrorinformation:
>vare=newError('Oops...again');>e.message;"Oops...again"
JSONThe JSON object is new to ES5. It's not a constructor (similarly to Math) and has only twomethods: parse() and stringify(). For ES3 browsers that don't support JSON natively, youcan use the "shim" from http://json.org.
JSONstandsforJavaScriptObjectNotation.It'salightweightdatainterchangeformat.It'sasubsetofJavaScriptthatonlysupportsprimitives,objectliterals,andarrayliterals.
MembersoftheJSONobject
FollowingarethemembersoftheJSONobject:
Method Description
parse(text,callback)
TakesaJSON-encodedstringandreturnsanobject:
>vardata='{"hello":1,"hi":[1,2,3]}';>varo=JSON.parse(data);>o.hello;1>o.hi;[1,2,3]
Theoptionalcallbackletsyouprovideyourownfunctionthatcaninspectandmodifytheresult.Thecallbacktakeskeyandvalueargumentsandcanmodifythevalueordeleteit(byreturningundefined).
>functioncallback(key,value){console.log(key,value);if(key==='hello'){return'bonjour';}if(key==='hi'){
return undefined;}returnvalue;}>varo=JSON.parse(data,callback);hello1011223hi[1,2,3]Object{hello:"bonjour"}>o.hello;"bonjour">'hi'ino;false
stringify(value,callback,white)
Takesanyvalue(mostcommonlyanobjectoranarray)andencodesittoaJSONstring.
>varo={hello:1,hi:2,when:newDate(2015,0,1)};>JSON.stringify(o);"{"hello":1,"hi":2,"when":"2015-01-01T08:00:00.000Z"}"
Thesecondparameterletsyouprovideacallback(orawhitelistarray)tocustomizethereturnvalue.Thewhitelistcontainsthekeysyou'reinterestedin:
JSON.stringify(o,['hello','hi']);"{"hello":1,"hi":2}"
Thelastparameterhelpsyougetahuman-readableversion.Youspecifythenumberofspacesasastringoranumber:
>JSON.stringify(o,null,4);
"{"hello":1,"hi":2,"when":"2015-01-01T08:00:00.000Z"}"
Appendix D. Regular ExpressionsWhen you use regular expressions (discussed in Chapter 4, Objects), you can match literalstrings,forexample:
>"sometext".match(/me/);["me"]
However,thetruepowerofregularexpressionscomesfrommatchingpatterns,notliteralstrings.Thefollowingtabledescribesthedifferentsyntaxyoucanuseinyourpatterns,andprovidessomeexamplesoftheiruse:
Pattern Description
[abc]
Matchesaclassofcharacters:
>"sometext".match(/[otx]/g);["o","t","x","t"]
[a-z]
Aclassofcharactersdefinedasarange.Forexample,[a-d]isthesameas[abcd],[a-z]matchesalllowercasecharacters,[a-zA-Z0-9_]matchesallcharacters,numbers,andtheunderscorecharacter:
>"SomeText".match(/[a-z]/g);["o","m","e","e","x","t"]>"SomeText".match(/[a-zA-Z]/g);
["S", "o", "m", "e", "T", "e", "x", "t"]
[^abc]
Matcheseverythingthatisnotmatchedbytheclassofcharacters:
>"SomeText".match(/[^a-z]/g);["S","","T"]
a|b
Matchesaorb.ThepipecharactermeansOR,anditcanbeusedmorethanonce:
>"SomeText".match(/t|T/g);["T","t"]>"SomeText".match(/t|T|Some/g);["Some","T","t"]
a(?=b)
Matchesaonlyiffollowedbyb:
>"SomeText".match(/Some(?=Tex)/g);null>"SomeText".match(/Some(?=Tex)/g);["Some"]
a(?!b)
Matchesaonlywhennotfollowedbyb:
>"SomeText".match(/Some(?!Tex)/g);null>"SomeText".match(/Some(?!Tex)/g);["Some"]
\
Escapecharacterusedtohelpyoumatchthespecialcharactersusedinpatternsasliterals:
>"R2-D2".match(/[2-3]/g);["2","2"] >"R2-D2".match(/[2\-3]/g);["2","-","2"]
\n
\r
\f
\t
\v
Newline
Carriagereturn
Formfeed
Tab
Verticaltab
\s
Whitespace,oranyofthepreviousfiveescapesequences:
>"R2\nD2".match(/\s/g);["\n", " "]
\S
Oppositeoftheabove;matcheseverythingbutwhitespace.Sameas[ \̂s]:
>"R2\nD2".match(/\S/g);["R","2","D","2"]
\w
Anyletter,number,orunderscore.Sameas[A-Za-z0-9_]:
>"S0m3text!".match(/\w/g);["S","0","m","3","t","e","x","t"]
\W
Oppositeof\w:
>"S0m3text!".match(/\W/g);["","!"]
\d
Matchesanumber,sameas[0-9]:
>"R2-D2andC-3PO".match(/\d/g);["2","2","3"]
\D
Oppositeof\d;matchesnon-numbers,sameas[^0-9]or[ \̂d]:
>"R2-D2andC-3PO".match(/\D/g);["R","-","D","","a","n","d","","C", "-","P","O"]
\b
Matchesawordboundarysuchasspaceorpunctuation.
MatchingRorDfollowedby2:
> "R2D2 and C-3PO".match(/[RD]2/g);["R2","D2"]
Sameasabovebutonlyattheendofaword:
>"R2D2and C-3PO".match(/[RD]2\b/g);["D2"]
Samepatternbuttheinputhasadash,whichisalsoanendofaword:
>"R2-D2andC-3PO".match(/[RD]2\b/g);["R2","D2"]
\B
Theoppositeof\b:
>"R2-D2andC-3PO".match(/[RD]2\B/g);null>"R2D2and C-3PO".match(/[RD]2\B/g);["R2"]
[\b] Matchesthebackspacecharacter.
\0 Thenullcharacter.
\u0000
MatchesaUnicodecharacter,representedbyafour-digithexadecimalnumber:
>"стоян".match(/\u0441\u0442\u043E/);["сто"]
\x00
Matchesacharactercoderepresentedbyatwo-digithexadecimalnumber:
>"\x64";"d">"dude".match(/\x64/g);["d","d"]
^
Thebeginningofthestringtobematched.Ifyousetthemmodifier(multi-line),itmatchesthebeginningofeachline:
>"regular\nregular\nexpression".match(/r/g);["r","r","r","r","r"]>"regular\nregular\nexpression".match(/^r/g);["r"]>"regular\nregular\nexpression".match(/^r/mg);["r","r"]
$
Matchestheendoftheinputor,whenusingthemultilinemodifier,theendofeachline:
>"regular\nregular\nexpression".match(/r$/g);null>"regular\nregular\nexpression".match(/r$/mg);["r","r"]
.
Matchesanysinglecharacterexceptforthenewlineandthelinefeed:
>"regular".match(/r./g);["re"]>"regular".match(/r.../g);["regu"]
*
Matchestheprecedingpatternifitoccurszeroormoretimes.Forexample,/.*/willmatchanythingincludingnothing(anempty input):
>"".match(/.*/);[""]>"anything".match(/.*/);["anything"]>"anything".match(/n.*h/);["nyth"]
Keepinmindthatthepatternis"greedy",meaningitwillmatchasmuchaspossible:
>"anything within".match(/n.*h/g);["nythingwith"]
?
Matchestheprecedingpatternifitoccurszerooronetimes:
>"anything".match(/ny?/g);["ny","n"]
+
Matchestheprecedingpatternifitoccursatleastonce(ormoretimes):
>"anything".match(/ny+/g);["ny"]>"R2-D2andC-3PO".match(/[a-z]/gi);["R","D","a","n","d","C","P","O"]>"R2-D2andC-3PO".match(/[a-z]+/gi);["R","D","and","C","PO"]
{n}
Matchestheprecedingpatternifitoccursexactlyntimes:
>"regularexpression".match(/s/g);["s","s"] >"regularexpression".match(/s{2}/g);["ss"]>"regularexpression".match(/\b\w{3}/g);["reg","exp"]
{min,max}
Matchestheprecedingpatternifitoccursbetweenminandmaxnumberoftimes.Youcanomitmax,whichwillmeannomaximum,butonlyaminimum.Youcannotomitmin.
Anexamplewheretheinputis"doodle"withthe"o"repeated10times:
>"doooooooooodle".match(/o/g);["o","o","o","o","o","o","o","o","o","o"]>"doooooooooodle".match(/o/g).length;10>"doooooooooodle".match(/o{2}/g);["oo","oo","oo","oo","oo"]>"doooooooooodle".match(/o{2,}/g);["oooooooooo"]>"doooooooooodle".match(/o{2,6}/g);["oooooo","oooo"]
Whenthepatternisinparentheses,itisrememberedsothatitcanbeusedforreplacements.Thesearealsoknownascapturingpatterns.
Thecapturedmatchesareavailableas$1,$2,...$9
Matchingall"r"occurrencesandrepeatingthem:
(pattern) >"regularexpression".replace(/(r)/g,'$1$1');"rregularrexprression"
Matching"re"andturningitto"er":
>"regularexpression".replace(/(r)(e)/g,'$2$1');"ergularexperssion"
(?:pattern)
Non-capturingpattern,notrememberedandnotavailablein$1,$2...
Here'sanexampleofhow"re"ismatched,butthe"r"isnotrememberedandthesecondpatternbecomes$1:
>"regularexpression".replace(/(?:r)(e)/g,'$1$1');"eegularexpeession"
Makesureyoupayattentionwhenaspecialcharactercanhavetwomeanings,asisthecasewith^,?,and\b.
Appendix E. Answers to Exercise QuestionsThis appendix lists possible answers to the exercises at the end of the chapters. Possible answersmeaningtheyarenottheonlyones,sodon'tworryifyoursolutionisdifferent.
Aswiththerestofthebook,youshouldtrytheminyourconsoleandplayaroundabit.
Thefirstandthelastchaptersdon'thavetheExercisessection,solet'sstartwithChapter2,PrimitiveDataTypes,Arrays,Loops,andConditions.
Chapter 2, Primitive Data Types, Arrays,Loops,andConditionsLetstryandsolvethefollowingexercises:
Exercises1. Theresultwillbeasfollows:
>vara;typeofa;"undefined"
Whenyoudeclareavariablebutdonotinitializeitwithavalue,itautomaticallygetstheundefinedvalue.Youcanalsocheck:
>a===undefined;true
Thevalueofvwillbe:
>vars='1s';s++;NaN
Adding 1 to the string '1s' returns the string '1s1', which is Not A Number, but the ++operator should return a number; so it returns the special NaN number.
Theprogramisasfollows:
>!!"false";true
Thetrickypartofthequestionisthat"false"isastringandallstringsaretruewhencasttoBooleans(excepttheemptystring"").Ifthequestionwasn'taboutthestring"false"buttheBooleanfalseinstead,thedoublenegation!!returnsthesameBoolean:
>!!false;false
Asyou'dexpect,singlenegationreturnstheopposite:
>!false;true>!true;false
YoucantestwithanystringanditwillcasttoaBooleantrue,excepttheemptystring:
>!!"hello";true>!!"0";true>!!"";false
Theoutputafterexecutingundefinedisasfollows:
>!!undefined;false
Hereundefinedis oneofthefalsyvaluesanditcaststofalse.Youcantrywithanyoftheotherfalsyvalues,suchtheemptystring""inthepreviousexample,NaN,or0.
> typeof -Infinity;"number"
Thenumbertypeincludesallnumbers,NaN,positiveandnegativeInfinity.
Theoutputafterexecutingthefollowingis:
>10%"0";NaN
Thestring"0"iscasttothenumber0.Divisionby0isInfinity,whichhasnoremainder.
Theoutputafterexecutingthefollowingis:
>undefined==null;true
Comparison with == operator doesn't check the types, but converts the operands; in this caseboth are falsy values. Strict comparison checks the types too:
>undefined===null;false
Thefollowingisthecodelineanditsoutput:
>false==="";false
Strictcomparisonbetweendifferenttypes(inthiscaseBooleanandstring)isdoomedtofail,nomatterwhat thevaluesare.
Thefollowingisthecodelineanditsoutput:
>typeof"2E+2";"string"
Anythinginquotesisastring,eventhough:
>2E+2;200>typeof2E+2;"number"
Thefollowingisthecodelineanditsoutput:
>a=3e+3;a++;3000
3e+3is3withthreezeroes,meaning3000.Then++isapost-increment,meaningitreturnstheoldvalueandthenitincrementsitandassignsittoa.That'swhyyougetthereturnvalue3000intheconsole,althoughaisnow3001:
> a;3001
2. Thevalueofvafterexecutingthefollowingis:
>varv=v||10;>v;10
Ifvhasneverbeendeclared,it'sundefinedsothisisthesameas:
>varv=undefined||10;>v;10
However,ifvhasalreadybeendefinedandinitializedwithanon-falsyvalue,you'llgetthepreviousvalue.
>varv=100;>varv=v||10;>v;100
Theseconduseofvardoesn't"reset"thevariable.
Ifvwasalreadyafalsyvalue(nota100),thecheckv||10willreturn10.
>varv=0;> var v = v || 10;
>v;10
3. Forprintingmultiplicationtables,performthefollowing:
for(vari=1;i<=12;i++){for(varj=1;j<=12;j++){console.log(i+'*'+j+'='+i*j);}}
Or:
vari=1,j=1;while(i<=12){
while (j <= 12) {
console.log(i+'*'+j+'='+i*j);j++;}i++;j=1;}
Chapter 3, FunctionsLets do the following exercises:
Exercises1. ToconvertHexcolorstoRGB,performthefollowing:
functiongetRGB(hex){return"rgb("+parseInt(hex[1]+hex[2],16)+","+parseInt(hex[3]+hex[4],16)+","+parseInt(hex[5]+hex[6],16)+")";}Testing:>getRGB("#00ff00");"rgb(0,255,0)">getRGB("#badfad");"rgb(186,223,173)"
One problemwith this solution is that array access to strings like hex[0] is not inECMAScript 3, although many browsers have supported it for a long time and is nowdescribedinES5.
However,Butatthispointinthebook,therewasasyetnodiscussionofobjectsandmethods.OtherwiseanES3-compatiblesolutionwouldbetouseoneofthestringmethods,suchascharAt(),substring(),orslice().Youcanalsouseanarraytoavoidtoomuchstringconcatenation:
function getRGB2(hex) {varresult=[];result.push(parseInt(hex.slice(1,3),16));result.push(parseInt(hex.slice(3,5),16));result.push(parseInt(hex.slice(5),16));return"rgb("+result.join(",")+")";}
Bonusexercise:Rewritetheprecedingfunctionusingaloopsoyoudon'thavetotypeparseInt()threetimes,butjustonce.
2. Theresultisasfollows:
>parseInt(1e1);10Here,you'reparsingsomethingthatisalreadyaninteger:>parseInt(10);10>1e1;10
Here,theparsingofastringgivesuponthefirstnon-integervalue.parseInt()doesn'tunderstandexponentialliterals,itexpectsintegernotation:
>parseInt('1e1');1
Thisisparsingthestring'1e1'whileexpectingittobeindecimalnotation,includingexponential:
>parseFloat('1e1');10
Thefollowingisthecodelineanditsoutput:
>isFinite(0/10);true
Because0/10is0and0isfinite.
Thefollowingisthecodelineanditsoutput:
>isFinite(20/0);false
Becausedivisionby0isInfinity:
>20/0;Infinity
Thefollowingisthecodelineanditsoutput:
>isNaN(parseInt(NaN));true
ParsingthespecialNaNvalueisNaN.3. Whatistheresultof:
vara=1;functionf(){functionn(){alert(a);}vara=2;n();}f();
Thissnippetalerts2eventhoughn()wasdefinedbeforetheassignment,a=2.Insidethefunctionn()youseethevariableathatisinthesamescope,andyouaccessitsmostrecentvalueatthetimeinvocationoff()(andhencen()).Duetohoistingf()actsasifitwas:
functionf(){vara;functionn(){alert(a);}a=2;
n();}
Moreinterestingly,considerthiscode:
vara=1;functionf(){functionn(){alert(a);
}n();vara=2;n();}f();
Italertsundefinedandthen2.Youmightexpectthefirstalerttosay1,butagainduetovariablehoisting,thedeclaration(notinitialization)ofaismovedtothetopofthefunction.Asiff()was:
var a = 1;functionf(){vara;//aisnowundefinedfunctionn(){alert(a);}n();//alertundefineda=2;n();//alert2}f();
Thelocala"shadows"theglobala,evenifit'satthebottom.4. Whyallthesealert"Boo!"
ThefollowingistheresultofExample1:
varf=alert;eval('f("Boo!")');
ThefollowingistheresultofExample2.Youcanassignafunctiontoadifferentvariable.Sof()pointstoalert().Evaluatingthisstringislikedoing:
>f("Boo");
Thefollowingistheoutputafterweexecute eval():
vare;varf=alert;eval('e=f')('Boo!');
ThefollowingistheoutputofExample3.eval()returnstheresultontheevaluation.Inthiscaseit'sanassignmente=fthatalsoreturnsthenewvalueofe.Likethefollowing:
>vara=1;>varb;>varc=(b=a);>c;1
Soeval('e=f')givesyouapointertoalert()thatisexecutedimmediatelywith"Boo!".
Theimmediate(self-invoking)anonymousfunctionreturnsapointertothefunctionalert(),whichisalsoimmediatelyinvokedwithaparameter"Boo!":
(function(){returnalert;})()('Boo!');
Chapter 4, ObjectsLets solve the following exercises:
Exercises1. Whathappenshere?Whatisthisandwhat'so?
functionF(){functionC(){returnthis;}returnC();}varo=newF();
Here, this === window because C() was called without new.
Alsoo===windowbecausenewF()returnstheobjectreturnedbyC(),whichisthis,andthisiswindow.
YoucanmakethecalltoC()aconstructorcall:
function F() {functionC(){returnthis;}returnnewC();}varo=newF();
Here,thisistheobjectcreatedbytheC()constructor.Soiso:
>o.constructor.name;"C"
ItbecomesmoreinterestingwithES5'sstrictmode.Inthestrictmode,non-constructorinvocationsresultinthisbeingundefined,nottheglobalobject.With"usestrict"insideF()orC()constructor'sbody,thiswouldbeundefinedinC().Therefore,returnC()cannotreturnthenon-objectundefined(becauseallconstructorinvocationsreturnsomesortofobject)andreturnsFinstances' this(whichisintheclosurescope).Tryit:
functionF(){"usestrict";this.name="IamF()";functionC(){console.log(this);//undefinedreturnthis;}
return C();}
Testing:
>varo=newF();>o.name;"IamF()"
2. Whathappenswheninvokingthisconstructorwithnew?
functionC(){this.a=1;returnfalse;}Andtesting:>typeofnewC();"object">newC().a;1
newC()isanobject,notBoolean,becauseconstructorinvocationsalwaysproduceanobject.It'sthethisobjectyougetunlessyoureturnsomeotherobjectinyourconstructor.Returningnon-objectsdoesn'tworkandyoustillgetthis.
3. Whatdoesthisdo?
>varc=[1,2,[1,2]];>c.sort();>c;[1,Array[2],2]
Thisisbecausesort()comparesstrings.[1,2].toString()is"1,2",soitcomesafter"1"andbefore"2".
Thesamethingwithjoin():
>c.join('--');>c;"1--1,2--2"
4. PretendString()doesn'texistandcreateMyString()mimickingString().Treattheinputprimitivestringsasarrays(arrayaccessofficiallysupportedinES5).
Here'sasampleimplementationwithjustthemethodstheexerciseaskedfor.Feelfreetocontinuewiththerestofthemethods.RefertoAppendixC,Built-inObjectsforthefulllist.
functionMyString(input){varindex=0;//casttostringthis._value=''+input;//setallnumericpropertiesforarrayaccesswhile(input[index]!==undefined){this[index]=input[index];index++;
}//rememberthelengththis.length=index;}MyString.prototype={constructor:MyString,valueOf:functionvalueOf(){returnthis._value;},toString:functiontoString(){returnthis.valueOf();},charAt:functioncharAt(index){returnthis[parseInt(index,10)||0];},concat:functionconcat(){varprim=this.valueOf();for(vari=0,len=arguments.length;i<len;i++){prim+=arguments[i];
}returnprim;},slice:functionslice(from,to){varresult='',original=this.valueOf();if(from===undefined){returnoriginal;}if(from>this.length){returnresult;}if(from<0){from=this.length-from;}if(to===undefined||to>this.length){to=this.length;}if(to<0){to=this.length+to;}//endofvalidation,actualslicingloopnowfor(vari=from;i<to;i++){result+=original[i];}
return result;},split:functionsplit(re){varindex=0,result=[],original=this.valueOf(),match,
pattern='',modifiers='g';if(reinstanceofRegExp){//splitwithregexpbutalwaysset"g"pattern=re.source;modifiers+=re.multiline?'m':'';modifiers+=re.ignoreCase?'i':'';}else{//notaregexp,probablyastring,we'llconvertitpattern=re;}re=RegExp(pattern,modifiers);while(match=re.exec(original)){result.push(this.slice(index,match.index));index=match.index+newMyString(match[0]).length;}result.push(this.slice(index));returnresult;}
};
Testing:
>vars=newMyString('hello');>s.length;5>s[0];"h">s.toString();"hello">s.valueOf();"hello"
> s.charAt(1);"e">s.charAt('2');"l">s.charAt('e');"h">s.concat('world!');"helloworld!">s.slice(1,3);"el">s.slice(0,-1);"hell">s.split('e');["h","llo"]>s.split('l');["he","","o"]
Feelfreetoplaysplittingwitharegularexpression.5. UpdateMyString()withareverse()method:
>MyString.prototype.reverse=functionreverse(){returnthis.valueOf().split("").reverse().join("");};>newMyString("pudding").reverse();"gniddup"
6. ImagineArray()isgoneandtheworldneedsyoutoimplementMyArray().Hereareahandfulofmethodstogetyoustarted:
functionMyArray(length){//singlenumericargumentmeanslengthif(typeoflength==='number'&&arguments[1]===undefined){this.length=length;returnthis;}//usualcasethis.length=arguments.length;for(vari=0,len=arguments.length;i<len;i++){this[i]=arguments[i];}returnthis;//laterinthebookyou'lllearnhowtosupport
// a non-constructor invocation too}MyArray.prototype={constructor:MyArray,join:functionjoin(glue){varresult='';if(glue===undefined){glue=',';}for(vari=0;i<this.length-1;i++){result+=this[i]===undefined?'':this[i];result+=glue;}result+=this[i]===undefined?'':this[i];returnresult;},toString:functiontoString(){returnthis.join();},push:functionpush(){for(vari=0,len=arguments.length;i<len;i++){this[this.length+i]=arguments[i];}this.length+=arguments.length;
return this.length;},pop:functionpop(){
varpoppd=this[this.length-1];deletethis[this.length-1];this.length--;returnpoppd;}};
Testing:
> var a = new MyArray(1, 2, 3, "test");>a.toString();"1,2,3,test">a.length;4>a[a.length-1];"test">a.push('boo');5>a.toString();"1,2,3,test,boo">a.pop();"boo">a.toString();"1,2,3,test">a.join(',');"1,2,3,test">a.join('isn't');"1isn't2isn't3isn'ttest"
Ifyoufoundthisexerciseamusing,don'tstopwithjoin();goonwithasmanymethodsaspossible.
7. CreateMyMathobjectthatalsohasrand(),min([]),max([]).
ThepointhereisthatMathisnotaconstructor,butanobjectthathassome"static"propertiesandmethods.Belowaresomemethodstogetyoustarted.
Let's also use an immediate function to keep some private utility functions. You can also takethisapproachwithMyStringabove,wherethis._valuecouldbereallyprivate.
varMyMath=(function(){functionisArray(ar){returnObject.prototype.toString.call(ar)==='[objectArray]';}functionsort(numbers){//notusingnumbers.sort()directlybecause//`arguments`isnotanarrayanddoesn'thavesort()returnArray.prototype.sort.call(numbers,function(a,b){
if(a===b){return0;}return1*(a>b)-0.5;//returns0.5or-0.5});}return{PI:3.141592653589793,E:2.718281828459045,LN10:2.302585092994046,LN2:0.6931471805599453,//...moreconstantsmax:functionmax(){//allowunlimitednumberofarguments//oranarrayofnumbersasfirstargumentvarnumbers=arguments;if(isArray(numbers[0])){numbers=numbers[0];}//wecanbelazy:
// let Array sort the numbers and pick the lastreturnsort(numbers)[numbers.length-1];},min:functionmin(){//differentapproachtohandlingarguments://callthesamefunctionagainif(isArray(numbers)){returnthis.min.apply(this,numbers[0]);}//Differentapproachtopickingthemin://sortingthearrayisanoverkill,it'stoomuch//worksincewedon'tworryaboutsortingbutonly//aboutthesmallestnumber.//Solet'sloop:varmin=numbers[0];for(vari=1;i<numbers.length;i++){if(min>numbers[i]){min=numbers[i];}}returnmin;},rand:functionrand(min,max,inclusive){if(inclusive){
return Math.round(Math.random() * (max - min) + min);//testboundariesforrandomnumber//between10and100*inclusive*://Math.round(0.000000*90+10);//10//Math.round(0.000001*90+10);//10//Math.round(0.999999*90+10);//100
}returnMath.floor(Math.random()*(max-min-1)+min+1);//testboundariesforrandomnumber//between10and100*non-inclusive*://Math.floor(0.000000*(89)+(11));//11//Math.floor(0.000001*(89)+(11));//11//Math.floor(0.999999*(89)+(11));//99}};})();
AfteryouhavefinishedthebookandknowaboutES5youcantryusingdefineProperty()fortightercontrolandcloserreplicationofthebuilt-ins.
Chapter 5, PrototypeLets try and solve the following exercise:
Exercises1. CreateanobjectcalledshapethathasatypepropertyandagetType()method:
varshape={type:'shape',getType:function(){returnthis.type;}};
2. ThefollowingistheprogramforaTriangle()constructor:
functionTriangle(a,b,c){this.a = a;
this.b=b;this.c=c;}Triangle.prototype=shape;Triangle.prototype.constructor=Triangle;Triangle.prototype.type='triangle';
3. ToaddthegetPerimeter()method,usethefollowingcode:
Triangle.prototype.getPerimeter=function(){returnthis.a+this.b+this.c;};
4. Testthefollowingcode:
>vart=newTriangle(1,2,3);>t.constructor===Triangle;true>shape.isPrototypeOf(t);true>t.getPerimeter();6>t.getType();"triangle"
5. Loopovertshowingonlyownpropertiesandmethods:
for(variint){if(t.hasOwnProperty(i)){console.log(i,'=',t[i]);}}
6. Randomizearrayelementsusingthefollowingcodesnippet:
Array.prototype.shuffle=function(){returnthis.sort(function(){returnMath.random()-0.5;
});};
Testing:
>[1,2,3,4,5,6,7,8,9].shuffle();[4,2,3,1,5,6,8,9,7]>[1,2,3,4,5,6,7,8,9].shuffle();[2,7,1,3,4,5,8,9,6]
> [1, 2, 3, 4, 5, 6, 7, 8, 9].shuffle();[4,2,1,3,5,6,8,9,7]
Chapter 6, InheritanceLets solve the following exercise:
Exercises1. Multipleinheritancebymixingintotheprototype,forexample:
varmy=objectMulti(obj,another_obj,a_third,{additional:"properties"});Apossiblesolution:functionobjectMulti(){varConstr,i,prop,mixme;//constructorthatsetsownpropertiesvarConstr=function(props){for(varpropinprops){this[prop]=props[prop];}};//mixintotheprototype
for (var i = 0; i < arguments.length - 1; i++) {varmixme=arguments[i];for(varpropinmixme){Constr.prototype[prop]=mixme[prop];}}returnnewConstr(arguments[arguments.length-1]);}
Testing:
>varobj_a={a:1};> var obj_b = {a: 2, b: 2};
>varobj_c={c:3};>varmy=objectMulti(obj_a,obj_b,obj_c,{hello:"world"});>my.a;2
Propertyais2becauseobj_boverwrotethepropertywiththesamenamefromobj_a(lastonewins):
>my.b;2>my.c;3>my.hello;
"world">my.hasOwnProperty('a');false>my.hasOwnProperty('hello');true
2. Practicewiththecanvasexampleathttp://www.phpied.com/files/canvas/.
Drawafewtrianglesusingthefollowingcodesnippet:
newTriangle(newPoint(100,155),newPoint(30,50),newPoint(220,00)).draw();newTriangle(newPoint(10,15),newPoint(300,50),newPoint(20,400)).draw();
Drawafewsquaresusingthefollowingcodesnippet:
newSquare(newPoint(150,150),300).draw();newSquare(newPoint(222,222),222).draw();
Drawafewrectanglesusingthefollowingcodesnippet:
newRectangle(newPoint(100,10),200,400).draw();newRectangle(newPoint(400,200),200,100).draw();
3. ToaddRhombus,Kite,Pentagon,Trapezoid,andCircle(reimplementsdraw()),usethefollowingcode:
functionKite(center,diag_a,diag_b,height){this.points=[newPoint(center.x-diag_a/2,center.y),newPoint(center.x,center.y+(diag_b-height)),newPoint(center.x+diag_a/2,center.y),newPoint(center.x,center.y-height)];this.getArea=function(){returndiag_a*diag_b/2;};}functionRhombus(center,diag_a,diag_b){Kite.call(this,center,diag_a,diag_b,diag_b/2);}functionTrapezoid(p1,side_a,p2,side_b){
this.points = [p1, p2, new Point(p2.x + side_b, p2.y),newPoint(p1.x+side_a,p1.y)];this.getArea=function(){varheight=p2.y-p1.y;returnheight*(side_a+side_b)/2;};}
//regularpentagon,alledgeshavethesamelengthfunctionPentagon(center,edge){varr=edge/(2*Math.sin(Math.PI/5)),x=center.x,y=center.y;this.points=[newPoint(x+r,y),newPoint(x+r*Math.cos(2*Math.PI/5),y-r*Math.sin(2*Math.PI/5)),newPoint(x-r*Math.cos(Math.PI/5),y-r*Math.sin(Math.PI/5)),newPoint(x-r*Math.cos(Math.PI/5),y+r*Math.sin(Math.PI/5)),newPoint(x+r*Math.cos(2*Math.PI/5),y+r*Math.sin(2*Math.PI/5))];this.getArea=function(){return1.72*edge*edge;};
}functionCircle(center,radius){this.getArea=function(){returnMath.pow(radius,2)*Math.PI;};this.getPerimeter=function(){return2*radius*Math.PI;};this.draw=function(){varctx=this.context;ctx.beginPath();ctx.arc(center.x,center.y,radius,0,2*Math.PI);ctx.stroke();};}(function(){vars=newShape();Kite.prototype=s;Rhombus.prototype=s;Trapezoid.prototype=s;Pentagon.prototype=s;
Circle.prototype = s;}());
Testing:
newKite(newPoint(300,300),200,300,100).draw();newRhombus(newPoint(200,200),350,200).draw();
newTrapezoid(newPoint(100,100),100,newPoint(50,250),400).draw();newPentagon(newPoint(400,400),100).draw();newCircle(newPoint(500,300),270).draw();
Theresultoftestingnewshapes4. Thinkofanotherwaytodotheinheritancepart.Useubersokidscanhaveaccesstotheirparents.Also,getparentstobeawareoftheirchildren.
KeepinmindthatnotallchildreninheritShape;forexample,RhombusinheritsKiteandSquareinheritsRectangle.Youendupwithsomethinglikethis:
//inherit(Child,Parent)inherit(Rectangle,Shape);
inherit(Square,Rectangle);
Intheinheritancepatternfromthechapterandthepreviousexercise,allchildrenweresharingthesameprototype,forexample:
vars=newShape();Kite.prototype=s;Rhombus.prototype=s;
Whilethisisconvenient,italsomeansnoonecantouchtheprototypebecauseitwillaffecteveryoneelse'sprototype.Thedrawbackisthatallcustommethodsneedtoownproperties,forexamplethis.getArea.
It'sagoodideatohavemethodssharedamonginstancesanddefined intheprototype,insteadofrecreatingthemforeveryobject.ThefollowingexamplemovesthecustomgetArea()methodstotheprototype.
Intheinheritancefunction,you'llseethechildrenonlyinherittheparent'sprototype.Soownpropertiessuchasthis.lineswillnotbeset.Therefore,youneedtohaveeachchildconstructorcallitsuberinordertogettheownproperties,forexample:
Child.prototype.uber.call(this,args...)
Anothernice-to-havefeatureiscarryingovertheprototypepropertiesalreadyaddedtothechild.Thisallowsthechildtoinheritfirstandthenaddmorecustomizationsortheotherway around as well, which is just a little more convenient.
functioninherit(Child,Parent){//rememberprototypevarextensions=Child.prototype;//inheritancewithanintermediateF()varF=function(){};F.prototype=Parent.prototype;Child.prototype=newF();//resetconstructorChild.prototype.constructor=Child;//rememberparentChild.prototype.uber=Parent;//keeptrackofwhoinheritstheParent
if (!Parent.children) {Parent.children=[];}Parent.children.push(Child);//carryoverstuffprevisoulyaddedtotheprototype//becausetheprototypeisnowoverwrittencompletelyfor(variinextensions){if(extensions.hasOwnProperty(i)){
Child.prototype[i]=extensions[i];}}}
EverythingaboutShape(),Line(),andPoint()staysthesame.Thechangesareinthechildrenonly:
functionTriangle(a,b,c){Triangle.prototype.uber.call(this);
this.points = [a, b, c];}Triangle.prototype.getArea=function(){varp=this.getPerimeter(),s=p/2;returnMath.sqrt(s*(s-this.lines[0].length)*(s-this.lines[1].length)*(s-this.lines[2].length));};functionRectangle(p,side_a,side_b){//callingparentShape()Rectangle.prototype.uber.call(this);this.points=[p,newPoint(p.x+side_a,p.y),newPoint(p.x+side_a,p.y+side_b),newPoint(p.x,p.y+side_b)];}Rectangle.prototype.getArea=function(){//Previsoulywehadaccesstoside_aandside_b//insidetheconstructorclosure.Nomore.//option1:addownpropertiesthis.side_aandthis.side_b
// option 2: use what we already have:varlines=this.getLines();returnlines[0].length*lines[1].length;};functionSquare(p,side){this.uber.call(this,p,side,side);//thiscallisshorterthanSquare.prototype.uber.call()//butmaybackfireincaseyouinherit//fromSquareandcalluber//tryit:-)}
Inheritance:
inherit(Triangle,Shape);inherit(Rectangle,Shape);
inherit(Square,Rectangle);
Testing:
>varsq=newSquare(newPoint(0,0),100);>sq.draw();>sq.getArea();10000
Testingthatinstanceofiscorrect:
>sq.constructor===Square;true>sqinstanceofSquare;true>sqinstanceofRectangle;true>sqinstanceofShape;
true
Thechildrenarrays:
>Shape.children[1]===Rectangle;true>Rectangle.children[0]===Triangle;false>Rectangle.children[0]===Square;
true>Square.children;undefined
Anduberlooksoktoo:
>sq.uber===Rectangle;true
CallingisPrototypeOf()alsoreturnsexpectedresults:
Shape.prototype.isPrototypeOf(sq);trueRectangle.prototype.isPrototypeOf(sq);trueTriangle.prototype.isPrototypeOf(sq);false
Thefullcodeisavailableathttp://www.phpied.com/files/canvas/index2.html,togetherwiththeadditionalKite(),Circle(),andsoonfromthepreviousexercise.
Chapter 7, The Browser EnvironmentLets practice the following exercise:
Exercises1. Thetitleclockprogramisasfollows:
setInterval(function(){document.title=newDate().toTimeString();},1000);
2. Toanimateresizingofa200x200popupto400x400,usethefollowingcode:
varw=window.open('http://phpied.com','my','width=200,height=200');vari=setInterval((function(){varsize=200;returnfunction(){size+=5;w.resizeTo(size,size);if(size===400){clearInterval(i);}
};}()),100);
Every100ms(1/10thofasecond)thepop-upsizeincreasesbyfivepixels.Youkeepareferencetotheintervalisoyoucanclearitoncedone.Thevariablesizetracksthepop-upsize(andwhynotkeepitprivateinsideaclosure).
3. Theearthquakeprogramisasfollows:
vari=setInterval((function(){varstart=+newDate();//Date.now()inES5returnfunction(){w.moveTo(Math.round(Math.random()*100),Math.round(Math.random()*100));if(newDate()-start>5000){clearInterval(i);}};}()),20);
Tryallofthem,butusingrequestAnimationFrame()insteadofsetInterval().4. AdifferentwalkDOM()withacallbackisasfollows:
functionwalkDOM(n,cb){cb(n);vari,children=n.childNodes,len=children.length,child;for(i=0;i<len;i++){
child=n.childNodes[i];if(child.hasChildNodes()){walkDOM(child,cb);}}}
Testing:
> walkDOM(document.documentElement,console.dir.bind(console));htmlheadtitlebodyh1...
5. Toremovecontentandcleanupfunctions,usethefollowingcode:
//helperfunctionisFunction(f){returnObject.prototype.toString.call(f)==="[objectFunction]";}functionremoveDom(node){vari,len,attr;//firstdrilldowninspectingthechildren//andonlyafterthatremovethecurrentnodewhile(node.firstChild){removeDom(node.firstChild);}//notallnodeshaveattributes,e.g.textnodesdon'tlen=node.attributes?node.attributes.length:0;
//cleanuploop//e.g.node===<body>,//node.attributes[0].name==="onload"//node.onload===function()...//node.onloadisnotenumerablesowecan'tuse//afor-inloopandhavetogotheattributesroutefor(i=0;i<len;i++){attr=node[node.attributes[i].name];if(isFunction(attr)){//console.log(node,attr);attr=null;}}node.parentNode.removeChild(node);}
Testing:
>removeDom(document.body);
6. Toincludescriptsdynamically,usethefollowingcode:
functioninclude(url){vars=document.createElement('script');s.src=url;document.getElementsByTagName('head')[0].appendChild(s);}
Testing:
>include("http://www.phpied.com/files/jinc/1.js");>include("http://www.phpied.com/files/jinc/2.js");
7. Events:Theeventutilityprogramisasfollows:
varmyevent=(function(){//wrapsomeprivatestuffinaclosurevaradd,remove,toStr=Object.prototype.toString;//helperfunctiontoArray(a){//alreadyanarrayif(toStr.call(a)==='[objectArray]'){returna;}
// duck-typing HTML collections, arguments etcvarresult,i,len;if('length'ina){for(result=[],i=0,len=a.length;i<len;i++){result[i]=a[i];}returnresult;}//primitivesandnon-array-likeobjects//becomethefirstandsinglearrayelementreturn[a];}//defineadd()andremove()depending//onthebrowser'scapabilitiesif(document.addEventListener){add=function(node,ev,cb){node.addEventListener(ev,cb,false);};remove=function(node,ev,cb){
node.removeEventListener(ev,cb,false);};}elseif(document.attachEvent){add=function(node,ev,cb){node.attachEvent('on'+ev,cb);};remove=function(node,ev,cb){node.detachEvent('on'+ev,cb);};}else{add=function(node,ev,cb){node['on'+ev]=cb;};remove=function(node,ev){node['on'+ev]=null;};}//publicAPIreturn{
addListener: function (element, event_name, callback) {//elementcouldalsobeanarrayofelementselement=toArray(element);for(vari=0;i<element.length;i++){add(element[i],event_name,callback);}},removeListener:function(element,event_name,callback){//sameasadd(),onlypracticingadifferentloopvari=0,els=toArray(element),len=els.length;for(;i<len;i++){remove(els[i],event_name,callback);}},getEvent:function(event){returnevent||window.event;},getTarget:function(event){vare=this.getEvent(event);returne.target||e.srcElement;},
stopPropagation: function (event) {vare=this.getEvent(event);if(e.stopPropagation){e.stopPropagation();}else{e.cancelBubble=true;}
},preventDefault:function(event){vare=this.getEvent(event);if(e.preventDefault){e.preventDefault();}else{e.returnValue=false;}}};}());
Testing:Gotoanypagewithlinks,executethefollowing,andthenclickanylink:
functionmyCallback(e){e=myevent.getEvent(e);alert(myevent.getTarget(e).href);myevent.stopPropagation(e);myevent.preventDefault(e);}myevent.addListener(document.links,'click',myCallback);
8. Moveadivaroundwiththekeyboardusing thefollowingcode:
//addadivtothebottomofthepagevardiv=document.createElement('div');div.style.cssText='width:100px;height:100px;background:red;position:absolute;';document.body.appendChild(div);//remembercoordinates
var x = div.offsetLeft;vary=div.offsetTop;myevent.addListener(document.body,'keydown',function(e){//preventscrollingmyevent.preventDefault(e);switch(e.keyCode){case37://leftx--;break;case38://upy--;break;case39://rightx++;break;case40://downy++;break;
default://notinterested}//movediv.style.left=x+'px';div.style.top=y+'px';});
9. YourownAjaxutility:
varajax={getXHR:function(){varids=['MSXML2.XMLHTTP.3.0','MSXML2.XMLHTTP','Microsoft.XMLHTTP'];varxhr;if(typeofXMLHttpRequest==='function'){xhr=newXMLHttpRequest();}else{//IE:trytofindanActiveXobjecttousefor(vari=0;i<ids.length;i++){try{xhr=newActiveXObject(ids[i]);
break;}catch(e){}}}returnxhr;},request:function(url,method,cb,post_body){varxhr=this.getXHR();xhr.onreadystatechange=(function(myxhr){returnfunction(){if(myxhr.readyState===4&&myxhr.status===200){cb(myxhr);}};}(xhr));xhr.open(method.toUpperCase(),url,true);xhr.send(post_body||'');}};
Whentesting,rememberthatsameoriginrestrictionsapply,soyouhavetobeonthesamedomain.Youcangotohttp://www.phpied.com/files/jinc/,whichisadirectorylistingandthentestintheconsole:
functionmyCallback(xhr){alert(xhr.responseText);}ajax.request('1.css','get',myCallback);
ajax.request('1.css','post',myCallback,'first=John&last=Smith');
Theresultofthetwoisthesame,butifyoulookintotheNetworktaboftheWebInspector,youcanseethatthesecondisindeedaPOSTrequestwithabody.