you don t know js scope closures

background image
background image

1.

Introduction

2.

Preface

3.

Chapter1:WhatisScope?

i. CompilerTheory

ii. UnderstandingScope

iii. NestedScope

iv. Errors

4.

Chapter2:LexicalScope

i. Lex-time

ii. CheatingLexical

5.

Chapter3:Functionvs.BlockScope

i. ScopeFromFunctions

ii. HidingInPlainScope

iii. FunctionsAsScopes

iv. BlocksAsScopes

6.

Chapter4:Hoisting

i. ChickenOrTheEgg?

ii. TheCompilerStrikesAgain

iii. FunctionsFirst

7.

Chapter5:ScopeClosures

i. Enlightenment

ii. NittyGritty

iii. NowICanSee

iv. Loops+Closure

v. Modules

8.

AppendixA:DynamicScope

9.

AppendixB:PolyfillingBlockScope

10.

AppendixC:Lexical-this

11.

AppendixD:ThankYou's!

TableofContents

YouDon'tKnowJS:Scope&Closures

2

background image

ThisisaseriesofbooksdivingdeepintothecoremechanismsoftheJavaScriptlanguage.

PleasefeelfreetocontributetothequalityofthiscontentbysubmittingPR'sforimprovementstocodesnippets,

explanations,etc.Whiletypofixesarewelcomed,theywilllikelybecaughtthroughnormaleditingprocesses,andarethus

notnecessarilyasimportantforthisrepository.

Toreadmoreaboutthemotivationsandperspectivebehindthisbookseries,checkoutthe

Preface

.

Readonline(free!):

"Up&Going"

,Published:

BuyNow

,ebookformatisfree!

Readonline(free!):

"Scope&Closures"

,Published:

BuyNow

Readonline(free!):

"this&ObjectPrototypes"

,Published:

BuyNow

Readonline(free!):

"Types&Grammar"

,Published:

BuyNow

Readonline(free!):

"Async&Performance"

,Published:

BuyNow

Readonline(free!):

"ES6&Beyond"

(inproduction)

Thesebooksarebeingreleasedhereasdrafts,freetoread,butarealsobeingedited,produced,andpublishedthrough

O'Reilly.

Ifyoulikethecontentyoufindhere,andwanttosupportmorecontentlikeit,pleasepurchasethebooksoncetheyare

availableforsale,throughyournormalbooksources.:)

Ifyou'dliketocontributefinanciallytowardstheeffort(oranyofmyotherOSSwork)asidefrompurchasingthebooks,Ido

havea

patreon

thatIwouldalwaysappreciateyourgenerositytowards.

ThecontentforthesebooksderivesheavilyfromaseriesoftrainingmaterialsIteachprofessionally(inbothpublicand

private-corporateworkshopformat),called"AdvancedJS:The'WhatYouNeedToKnow'Parts".

Ifyoulikethiscontentandwouldliketocontactmeregardingconductingtrainingonthese,orothervarious

JS/HTML5/node.jstopics,pleasereachouttomethroughanyofthesechannelslistedhere:

http://getify.me

IalsohavesomeJStrainingmaterialavailableinon-demandvideoformat.Iteachcoursesthrough

FrontendMasters

,like

YouDon'tKnowJS(bookseries)

Titles

Publishing

In-personTraining

OnlineVideoTraining

YouDon'tKnowJS:Scope&Closures

3

Introduction

background image

my

AdvancedJS

workshop(morecoursescomingsoon!).

Thatsamecourseisalso

availablethroughPluralsight

.

Anycontributionsyoumaketothiseffortareofcoursegreatlyappreciated.

However,ifyouchoosetocontributecontent(notjusttypocorrections)tothisrepo,youagreethatyou'regivingmeanon-

exclusivelicensetousethatcontentforthebookseries,asI(andO'Reilly)deemappropriate.Youprobablyguessedthat

already,butwejusthavetomakethelawyershappybyexplicitlystatingit.

So:blah,blah,blah...:)

Thematerialshereinareall(c)2013-2015KyleSimpson.

Thisworkislicensedundera

CreativeCommonsAttribution-NonCommercial-NoDerivs3.0UnportedLicense

.

ContentContributions

License&Copyright

YouDon'tKnowJS:Scope&Closures

4

Introduction

background image

I'msureyounoticed,but"JS"inthebookseriestitleisnotanabbreviationforwordsusedtocurseaboutJavaScript,

thoughcursingatthelanguage'squirksissomethingwecanprobablyallidentifywith!

Fromtheearliestdaysoftheweb,JavaScripthasbeenafoundationaltechnologythatdrivesinteractiveexperiencearound

thecontentweconsume.Whileflickeringmousetrailsandannoyingpop-uppromptsmaybewhereJavaScriptstarted,

nearly2decadeslater,thetechnologyandcapabilityofJavaScripthasgrownmanyordersofmagnitude,andfewdoubtits

importanceattheheartoftheworld'smostwidelyavailablesoftwareplatform:theweb.

Butasalanguage,ithasperpetuallybeenatargetforagreatdealofcriticism,owingpartlytoitsheritagebutevenmoreto

itsdesignphilosophy.Eventhenameevokes,asBrendanEichonceputit,"dumbkidbrother"statusnexttoitsmore

matureolderbrother"Java".Butthenameismerelyanaccidentofpoliticsandmarketing.Thetwolanguagesarevastly

differentinmanyimportantways."JavaScript"isasrelatedto"Java"as"Carnival"isto"Car".

BecauseJavaScriptborrowsconceptsandsyntaxidiomsfromseverallanguages,includingproudC-styleproceduralroots

aswellassubtle,lessobviousScheme/Lisp-stylefunctionalroots,itisexceedinglyapproachabletoabroadaudienceof

developers,eventhosewithjustlittletonoprogrammingexperience.The"HelloWorld"ofJavaScriptissosimplethatthe

languageisinvitingandeasytogetcomfortablewithinearlyexposure.

WhileJavaScriptisperhapsoneoftheeasiestlanguagestogetupandrunningwith,itseccentricitiesmakesolidmastery

ofthelanguageavastlylesscommonoccurrencethaninmanyotherlanguages.Whereittakesaprettyin-depth

knowledgeofalanguagelikeCorC++towriteafull-scaleprogram,full-scaleproductionJavaScriptcan,andoftendoes,

barelyscratchthesurfaceofwhatthelanguagecando.

Sophisticatedconceptswhicharedeeplyrootedintothelanguagetendinsteadtosurfacethemselvesinseemingly

simplisticways,suchaspassingaroundfunctionsascallbacks,whichencouragestheJavaScriptdevelopertojustusethe

languageas-isandnotworrytoomuchaboutwhat'sgoingonunderthehood.

Itissimultaneouslyasimple,easy-to-uselanguagethathasbroadappeal,andacomplexandnuancedcollectionof

languagemechanicswhichwithoutcarefulstudywilleludetrueunderstandingevenforthemostseasonedofJavaScript

developers.

ThereinliestheparadoxofJavaScript,theAchilles'Heelofthelanguage,thechallengewearepresentlyaddressing.

BecauseJavaScriptcanbeusedwithoutunderstanding,theunderstandingofthelanguageisoftenneverattained.

IfateverypointthatyouencounterasurpriseorfrustrationinJavaScript,yourresponseistoaddittotheblacklist,as

someareaccustomedtodoing,yousoonwillberelegatedtoahollowshelloftherichnessofJavaScript.

Whilethissubsethasbeenfamouslydubbed"TheGoodParts",Iwouldimploreyou,dearreader,toinsteadconsideritthe

"TheEasyParts","TheSafeParts",oreven"TheIncompleteParts".

ThisYouDon'tKnowJavaScriptbookseriesoffersacontrarychallenge:learnanddeeplyunderstandallofJavaScript,

evenandespecially"TheToughParts".

Here,weaddressheadonthetendencyofJSdeveloperstolearn"justenough"togetby,withouteverforcingthemselves

tolearnexactlyhowandwhythelanguagebehavesthewayitdoes.Furthermore,weeschewthecommonadviceto

retreatwhentheroadgetsrough.

Iamnotcontent,norshouldyoube,atstoppingoncesomethingjustworks,andnotreallyknowingwhy.Igentlychallenge

Preface

Mission

YouDon'tKnowJS:Scope&Closures

5

Preface

background image

youtojourneydownthatbumpy"roadlesstraveled"andembraceallthatJavaScriptisandcando.Withthatknowledge,

notechnique,noframework,nopopularbuzzwordacronymoftheweek,willbebeyondyourunderstanding.

Thesebookseachtakeonspecificcorepartsofthelanguagewhicharemostcommonlymisunderstoodorunder-

understood,anddiveverydeepandexhaustivelyintothem.Youshouldcomeawayfromreadingwithafirmconfidencein

yourunderstanding,notjustofthetheoretical,butthepractical"whatyouneedtoknow"bits.

TheJavaScriptyouknowrightnowisprobablypartshandeddowntoyoubyotherswho'vebeenburnedbyincomplete

understanding.ThatJavaScriptisbutashadowofthetruelanguage.Youdon'treallyknowJavaScript,yet,butifyoudig

intothisseries,youwill.Readon,myfriends.JavaScriptawaitsyou.

JavaScriptisawesome.It'seasytolearnpartially,andmuchhardertolearncompletely(orevensufficiently).When

developersencounterconfusion,theyusuallyblamethelanguageinsteadoftheirlackofunderstanding.Thesebooksaim

tofixthat,inspiringastrongappreciationforthelanguageyoucannow,andshould,deeplyknow.

Note:Manyoftheexamplesinthisbookassumemodern(andfuture-reaching)JavaScriptengineenvironments,suchas

ES6.Somecodemaynotworkasdescribedifruninolder(pre-ES6)engines.

Summary

YouDon'tKnowJS:Scope&Closures

6

Preface

background image

Oneofthemostfundamentalparadigmsofnearlyallprogramminglanguagesistheabilitytostorevaluesinvariables,and

laterretrieveormodifythosevalues.Infact,theabilitytostorevaluesandpullvaluesoutofvariablesiswhatgivesa

programstate.

Withoutsuchaconcept,aprogramcouldperformsometasks,buttheywouldbeextremelylimitedandnotterribly

interesting.

Buttheinclusionofvariablesintoourprogrambegetsthemostinterestingquestionswewillnowaddress:wheredothose

variableslive?Inotherwords,wherearetheystored?And,mostimportantly,howdoesourprogramfindthemwhenit

needsthem?

Thesequestionsspeaktotheneedforawell-definedsetofrulesforstoringvariablesinsomelocation,andforfinding

thosevariablesatalatertime.We'llcallthatsetofrules:Scope.

But,whereandhowdotheseScoperulesgetset?

Itmaybeself-evident,oritmaybesurprising,dependingonyourlevelofinteractionwithvariouslanguages,butdespite

thefactthatJavaScriptfallsunderthegeneralcategoryof"dynamic"or"interpreted"languages,itisinfactacompiled

language.Itisnotcompiledwellinadvance,asaremanytraditionally-compiledlanguages,noraretheresultsof

compilationportableamongvariousdistributedsystems.

But,nevertheless,theJavaScriptengineperformsmanyofthesamesteps,albeitinmoresophisticatedwaysthanwemay

commonlybeaware,ofanytraditionallanguage-compiler.

Intraditionalcompiled-languageprocess,achunkofsourcecode,yourprogram,willundergotypicallythreestepsbeforeit

isexecuted,roughlycalled"compilation":

1. Tokenizing/Lexing:breakingupastringofcharactersintomeaningful(tothelanguage)chunks,calledtokens.For

instance,considertheprogram:

vara=2;

.Thisprogramwouldlikelybebrokenupintothefollowingtokens:

var

,

a

,

=

,

2

,and

;

.Whitespacemayormaynotbepersistedasatoken,dependingonwhetherit'smeaningfulornot.

Note:Thedifferencebetweentokenizingandlexingissubtleandacademic,butitcentersonwhetherornotthese

tokensareidentifiedinastatelessorstatefulway.Putsimply,ifthetokenizerweretoinvokestatefulparsingrulesto

figureoutwhether

a

shouldbeconsideredadistincttokenorjustpartofanothertoken,thatwouldbelexing.

2. Parsing:takingastream(array)oftokensandturningitintoatreeofnestedelements,whichcollectivelyrepresentthe

grammaticalstructureoftheprogram.Thistreeiscalledan"AST"(AbstractSyntaxTree).

Thetreefor

vara=2;

mightstartwithatop-levelnodecalled

VariableDeclaration

,withachildnodecalled

Identifier

(whosevalueis

a

),andanotherchildcalled

AssignmentExpression

whichitselfhasachildcalled

NumericLiteral

(whosevalueis

2

).

3. Code-Generation:theprocessoftakinganASTandturningitintoexecutablecode.Thispartvariesgreatly

dependingonthelanguage,theplatformit'stargeting,etc.

So,ratherthangetmiredindetails,we'lljusthandwaveandsaythatthere'sawaytotakeourabovedescribedASTfor

vara=2;

andturnitintoasetofmachineinstructionstoactuallycreateavariablecalled

a

(includingreserving

memory,etc.),andthenstoreavalueinto

a

.

Chapter1:WhatisScope?

CompilerTheory

YouDon'tKnowJS:Scope&Closures

7

Chapter1:WhatisScope?

background image

Note:Thedetailsofhowtheenginemanagessystemresourcesaredeeperthanwewilldig,sowe'lljusttakeitfor

grantedthattheengineisabletocreateandstorevariablesasneeded.

TheJavaScriptengineisvastlymorecomplexthanjustthosethreesteps,asaremostotherlanguagecompilers.For

instance,intheprocessofparsingandcode-generation,therearecertainlystepstooptimizetheperformanceofthe

execution,includingcollapsingredundantelements,etc.

So,I'mpaintingonlywithbroadstrokeshere.ButIthinkyou'llseeshortlywhythesedetailswedocover,evenatahigh

level,arerelevant.

Foronething,JavaScriptenginesdon'tgettheluxury(likeotherlanguagecompilers)ofhavingplentyoftimetooptimize,

becauseJavaScriptcompilationdoesn'thappeninabuildstepaheadoftime,aswithotherlanguages.

ForJavaScript,thecompilationthatoccurshappens,inmanycases,meremicroseconds(orless!)beforethecodeis

executed.Toensurethefastestperformance,JSenginesuseallkindsoftricks(likeJITs,whichlazycompileandevenhot

re-compile,etc.)whicharewellbeyondthe"scope"ofourdiscussionhere.

Let'sjustsay,forsimplicity'ssake,thatanysnippetofJavaScripthastobecompiledbefore(usuallyrightbefore!)it's

executed.So,theJScompilerwilltaketheprogram

vara=2;

andcompileitfirst,andthenbereadytoexecuteit,usually

rightaway.

Thewaywewillapproachlearningaboutscopeistothinkoftheprocessintermsofaconversation.But,whoishavingthe

conversation?

Let'smeetthecastofcharactersthatinteracttoprocesstheprogram

vara=2;

,soweunderstandtheirconversations

thatwe'lllisteninonshortly:

1. Engine:responsibleforstart-to-finishcompilationandexecutionofourJavaScriptprogram.

2. Compiler:oneofEngine'sfriends;handlesallthedirtyworkofparsingandcode-generation(seeprevioussection).

3. Scope:anotherfriendofEngine;collectsandmaintainsalook-uplistofallthedeclaredidentifiers(variables),and

enforcesastrictsetofrulesastohowtheseareaccessibletocurrentlyexecutingcode.

ForyoutofullyunderstandhowJavaScriptworks,youneedtobegintothinklikeEngine(andfriends)think,askthe

questionstheyask,andanswerthosequestionsthesame.

Whenyouseetheprogram

vara=2;

,youmostlikelythinkofthatasonestatement.Butthat'snothowournewfriend

Engineseesit.Infact,Engineseestwodistinctstatements,onewhichCompilerwillhandleduringcompilation,andone

whichEnginewillhandleduringexecution.

So,let'sbreakdownhowEngineandfriendswillapproachtheprogram

vara=2;

.

ThefirstthingCompilerwilldowiththisprogramisperformlexingtobreakitdownintotokens,whichitwillthenparseintoa

tree.ButwhenCompilergetstocode-generation,itwilltreatthisprogramsomewhatdifferentlythanperhapsassumed.

AreasonableassumptionwouldbethatCompilerwillproducecodethatcouldbesummedupbythispseudo-code:

"Allocatememoryforavariable,labelit

a

,thenstickthevalue

2

intothatvariable."Unfortunately,that'snotquite

UnderstandingScope

TheCast

Back&Forth

YouDon'tKnowJS:Scope&Closures

8

Chapter1:WhatisScope?

background image

accurate.

Compilerwillinsteadproceedas:

1. Encountering

vara

,CompilerasksScopetoseeifavariable

a

alreadyexistsforthatparticularscopecollection.If

so,Compilerignoresthisdeclarationandmoveson.Otherwise,CompilerasksScopetodeclareanewvariablecalled

a

forthatscopecollection.

2. CompilerthenproducescodeforEnginetolaterexecute,tohandlethe

a=2

assignment.ThecodeEnginerunswill

firstaskScopeifthereisavariablecalled

a

accessibleinthecurrentscopecollection.Ifso,Engineusesthat

variable.Ifnot,Enginelookselsewhere(seenestedScopesectionbelow).

IfEngineeventuallyfindsavariable,itassignsthevalue

2

toit.Ifnot,Enginewillraiseitshandandyelloutanerror!

Tosummarize:twodistinctactionsaretakenforavariableassignment:First,Compilerdeclaresavariable(ifnotpreviously

declaredinthecurrentscope),andsecond,whenexecuting,EnginelooksupthevariableinScopeandassignstoit,if

found.

Weneedalittlebitmorecompilerterminologytoproceedfurtherwithunderstanding.

WhenEngineexecutesthecodethatCompilerproducedforstep(2),ithastolook-upthevariable

a

toseeifithasbeen

declared,andthislook-upisconsultingScope.Butthetypeoflook-upEngineperformsaffectstheoutcomeofthelook-up.

Inourcase,itissaidthatEnginewouldbeperformingan"LHS"look-upforthevariable

a

.Theothertypeoflook-upis

called"RHS".

Ibetyoucanguesswhatthe"L"and"R"mean.Thesetermsstandfor"Left-handSide"and"Right-handSide".

Side...ofwhat?Ofanassignmentoperation.

Inotherwords,anLHSlook-upisdonewhenavariableappearsontheleft-handsideofanassignmentoperation,andan

RHSlook-upisdonewhenavariableappearsontheright-handsideofanassignmentoperation.

Actually,let'sbealittlemoreprecise.AnRHSlook-upisindistinguishable,forourpurposes,fromsimplyalook-upofthe

valueofsomevariable,whereastheLHSlook-upistryingtofindthevariablecontaineritself,sothatitcanassign.Inthis

way,RHSdoesn'treallymean"right-handsideofanassignment"perse,itjust,moreaccurately,means"notleft-hand

side".

Beingslightlyglibforamoment,youcouldalsothink"RHS"insteadmeans"retrievehis/hersource(value)",implyingthat

RHSmeans"gogetthevalueof...".

Let'sdigintothatdeeper.

WhenIsay:

console

.log(a);

Thereferenceto

a

isanRHSreference,becausenothingisbeingassignedto

a

here.Instead,we'relooking-upto

retrievethevalueof

a

,sothatthevaluecanbepassedto

console.log(..)

.

Bycontrast:

CompilerSpeak

YouDon'tKnowJS:Scope&Closures

9

Chapter1:WhatisScope?

background image

a=

2

;

Thereferenceto

a

hereisanLHSreference,becausewedon'tactuallycarewhatthecurrentvalueis,wesimplywantto

findthevariableasatargetforthe

=2

assignmentoperation.

Note:LHSandRHSmeaning"left/right-handsideofanassignment"doesn'tnecessarilyliterallymean"left/rightsideofthe

=

assignmentoperator".Thereareseveralotherwaysthatassignmentshappen,andsoit'sbettertoconceptuallythink

aboutitas:"who'sthetargetoftheassignment(LHS)"and"who'sthesourceoftheassignment(RHS)".

Considerthisprogram,whichhasbothLHSandRHSreferences:

function

foo

(

a

)

{

console

.log(a);

//2

}

foo(

2

);

Thelastlinethatinvokes

foo(..)

asafunctioncallrequiresanRHSreferenceto

foo

,meaning,"golook-upthevalueof

foo

,andgiveittome."Moreover,

(..)

meansthevalueof

foo

shouldbeexecuted,soit'dbetteractuallybeafunction!

There'sasubtlebutimportantassignmenthere.Didyouspotit?

Youmayhavemissedtheimplied

a=2

inthiscodesnippet.Ithappenswhenthevalue

2

ispassedasanargumentto

the

foo(..)

function,inwhichcasethe

2

valueisassignedtotheparameter

a

.To(implicitly)assigntoparameter

a

,an

LHSlook-upisperformed.

There'salsoanRHSreferenceforthevalueof

a

,andthatresultingvalueispassedto

console.log(..)

.

console.log(..)

needsareferencetoexecute.It'sanRHSlook-upforthe

console

object,thenaproperty-resolutionoccurstoseeifithas

amethodcalled

log

.

Finally,wecanconceptualizethatthere'sanLHS/RHSexchangeofpassingthevalue

2

(bywayofvariable

a

'sRHS

look-up)into

log(..)

.Insideofthenativeimplementationof

log(..)

,wecanassumeithasparameters,thefirstofwhich

(perhapscalled

arg1

)hasanLHSreferencelook-up,beforeassigning

2

toit.

Note:Youmightbetemptedtoconceptualizethefunctiondeclaration

functionfoo(a){...

asanormalvariable

declarationandassignment,suchas

varfoo

and

foo=function(a){...

.Insodoing,itwouldbetemptingtothinkofthis

functiondeclarationasinvolvinganLHSlook-up.

However,thesubtlebutimportantdifferenceisthatCompilerhandlesboththedeclarationandthevaluedefinitionduring

code-generation,suchthatwhenEngineisexecutingcode,there'snoprocessingnecessaryto"assign"afunctionvalueto

foo

.Thus,it'snotreallyappropriatetothinkofafunctiondeclarationasanLHSlook-upassignmentinthewaywe're

discussingthemhere.

function

foo

(

a

)

{

console

.log(a);

//2

}

foo(

2

);

Let'simaginetheaboveexchange(whichprocessesthiscodesnippet)asaconversation.Theconversationwouldgoa

littlesomethinglikethis:

Engine/ScopeConversation

YouDon'tKnowJS:Scope&Closures

10

Chapter1:WhatisScope?

background image

Engine:HeyScope,IhaveanRHSreferencefor

foo

.Everheardofit?

Scope:Whyyes,Ihave.Compilerdeclareditjustasecondago.He'safunction.Hereyougo.

Engine:Great,thanks!OK,I'mexecuting

foo

.

Engine:Hey,Scope,I'vegotanLHSreferencefor

a

,everheardofit?

Scope:Whyyes,Ihave.Compilerdeclareditasaformalparameterto

foo

justrecently.Hereyougo.

Engine:Helpfulasalways,Scope.Thanksagain.Now,timetoassign

2

to

a

.

Engine:Hey,Scope,sorrytobotheryouagain.IneedanRHSlook-upfor

console

.Everheardofit?

Scope:Noproblem,Engine,thisiswhatIdoallday.Yes,I'vegot

console

.He'sbuilt-in.Hereyago.

Engine:Perfect.Lookingup

log(..)

.OK,great,it'safunction.

Engine:Yo,Scope.CanyouhelpmeoutwithanRHSreferenceto

a

.IthinkIrememberit,butjustwanttodouble-

check.

Scope:You'reright,Engine.Sameguy,hasn'tchanged.Hereyago.

Engine:Cool.Passingthevalueof

a

,whichis

2

,into

log(..)

.

...

Checkyourunderstandingsofar.MakesuretoplaythepartofEngineandhavea"conversation"withtheScope:

function

foo

(

a

)

{

var

b=a;

return

a+b;

}

var

c=foo(

2

);

1. IdentifyalltheLHSlook-ups(thereare3!).

2. IdentifyalltheRHSlook-ups(thereare4!).

Note:Seethechapterreviewforthequizanswers!

WesaidthatScopeisasetofrulesforlookingupvariablesbytheiridentifiername.There'susuallymorethanoneScope

toconsider,however.

Justasablockorfunctionisnestedinsideanotherblockorfunction,scopesarenestedinsideotherscopes.So,ifa

variablecannotbefoundintheimmediatescope,Engineconsultsthenextoutercontainingscope,continuinguntilfoundor

untiltheoutermost(aka,global)scopehasbeenreached.

Consider:

Quiz

NestedScope

YouDon'tKnowJS:Scope&Closures

11

Chapter1:WhatisScope?

background image

function

foo

(

a

)

{

console

.log(a+b);

}

var

b=

2

;

foo(

2

);

//4

TheRHSreferencefor

b

cannotberesolvedinsidethefunction

foo

,butitcanberesolvedintheScopesurroundingit(in

thiscase,theglobal).

So,revisitingtheconversationsbetweenEngineandScope,we'doverhear:

Engine:"Hey,Scopeof

foo

,everheardof

b

?GotanRHSreferenceforit."

Scope:"Nope,neverheardofit.Gofish."

Engine:"Hey,Scopeoutsideof

foo

,ohyou'retheglobalScope,okcool.Everheardof

b

?GotanRHSreference

forit."

Scope:"Yep,surehave.Hereyago."

ThesimplerulesfortraversingnestedScope:EnginestartsatthecurrentlyexecutingScope,looksforthevariablethere,

thenifnotfound,keepsgoinguponelevel,andsoon.Iftheoutermostglobalscopeisreached,thesearchstops,whether

itfindsthevariableornot.

TovisualizetheprocessofnestedScoperesolution,Iwantyoutothinkofthistallbuilding.

Thebuildingrepresentsourprogram'snestedScoperuleset.Thefirstfloorofthebuildingrepresentsyourcurrently

executingScope,whereveryouare.ThetoplevelofthebuildingistheglobalScope.

BuildingonMetaphors

YouDon'tKnowJS:Scope&Closures

12

Chapter1:WhatisScope?

background image

YouresolveLHSandRHSreferencesbylookingonyourcurrentfloor,andifyoudon'tfindit,takingtheelevatortothenext

floor,lookingthere,thenthenext,andsoon.Onceyougettothetopfloor(theglobalScope),youeitherfindwhatyou're

lookingfor,oryoudon't.Butyouhavetostopregardless.

WhydoesitmatterwhetherwecallitLHSorRHS?

Becausethesetwotypesoflook-upsbehavedifferentlyinthecircumstancewherethevariablehasnotyetbeendeclared

(isnotfoundinanyconsultedScope).

Consider:

function

foo

(

a

)

{

console

.log(a+b);

b=a;

}

foo(

2

);

WhentheRHSlook-upoccursfor

b

thefirsttime,itwillnotbefound.Thisissaidtobean"undeclared"variable,because

itisnotfoundinthescope.

IfanRHSlook-upfailstoeverfindavariable,anywhereinthenestedScopes,thisresultsina

ReferenceError

being

thrownbytheEngine.It'simportanttonotethattheerrorisofthetype

ReferenceError

.

Bycontrast,iftheEngineisperforminganLHSlook-up,anditarrivesatthetopfloor(globalScope)withoutfindingit,ifthe

programisnotrunningin"StrictMode"

note-strictmode

,thentheglobalScopewillcreateanewvariableofthatnameinthe

globalscope,andhanditbacktoEngine.

"No,therewasn'tonebefore,butIwashelpfulandcreatedoneforyou."

"StrictMode"

note-strictmode

,whichwasaddedinES5,hasanumberofdifferentbehaviorsfromnormal/relaxed/lazymode.

Onesuchbehavioristhatitdisallowstheautomatic/implicitglobalvariablecreation.Inthatcase,therewouldbenoglobal

Scope'dvariabletohandbackfromanLHSlook-up,andEnginewouldthrowa

ReferenceError

similarlytotheRHScase.

Now,ifavariableisfoundforanRHSlook-up,butyoutrytodosomethingwithitsvaluethatisimpossible,suchastryingto

execute-as-functionanon-functionvalue,orreferenceapropertyona

null

or

undefined

value,thenEnginethrowsa

differentkindoferror,calleda

TypeError

.

ReferenceError

isScoperesolution-failurerelated,whereas

TypeError

impliesthatScoperesolutionwassuccessful,but

thattherewasanillegal/impossibleactionattemptedagainsttheresult.

Scopeisthesetofrulesthatdetermineswhereandhowavariable(identifier)canbelooked-up.Thislook-upmaybefor

thepurposesofassigningtothevariable,whichisanLHS(left-hand-side)reference,oritmaybeforthepurposesof

retrievingitsvalue,whichisanRHS(right-hand-side)reference.

LHSreferencesresultfromassignmentoperations.Scope-relatedassignmentscanoccureitherwiththe

=

operatororby

passingargumentsto(assignto)functionparameters.

TheJavaScriptEnginefirstcompilescodebeforeitexecutes,andinsodoing,itsplitsupstatementslike

vara=2;

into

Errors

Review(TL;DR)

YouDon'tKnowJS:Scope&Closures

13

Chapter1:WhatisScope?

background image

twoseparatesteps:

1. First,

vara

todeclareitinthatScope.Thisisperformedatthebeginning,beforecodeexecution.

2. Later,

a=2

tolookupthevariable(LHSreference)andassigntoitiffound.

BothLHSandRHSreferencelook-upsstartatthecurrentlyexecutingScope,andifneedbe(thatis,theydon'tfindwhat

they'relookingforthere),theyworktheirwayupthenestedScope,onescope(floor)atatime,lookingfortheidentifier,

untiltheygettotheglobal(topfloor)andstop,andeitherfindit,ordon't.

UnfulfilledRHSreferencesresultin

ReferenceError

sbeingthrown.UnfulfilledLHSreferencesresultinanautomatic,

implicitly-createdglobalofthatname(ifnotin"StrictMode"

note-strictmode

),ora

ReferenceError

(ifin"StrictMode"

note-

strictmode

).

function

foo

(

a

)

{

var

b=a;

return

a+b;

}

var

c=foo(

2

);

1. IdentifyalltheLHSlook-ups(thereare3!).

c=..

,

a=2

(implicitparamassignment)and

b=..

2. IdentifyalltheRHSlook-ups(thereare4!).

foo(2..

,

=a;

,

a+..

and

..+b

note-strictmode

.MDN:

StrictMode

QuizAnswers

YouDon'tKnowJS:Scope&Closures

14

Chapter1:WhatisScope?

background image

InChapter1,wedefined"scope"asthesetofrulesthatgovernhowtheEnginecanlookupavariablebyitsidentifier

nameandfindit,eitherinthecurrentScope,orinanyoftheNestedScopesit'scontainedwithin.

Therearetwopredominantmodelsforhowscopeworks.Thefirstoftheseisbyfarthemostcommon,usedbythevast

majorityofprogramminglanguages.It'scalledLexicalScope,andwewillexamineitin-depth.Theothermodel,whichis

stillusedbysomelanguages(suchasBashscripting,somemodesinPerl,etc.)iscalledDynamicScope.

DynamicScopeiscoveredinAppendixA.ImentionithereonlytoprovideacontrastwithLexicalScope,whichisthe

scopemodelthatJavaScriptemploys.

AswediscussedinChapter1,thefirsttraditionalphaseofastandardlanguagecompileriscalledlexing(aka,tokenizing).

Ifyourecall,thelexingprocessexaminesastringofsourcecodecharactersandassignssemanticmeaningtothetokens

asaresultofsomestatefulparsing.

Itisthisconceptwhichprovidesthefoundationtounderstandwhatlexicalscopeisandwherethenamecomesfrom.

Todefineitsomewhatcircularly,lexicalscopeisscopethatisdefinedatlexingtime.Inotherwords,lexicalscopeisbased

onwherevariablesandblocksofscopeareauthored,byyou,atwritetime,andthusis(mostly)setinstonebythetimethe

lexerprocessesyourcode.

Note:Wewillseeinalittlebittherearesomewaystocheatlexicalscope,therebymodifyingitafterthelexerhaspassed

by,butthesearefrownedupon.Itisconsideredbestpracticetotreatlexicalscopeas,infact,lexical-only,andthusentirely

author-timeinnature.

Let'sconsiderthisblockofcode:

function

foo

(

a

)

{

var

b=a*

2

;

function

bar

(

c

)

{

console

.log(a,b,c);

}

bar(b*

3

);

}

foo(

2

);

//2412

Therearethreenestedscopesinherentinthiscodeexample.Itmaybehelpfultothinkaboutthesescopesasbubbles

insideofeachother.

Chapter2:LexicalScope

Lex-time

YouDon'tKnowJS:Scope&Closures

15

Chapter2:LexicalScope

background image

Bubble1encompassestheglobalscope,andhasjustoneidentifierinit:

foo

.

Bubble2encompassesthescopeof

foo

,whichincludesthethreeidentifiers:

a

,

bar

and

b

.

Bubble3encompassesthescopeof

bar

,anditincludesjustoneidentifier:

c

.

Scopebubblesaredefinedbywheretheblocksofscopearewritten,whichoneisnestedinsidetheother,etc.Inthenext

chapter,we'lldiscussdifferentunitsofscope,butfornow,let'sjustassumethateachfunctioncreatesanewbubbleof

scope.

Thebubblefor

bar

isentirelycontainedwithinthebubblefor

foo

,because(andonlybecause)that'swherewechoseto

definethefunction

bar

.

Noticethatthesenestedbubblesarestrictlynested.We'renottalkingaboutVenndiagramswherethebubblescancross

boundaries.Inotherwords,nobubbleforsomefunctioncansimultaneouslyexist(partially)insidetwootherouterscope

bubbles,justasnofunctioncanpartiallybeinsideeachoftwoparentfunctions.

ThestructureandrelativeplacementofthesescopebubblesfullyexplainstotheEnginealltheplacesitneedstolookto

findanidentifier.

Intheabovecodesnippet,theEngineexecutesthe

console.log(..)

statementandgoeslookingforthethreereferenced

variables

a

,

b

,and

c

.Itfirststartswiththeinnermostscopebubble,thescopeofthe

bar(..)

function.Itwon'tfind

a

there,soitgoesuponelevel,outtothenextnearestscopebubble,thescopeof

foo(..)

.Itfinds

a

there,andsoituses

that

a

.Samethingfor

b

.But

c

,itdoesfindinsideof

bar(..)

.

Hadtherebeena

c

bothinsideof

bar(..)

andinsideof

foo(..)

,the

console.log(..)

statementwouldhavefoundand

usedtheonein

bar(..)

,nevergettingtotheonein

foo(..)

.

Scopelook-upstopsonceitfindsthefirstmatch.Thesameidentifiernamecanbespecifiedatmultiplelayersof

nestedscope,whichiscalled"shadowing"(theinneridentifier"shadows"theouteridentifier).Regardlessofshadowing,

scopelook-upalwaysstartsattheinnermostscopebeingexecutedatthetime,andworksitswayoutward/upwarduntilthe

firstmatch,andstops.

Note:Globalvariablesarealsoautomaticallypropertiesoftheglobalobject(

window

inbrowsers,etc.),soitispossibleto

referenceaglobalvariablenotdirectlybyitslexicalname,butinsteadindirectlyasapropertyreferenceoftheglobalobject.

Look-ups

YouDon'tKnowJS:Scope&Closures

16

Chapter2:LexicalScope

background image

window

.a

Thistechniquegivesaccesstoaglobalvariablewhichwouldotherwisebeinaccessibleduetoitbeingshadowed.

However,non-globalshadowedvariablescannotbeaccessed.

Nomatterwhereafunctionisinvokedfrom,orevenhowitisinvoked,itslexicalscopeisonlydefinedbywherethe

functionwasdeclared.

Thelexicalscopelook-upprocessonlyappliestofirst-classidentifiers,suchasthe

a

,

b

,and

c

.Ifyouhadareferenceto

foo.bar.baz

inapieceofcode,thelexicalscopelook-upwouldapplytofindingthe

foo

identifier,butonceitlocatesthat

variable,objectproperty-accessrulestakeovertoresolvethe

bar

and

baz

properties,respectively.

Iflexicalscopeisdefinedonlybywhereafunctionisdeclared,whichisentirelyanauthor-timedecision,howcouldthere

possiblybeawayto"modify"(aka,cheat)lexicalscopeatrun-time?

JavaScripthastwosuchmechanisms.Bothofthemareequallyfrowned-uponinthewidercommunityasbadpracticesto

useinyourcode.Butthetypicalargumentsagainstthemareoftenmissingthemostimportantpoint:cheatinglexical

scopeleadstopoorerperformance.

BeforeIexplaintheperformanceissue,though,let'slookathowthesetwomechanismswork.

The

eval(..)

functioninJavaScripttakesastringasanargument,andtreatsthecontentsofthestringasifithadactually

beenauthoredcodeatthatpointintheprogram.Inotherwords,youcanprogrammaticallygeneratecodeinsideofyour

authoredcode,andrunthegeneratedcodeasifithadbeenthereatauthortime.

Evaluating

eval(..)

(punintended)inthatlight,itshouldbeclearhow

eval(..)

allowsyoutomodifythelexicalscope

environmentbycheatingandpretendingthatauthor-time(aka,lexical)codewasthereallalong.

Onsubsequentlinesofcodeafteran

eval(..)

hasexecuted,theEnginewillnot"know"or"care"thatthepreviouscodein

questionwasdynamicallyinterpretedandthusmodifiedthelexicalscopeenvironment.TheEnginewillsimplyperformits

lexicalscopelook-upsasitalwaysdoes.

Considerthefollowingcode:

function

foo

(

str,a

)

{

eval

(str);

//cheating!

console

.log(a,b);

}

var

b=

2

;

foo(

"varb=3;"

,

1

);

//1,3

Thestring

"varb=3;"

istreated,atthepointofthe

eval(..)

call,ascodethatwasthereallalong.Becausethatcode

happenstodeclareanewvariable

b

,itmodifiestheexistinglexicalscopeof

foo(..)

.Infact,asmentionedabove,this

codeactuallycreatesvariable

b

insideof

foo(..)

thatshadowsthe

b

thatwasdeclaredintheouter(global)scope.

Whenthe

console.log(..)

calloccurs,itfindsboth

a

and

b

inthescopeof

foo(..)

,andneverfindstheouter

b

.Thus,

weprintout"1,3"insteadof"1,2"aswouldhavenormallybeenthecase.

CheatingLexical

eval

YouDon'tKnowJS:Scope&Closures

17

Chapter2:LexicalScope

background image

Note:Inthisexample,forsimplicity'ssake,thestringof"code"wepassinwasafixedliteral.Butitcouldeasilyhavebeen

programmaticallycreatedbyaddingcharacterstogetherbasedonyourprogram'slogic.

eval(..)

isusuallyusedto

executedynamicallycreatedcode,asdynamicallyevaluatingessentiallystaticcodefromastringliteralwouldprovideno

realbenefittojustauthoringthecodedirectly.

Bydefault,ifastringofcodethat

eval(..)

executescontainsoneormoredeclarations(eithervariablesorfunctions),this

actionmodifiestheexistinglexicalscopeinwhichthe

eval(..)

resides.Technically,

eval(..)

canbeinvoked"indirectly",

throughvarioustricks(beyondourdiscussionhere),whichcausesittoinsteadexecuteinthecontextoftheglobalscope,

thusmodifyingit.Butineithercase,

eval(..)

canatruntimemodifyanauthor-timelexicalscope.

Note:

eval(..)

whenusedinastrict-modeprogramoperatesinitsownlexicalscope,whichmeansdeclarationsmade

insideoftheeval()donotactuallymodifytheenclosingscope.

function

foo

(

str

)

{

"usestrict";

eval

(str);

console

.log(a);

//ReferenceError:aisnotdefined

}

foo(

"vara=2"

);

ThereareotherfacilitiesinJavaScriptwhichamounttoaverysimilareffectto

eval(..)

.

setTimeout(..)

and

setInterval(..)

cantakeastringfortheirrespectivefirstargument,thecontentsofwhichare

eval

uatedasthecodeofa

dynamically-generatedfunction.Thisisold,legacybehaviorandlong-sincedeprecated.Don'tdoit!

The

newFunction(..)

functionconstructorsimilarlytakesastringofcodeinitslastargumenttoturnintoadynamically-

generatedfunction(thefirstargument(s),ifany,arethenamedparametersforthenewfunction).Thisfunction-constructor

syntaxisslightlysaferthan

eval(..)

,butitshouldstillbeavoidedinyourcode.

Theuse-casesfordynamicallygeneratingcodeinsideyourprogramareincrediblyrare,astheperformancedegradations

arealmostneverworththecapability.

Theotherfrowned-upon(andnowdeprecated!)featureinJavaScriptwhichcheatslexicalscopeisthe

with

keyword.

Therearemultiplevalidwaysthat

with

canbeexplained,butIwillchooseheretoexplainitfromtheperspectiveofhowit

interactswithandaffectslexicalscope.

with

istypicallyexplainedasashort-handformakingmultiplepropertyreferencesagainstanobjectwithoutrepeatingthe

objectreferenceitselfeachtime.

Forexample:

var

obj={

a:

1

,

b:

2

,

c:

3

};

//more"tedious"torepeat"obj"

obj.a=

2

;

obj.b=

3

;

obj.c=

4

;

//"easier"short-hand

with

(obj){

a=

3

;

b=

4

;

c=

5

;

with

YouDon'tKnowJS:Scope&Closures

18

Chapter2:LexicalScope

background image

}

However,there'smuchmoregoingonherethanjustaconvenientshort-handforobjectpropertyaccess.Consider:

function

foo

(

obj

)

{

with

(obj){

a=

2

;

}

}

var

o1={

a:

3

};

var

o2={

b:

3

};

foo(o1);

console

.log(o1.a);

//2

foo(o2);

console

.log(o2.a);

//undefined

console

.log(a);

//2--Oops,leakedglobal!

Inthiscodeexample,twoobjects

o1

and

o2

arecreated.Onehasan

a

property,andtheotherdoesnot.The

foo(..)

functiontakesanobjectreference

obj

asanargument,andcalls

with(obj){..}

onthereference.Insidethe

with

block,wemakewhatappearstobeanormallexicalreferencetoavariable

a

,anLHSreferenceinfact(seeChapter1),to

assigntoitthevalueof

2

.

Whenwepassin

o1

,the

a=2

assignmentfindstheproperty

o1.a

andassignsitthevalue

2

,asreflectedinthe

subsequent

console.log(o1.a)

statement.However,whenwepassin

o2

,sinceitdoesnothavean

a

property,nosuch

propertyiscreated,and

o2.a

remains

undefined

.

Butthenwenoteapeculiarside-effect,thefactthataglobalvariable

a

wascreatedbythe

a=2

assignment.Howcan

thisbe?

The

with

statementtakesanobject,onewhichhaszeroormoreproperties,andtreatsthatobjectasifitisawholly

separatelexicalscope,andthustheobject'spropertiesaretreatedaslexicallydefinedidentifiersinthat"scope".

Note:Eventhougha

with

blocktreatsanobjectlikealexicalscope,anormal

var

declarationinsidethat

with

blockwill

notbescopedtothat

with

block,butinsteadthecontainingfunctionscope.

Whilethe

eval(..)

functioncanmodifyexistinglexicalscopeifittakesastringofcodewithoneormoredeclarationsinit,

the

with

statementactuallycreatesawholenewlexicalscopeoutofthinair,fromtheobjectyoupasstoit.

Understoodinthisway,the"scope"declaredbythe

with

statementwhenwepassedin

o1

was

o1

,andthat"scope"had

an"identifier"initwhichcorrespondstothe

o1.a

property.Butwhenweused

o2

asthe"scope",ithadnosuch

a

"identifier"init,andsothenormalrulesofLHSidentifierlook-up(seeChapter1)occurred.

Neitherthe"scope"of

o2

,northescopeof

foo(..)

,northeglobalscopeeven,hasan

a

identifiertobefound,sowhen

a=2

isexecuted,itresultsintheautomatic-globalbeingcreated(sincewe'reinnon-strictmode).

Itisastrangesortofmind-bendingthoughttosee

with

turning,atruntime,anobjectanditspropertiesintoa"scope"with

"identifiers".ButthatistheclearestexplanationIcangivefortheresultswesee.

Note:Inadditiontobeingabadideatouse,both

eval(..)

and

with

areaffected(restricted)byStrictMode.

with

is

outrightdisallowed,whereasvariousformsofindirectorunsafe

eval(..)

aredisallowedwhileretainingthecore

functionality.

YouDon'tKnowJS:Scope&Closures

19

Chapter2:LexicalScope

background image

Both

eval(..)

and

with

cheattheotherwiseauthor-timedefinedlexicalscopebymodifyingorcreatingnewlexicalscope

atruntime.

So,what'sthebigdeal,youask?Iftheyoffermoresophisticatedfunctionalityandcodingflexibility,aren'tthesegood

features?No.

TheJavaScriptEnginehasanumberofperformanceoptimizationsthatitperformsduringthecompilationphase.Someof

theseboildowntobeingabletoessentiallystaticallyanalyzethecodeasitlexes,andpre-determinewhereallthevariable

andfunctiondeclarationsare,sothatittakeslessefforttoresolveidentifiersduringexecution.

ButiftheEnginefindsan

eval(..)

or

with

inthecode,itessentiallyhastoassumethatallitsawarenessofidentifier

locationmaybeinvalid,becauseitcannotknowatlexingtimeexactlywhatcodeyoumaypassto

eval(..)

tomodifythe

lexicalscope,orthecontentsoftheobjectyoumaypassto

with

tocreateanewlexicalscopetobeconsulted.

Inotherwords,inthepessimisticsense,mostofthoseoptimizationsitwouldmakearepointlessif

eval(..)

or

with

are

present,soitsimplydoesn'tperformtheoptimizationsatall.

Yourcodewillalmostcertainlytendtorunslowersimplybythefactthatyouincludean

eval(..)

or

with

anywhereinthe

code.NomatterhowsmarttheEnginemaybeabouttryingtolimittheside-effectsofthesepessimisticassumptions,

there'snogettingaroundthefactthatwithouttheoptimizations,coderunsslower.

Lexicalscopemeansthatscopeisdefinedbyauthor-timedecisionsofwherefunctionsaredeclared.Thelexingphaseof

compilationisessentiallyabletoknowwhereandhowallidentifiersaredeclared,andthuspredicthowtheywillbelooked-

upduringexecution.

TwomechanismsinJavaScriptcan"cheat"lexicalscope:

eval(..)

and

with

.Theformercanmodifyexistinglexical

scope(atruntime)byevaluatingastringof"code"whichhasoneormoredeclarationsinit.Thelatteressentiallycreatesa

wholenewlexicalscope(again,atruntime)bytreatinganobjectreferenceasa"scope"andthatobject'spropertiesas

scopedidentifiers.

ThedownsidetothesemechanismsisthatitdefeatstheEngine'sabilitytoperformcompile-timeoptimizationsregarding

scopelook-up,becausetheEnginehastoassumepessimisticallythatsuchoptimizationswillbeinvalid.Codewillrun

slowerasaresultofusingeitherfeature.Don'tusethem.

Performance

Review(TL;DR)

YouDon'tKnowJS:Scope&Closures

20

Chapter2:LexicalScope

background image

AsweexploredinChapter2,scopeconsistsofaseriesof"bubbles"thateachactasacontainerorbucket,inwhich

identifiers(variables,functions)aredeclared.Thesebubblesnestneatlyinsideeachother,andthisnestingisdefinedat

author-time.

Butwhatexactlymakesanewbubble?Isitonlythefunction?CanotherstructuresinJavaScriptcreatebubblesofscope?

ThemostcommonanswertothosequestionsisthatJavaScripthasfunction-basedscope.Thatis,eachfunctionyou

declarecreatesabubbleforitself,butnootherstructurescreatetheirownscopebubbles.Aswe'llseeinjustalittlebit,this

isnotquitetrue.

Butfirst,let'sexplorefunctionscopeanditsimplications.

Considerthiscode:

function

foo

(

a

)

{

var

b=

2

;

//somecode

function

bar

()

{

//...

}

//morecode

var

c=

3

;

}

Inthissnippet,thescopebubblefor

foo(..)

includesidentifiers

a

,

b

,

c

and

bar

.Itdoesn'tmatterwhereinthescope

adeclarationappears,thevariableorfunctionbelongstothecontainingscopebubble,regardless.We'llexplorehow

exactlythatworksinthenextchapter.

bar(..)

hasitsownscopebubble.Sodoestheglobalscope,whichhasjustoneidentifierattachedtoit:

foo

.

Because

a

,

b

,

c

,and

bar

allbelongtothescopebubbleof

foo(..)

,theyarenotaccessibleoutsideof

foo(..)

.That

is,thefollowingcodewouldallresultin

ReferenceError

errors,astheidentifiersarenotavailabletotheglobalscope:

bar();

//fails

console

.log(a,b,c);

//all3fail

However,alltheseidentifiers(

a

,

b

,

c

,

foo

,and

bar

)areaccessibleinsideof

foo(..)

,andindeedalsoavailableinside

of

bar(..)

(assumingtherearenoshadowidentifierdeclarationsinside

bar(..)

).

Functionscopeencouragestheideathatallvariablesbelongtothefunction,andcanbeusedandre-usedthroughoutthe

entiretyofthefunction(andindeed,accessibleeventonestedscopes).Thisdesignapproachcanbequiteuseful,and

certainlycanmakefulluseofthe"dynamic"natureofJavaScriptvariablestotakeonvaluesofdifferenttypesasneeded.

Ontheotherhand,ifyoudon'ttakecarefulprecautions,variablesexistingacrosstheentiretyofascopecanleadtosome

unexpectedpitfalls.

Chapter3:Functionvs.BlockScope

ScopeFromFunctions

YouDon'tKnowJS:Scope&Closures

21

Chapter3:Functionvs.BlockScope

background image

Thetraditionalwayofthinkingaboutfunctionsisthatyoudeclareafunction,andthenaddcodeinsideit.Buttheinverse

thinkingisequallypowerfulanduseful:takeanyarbitrarysectionofcodeyou'vewritten,andwrapafunctiondeclaration

aroundit,whichineffect"hides"thecode.

Thepracticalresultistocreateascopebubblearoundthecodeinquestion,whichmeansthatanydeclarations(variableor

function)inthatcodewillnowbetiedtothescopeofthenewwrappingfunction,ratherthanthepreviouslyenclosing

scope.Inotherwords,youcan"hide"variablesandfunctionsbyenclosingtheminthescopeofafunction.

Whywould"hiding"variablesandfunctionsbeausefultechnique?

There'savarietyofreasonsmotivatingthisscope-basedhiding.Theytendtoarisefromthesoftwaredesignprinciple

"PrincipleofLeastPrivilege"

note-leastprivilege

,alsosometimescalled"LeastAuthority"or"LeastExposure".Thisprinciple

statesthatinthedesignofsoftware,suchastheAPIforamodule/object,youshouldexposeonlywhatisminimally

necessary,and"hide"everythingelse.

Thisprincipleextendstothechoiceofwhichscopetocontainvariablesandfunctions.Ifallvariablesandfunctionswerein

theglobalscope,theywouldofcoursebeaccessibletoanynestedscope.Butthiswouldviolatethe"Least..."principlein

thatyouare(likely)exposingmanyvariablesorfunctionswhichyoushouldotherwisekeepprivate,asproperuseofthe

codewoulddiscourageaccesstothosevariables/functions.

Forexample:

function

doSomething

(

a

)

{

b=a+doSomethingElse(a*

2

);

console

.log(b*

3

);

}

function

doSomethingElse

(

a

)

{

return

a-

1

;

}

var

b;

doSomething(

2

);

//15

Inthissnippet,the

b

variableandthe

doSomethingElse(..)

functionarelikely"private"detailsofhow

doSomething(..)

doesitsjob.Givingtheenclosingscope"access"to

b

and

doSomethingElse(..)

isnotonlyunnecessarybutalsopossibly

"dangerous",inthattheymaybeusedinunexpectedways,intentionallyornot,andthismayviolatepre-condition

assumptionsof

doSomething(..)

.

Amore"proper"designwouldhidetheseprivatedetailsinsidethescopeof

doSomething(..)

,suchas:

function

doSomething

(

a

)

{

function

doSomethingElse

(

a

)

{

return

a-

1

;

}

var

b;

b=a+doSomethingElse(a*

2

);

console

.log((b*

3

));

}

doSomething(

2

);

//15

HidingInPlainScope

YouDon'tKnowJS:Scope&Closures

22

Chapter3:Functionvs.BlockScope

background image

Now,

b

and

doSomethingElse(..)

arenotaccessibletoanyoutsideinfluence,insteadcontrolledonlyby

doSomething(..)

.

Thefunctionalityandend-resulthasnotbeenaffected,butthedesignkeepsprivatedetailsprivate,whichisusually

consideredbettersoftware.

Anotherbenefitof"hiding"variablesandfunctionsinsideascopeistoavoidunintendedcollisionbetweentwodifferent

identifierswiththesamenamebutdifferentintendedusages.Collisionresultsofteninunexpectedoverwritingofvalues.

Forexample:

function

foo

()

{

function

bar

(

a

)

{

i=

3

;

//changingthe`i`intheenclosingscope'sfor-loop

console

.log(a+i);

}

for

(

var

i=

0

;i<

10

;i++){

bar(i*

2

);

//oops,infiniteloopahead!

}

}

foo();

The

i=3

assignmentinsideof

bar(..)

overwrites,unexpectedly,the

i

thatwasdeclaredin

foo(..)

atthefor-loop.In

thiscase,itwillresultinaninfiniteloop,because

i

issettoafixedvalueof

3

andthatwillforeverremain

<10

.

Theassignmentinside

bar(..)

needstodeclarealocalvariabletouse,regardlessofwhatidentifiernameischosen.

var

i=3;

wouldfixtheproblem(andwouldcreatethepreviouslymentioned"shadowedvariable"declarationfor

i

).An

additional,notalternate,optionistopickanotheridentifiernameentirely,suchas

varj=3;

.Butyoursoftwaredesign

maynaturallycallforthesameidentifiername,soutilizingscopeto"hide"yourinnerdeclarationisyourbest/onlyoptionin

thatcase.

Aparticularlystrongexampleof(likely)variablecollisionoccursintheglobalscope.Multiplelibrariesloadedintoyour

programcanquiteeasilycollidewitheachotheriftheydon'tproperlyhidetheirinternal/privatefunctionsandvariables.

Suchlibrariestypicallywillcreateasinglevariabledeclaration,oftenanobject,withasufficientlyuniquename,intheglobal

scope.Thisobjectisthenusedasa"namespace"forthatlibrary,whereallspecificexposuresoffunctionalityaremadeas

propertiesoffthatobject(namespace),ratherthanastop-levellexicallyscopedidentifiersthemselves.

Forexample:

var

MyReallyCoolLibrary={

awesome:

"stuff"

,

doSomething:

function

()

{

//...

},

doAnotherThing:

function

()

{

//...

}

};

Anotheroptionforcollisionavoidanceisthemoremodern"module"approach,usinganyofvariousdependencymanagers.

CollisionAvoidance

Global"Namespaces"

ModuleManagement

YouDon'tKnowJS:Scope&Closures

23

Chapter3:Functionvs.BlockScope

background image

Usingthesetools,nolibrarieseveraddanyidentifierstotheglobalscope,butareinsteadrequiredtohavetheiridentifier(s)

beexplicitlyimportedintoanotherspecificscopethroughusageofthedependencymanager'svariousmechanisms.

Itshouldbeobservedthatthesetoolsdonotpossess"magic"functionalitythatisexemptfromlexicalscopingrules.They

simplyusetherulesofscopingasexplainedheretoenforcethatnoidentifiersareinjectedintoanysharedscope,andare

insteadkeptinprivate,non-collision-susceptiblescopes,whichpreventsanyaccidentalscopecollisions.

Assuch,youcancodedefensivelyandachievethesameresultsasthedependencymanagersdowithoutactuallyneeding

tousethem,ifyousochoose.SeetheChapter5formoreinformationaboutthemodulepattern.

We'veseenthatwecantakeanysnippetofcodeandwrapafunctionaroundit,andthateffectively"hides"anyenclosed

variableorfunctiondeclarationsfromtheoutsidescopeinsidethatfunction'sinnerscope.

Forexample:

var

a=

2

;

function

foo

()

{

//<--insertthis

var

a=

3

;

console

.log(a);

//3

}

//<--andthis

foo();

//<--andthis

console

.log(a);

//2

Whilethistechnique"works",itisnotnecessarilyveryideal.Thereareafewproblemsitintroduces.Thefirstisthatwe

havetodeclareanamed-function

foo()

,whichmeansthattheidentifiername

foo

itself"pollutes"theenclosingscope

(global,inthiscase).Wealsohavetoexplicitlycallthefunctionbyname(

foo()

)sothatthewrappedcodeactually

executes.

Itwouldbemoreidealifthefunctiondidn'tneedaname(or,rather,thenamedidn'tpollutetheenclosingscope),andifthe

functioncouldautomaticallybeexecuted.

Fortunately,JavaScriptoffersasolutiontobothproblems.

var

a=

2

;

(

function

foo

()

{

//<--insertthis

var

a=

3

;

console

.log(a);

//3

})();

//<--andthis

console

.log(a);

//2

Let'sbreakdownwhat'shappeninghere.

First,noticethatthewrappingfunctionstatementstartswith

(function...

asopposedtojust

function...

.Whilethismay

seemlikeaminordetail,it'sactuallyamajorchange.Insteadoftreatingthefunctionasastandarddeclaration,thefunction

istreatedasafunction-expression.

Note:Theeasiestwaytodistinguishdeclarationvs.expressionisthepositionoftheword"function"inthestatement(not

FunctionsAsScopes

YouDon'tKnowJS:Scope&Closures

24

Chapter3:Functionvs.BlockScope

background image

justaline,butadistinctstatement).If"function"istheveryfirstthinginthestatement,thenit'safunctiondeclaration.

Otherwise,it'safunctionexpression.

Thekeydifferencewecanobserveherebetweenafunctiondeclarationandafunctionexpressionrelatestowhereits

nameisboundasanidentifier.

Comparetheprevioustwosnippets.Inthefirstsnippet,thename

foo

isboundintheenclosingscope,andwecallit

directlywith

foo()

.Inthesecondsnippet,thename

foo

isnotboundintheenclosingscope,butinsteadisboundonly

insideofitsownfunction.

Inotherwords,

(functionfoo(){..})

asanexpressionmeanstheidentifier

foo

isfoundonlyinthescopewherethe

..

indicates,notintheouterscope.Hidingthename

foo

insideitselfmeansitdoesnotpollutetheenclosingscope

unnecessarily.

Youareprobablymostfamiliarwithfunctionexpressionsascallbackparameters,suchas:

setTimeout(

function

()

{

console

.log(

"Iwaited1second!"

);

},

1000

);

Thisiscalledan"anonymousfunctionexpression",because

function()...

hasnonameidentifieronit.Function

expressionscanbeanonymous,butfunctiondeclarationscannotomitthename--thatwouldbeillegalJSgrammar.

Anonymousfunctionexpressionsarequickandeasytotype,andmanylibrariesandtoolstendtoencouragethisidiomatic

styleofcode.However,theyhaveseveraldraw-backstoconsider:

1. Anonymousfunctionshavenousefulnametodisplayinstacktraces,whichcanmakedebuggingmoredifficult.

2. Withoutaname,ifthefunctionneedstorefertoitself,forrecursion,etc.,thedeprecated

arguments.callee

reference

isunfortunatelyrequired.Anotherexampleofneedingtoself-referenceiswhenaneventhandlerfunctionwantsto

unbinditselfafteritfires.

3. Anonymousfunctionsomitanamewhichisoftenhelpfulinprovidingmorereadable/understandablecode.A

descriptivenamehelpsself-documentthecodeinquestion.

Inlinefunctionexpressionsarepowerfulanduseful--thequestionofanonymousvs.nameddoesn'tdetractfromthat.

Providinganameforyourfunctionexpressionquiteeffectivelyaddressesallthesedraw-backs,buthasnotangible

downsides.Thebestpracticeistoalwaysnameyourfunctionexpressions:

setTimeout(

function

timeoutHandler

()

{

//<--Look,Ihaveaname!

console

.log(

"Iwaited1second!"

);

},

1000

);

var

a=

2

;

(

function

foo

()

{

var

a=

3

;

console

.log(a);

//3

})();

Anonymousvs.Named

InvokingFunctionExpressionsImmediately

YouDon'tKnowJS:Scope&Closures

25

Chapter3:Functionvs.BlockScope

background image

console

.log(a);

//2

Nowthatwehaveafunctionasanexpressionbyvirtueofwrappingitina

()

pair,wecanexecutethatfunctionbyadding

another

()

ontheend,like

(functionfoo(){..})()

.Thefirstenclosing

()

pairmakesthefunctionanexpression,and

thesecond

()

executesthefunction.

Thispatternissocommon,afewyearsagothecommunityagreedonatermforit:IIFE,whichstandsforImmediately

InvokedFunctionExpression.

Ofcourse,IIFE'sdon'tneednames,necessarily--themostcommonformofIIFEistouseananonymousfunction

expression.Whilecertainlylesscommon,naminganIIFEhasalltheaforementionedbenefitsoveranonymousfunction

expressions,soit'sagoodpracticetoadopt.

var

a=

2

;

(

function

IIFE

()

{

var

a=

3

;

console

.log(a);

//3

})();

console

.log(a);

//2

There'saslightvariationonthetraditionalIIFEform,whichsomeprefer:

(function(){..}())

.Lookcloselytoseethe

difference.Inthefirstform,thefunctionexpressioniswrappedin

()

,andthentheinvoking

()

pairisontheoutsideright

afterit.Inthesecondform,theinvoking

()

pairismovedtotheinsideoftheouter

()

wrappingpair.

Thesetwoformsareidenticalinfunctionality.It'spurelyastylisticchoicewhichyouprefer.

AnothervariationonIIFE'swhichisquitecommonistousethefactthattheyare,infact,justfunctioncalls,andpassin

argument(s).

Forinstance:

var

a=

2

;

(

function

IIFE

(

global

)

{

var

a=

3

;

console

.log(a);

//3

console

.log(global.a);

//2

})(

window

);

console

.log(a);

//2

Wepassinthe

window

objectreference,butwenametheparameter

global

,sothatwehaveaclearstylisticdelineation

forglobalvs.non-globalreferences.Ofcourse,youcanpassinanythingfromanenclosingscopeyouwant,andyoucan

nametheparameter(s)anythingthatsuitsyou.Thisismostlyjuststylisticchoice.

Anotherapplicationofthispatternaddressesthe(minorniche)concernthatthedefault

undefined

identifiermighthaveits

valueincorrectlyoverwritten,causingunexpectedresults.Bynamingaparameter

undefined

,butnotpassinganyvaluefor

thatargument,wecanguaranteethatthe

undefined

identifierisinfacttheundefinedvalueinablockofcode:

undefined

=

true

;

//settingaland-mineforothercode!avoid!

YouDon'tKnowJS:Scope&Closures

26

Chapter3:Functionvs.BlockScope

background image

(

function

IIFE

(

undefined

)

{

var

a;

if

(a===

undefined

){

console

.log(

"Undefinedissafehere!"

);

}

})();

StillanothervariationoftheIIFEinvertstheorderofthings,wherethefunctiontoexecuteisgivensecond,afterthe

invocationandparameterstopasstoit.ThispatternisusedintheUMD(UniversalModuleDefinition)project.Somepeople

finditalittlecleanertounderstand,thoughitisslightlymoreverbose.

var

a=

2

;

(

function

IIFE

(

def

)

{

def(

window

);

})(

function

def

(

global

)

{

var

a=

3

;

console

.log(a);

//3

console

.log(global.a);

//2

});

The

def

functionexpressionisdefinedinthesecond-halfofthesnippet,andthenpassedasaparameter(alsocalled

def

)tothe

IIFE

functiondefinedinthefirsthalfofthesnippet.Finally,theparameter

def

(thefunction)isinvoked,

passing

window

inasthe

global

parameter.

Whilefunctionsarethemostcommonunitofscope,andcertainlythemostwide-spreadofthedesignapproachesinthe

majorityofJSincirculation,otherunitsofscopearepossible,andtheusageoftheseotherscopeunitscanleadtoeven

better,cleanertomaintaincode.

ManylanguagesotherthanJavaScriptsupportBlockScope,andsodevelopersfromthoselanguagesareaccustomedto

themindset,whereasthosewho'veprimarilyonlyworkedinJavaScriptmayfindtheconceptslightlyforeign.

Butevenifyou'veneverwrittenasinglelineofcodeinblock-scopedfashion,youarestillprobablyfamiliarwiththis

extremelycommonidiominJavaScript:

for

(

var

i=

0

;i<

10

;i++){

console

.log(i);

}

Wedeclarethevariable

i

directlyinsidethefor-loophead,mostlikelybecauseourintentistouse

i

onlywithinthe

contextofthatfor-loop,andessentiallyignorethefactthatthevariableactuallyscopesitselftotheenclosingscope

(functionorglobal).

That'swhatblock-scopingisallabout.Declaringvariablesascloseaspossible,aslocalaspossible,towheretheywillbe

used.Anotherexample:

var

foo=

true

;

if

(foo){

var

bar=foo*

2

;

bar=something(bar);

BlocksAsScopes

YouDon'tKnowJS:Scope&Closures

27

Chapter3:Functionvs.BlockScope

background image

console

.log(bar);

}

Weareusinga

bar

variableonlyinthecontextoftheif-statement,soitmakesakindofsensethatwewoulddeclareit

insidetheif-block.However,wherewedeclarevariablesisnotrelevantwhenusing

var

,becausetheywillalwaysbelong

totheenclosingscope.Thissnippetisessentially"fake"block-scoping,forstylisticreasons,andrelyingonself-

enforcementnottoaccidentallyuse

bar

inanotherplaceinthatscope.

Blockscopeisatooltoextendtheearlier"PrincipleofLeastPrivilegeExposure"

note-leastprivilege

fromhidinginformationin

functionstohidinginformationinblocksofourcode.

Considerthefor-loopexampleagain:

for

(

var

i=

0

;i<

10

;i++){

console

.log(i);

}

Whypollutetheentirescopeofafunctionwiththe

i

variablethatisonlygoingtobe(oronlyshouldbe,atleast)usedfor

thefor-loop?

Butmoreimportantly,developersmayprefertocheckthemselvesagainstaccidentally(re)usingvariablesoutsideoftheir

intendedpurpose,suchasbeingissuedanerroraboutanunknownvariableifyoutrytouseitinthewrongplace.Block-

scoping(ifitwerepossible)forthe

i

variablewouldmake

i

availableonlyforthefor-loop,causinganerrorif

i

is

accessedelsewhereinthefunction.Thishelpsensurevariablesarenotre-usedinconfusingorhard-to-maintainways.

But,thesadrealityisthat,onthesurface,JavaScripthasnofacilityforblockscope.

Thatis,untilyoudigalittlefurther.

Welearnedabout

with

inChapter2.Whileitisafrowneduponconstruct,itisanexampleof(aformof)blockscope,in

thatthescopethatiscreatedfromtheobjectonlyexistsforthelifetimeofthat

with

statement,andnotintheenclosing

scope.

It'saverylittleknownfactthatJavaScriptinES3specifiedthevariabledeclarationinthe

catch

clauseofa

try/catch

to

beblock-scopedtothe

catch

block.

Forinstance:

try

{

undefined

();

//illegaloperationtoforceanexception!

}

catch

(err){

console

.log(err);

//works!

}

console

.log(err);

//ReferenceError:`err`notfound

Asyoucansee,

err

existsonlyinthe

catch

clause,andthrowsanerrorwhenyoutrytoreferenceitelsewhere.

Note:WhilethisbehaviorhasbeenspecifiedandtrueofpracticallyallstandardJSenvironments(exceptperhapsoldIE),

manylintersseemtostillcomplainifyouhavetwoormore

catch

clausesinthesamescopewhicheachdeclaretheirerror

with

try/catch

YouDon'tKnowJS:Scope&Closures

28

Chapter3:Functionvs.BlockScope

background image

variablewiththesameidentifiername.Thisisnotactuallyare-definition,sincethevariablesaresafelyblock-scoped,but

thelintersstillseemto,annoyingly,complainaboutthisfact.

Toavoidtheseunnecessarywarnings,somedevswillnametheir

catch

variables

err1

,

err2

,etc.Otherdevswillsimply

turnoffthelintingcheckforduplicatevariablenames.

Theblock-scopingnatureof

catch

mayseemlikeauselessacademicfact,butseeAppendixBformoreinformationon

justhowusefulitmightbe.

Thusfar,we'veseenthatJavaScriptonlyhassomestrangenichebehaviorswhichexposeblockscopefunctionality.Ifthat

wereallwehad,anditwasformany,manyyears,thenblockscopingwouldnotbeterriblyusefultotheJavaScript

developer.

Fortunately,ES6changesthat,andintroducesanewkeyword

let

whichsitsalongside

var

asanotherwaytodeclare

variables.

The

let

keywordattachesthevariabledeclarationtothescopeofwhateverblock(commonlya

{..}

pair)it'scontained

in.Inotherwords,

let

implicitlyhijacksanyblock'sscopeforitsvariabledeclaration.

var

foo=

true

;

if

(foo){

let

bar=foo*

2

;

bar=something(bar);

console

.log(bar);

}

console

.log(bar);

//ReferenceError

Using

let

toattachavariabletoanexistingblockissomewhatimplicit.Itcanconfuseifyou'renotpayingcloseattention

towhichblockshavevariablesscopedtothem,andareinthehabitofmovingblocksaround,wrappingtheminother

blocks,etc.,asyoudevelopandevolvecode.

Creatingexplicitblocksforblock-scopingcanaddresssomeoftheseconcerns,makingitmoreobviouswherevariablesare

attachedandnot.Usually,explicitcodeispreferableoverimplicitorsubtlecode.Thisexplicitblock-scopingstyleiseasyto

achieve,andfitsmorenaturallywithhowblock-scopingworksinotherlanguages:

var

foo=

true

;

if

(foo){

{

//<--explicitblock

let

bar=foo*

2

;

bar=something(bar);

console

.log(bar);

}

}

console

.log(bar);

//ReferenceError

Wecancreateanarbitraryblockfor

let

tobindtobysimplyincludinga

{..}

pairanywhereastatementisvalid

grammar.Inthiscase,we'vemadeanexplicitblockinsidetheif-statement,whichmaybeeasierasawholeblocktomove

aroundlaterinrefactoring,withoutaffectingthepositionandsemanticsoftheenclosingif-statement.

Note:Foranotherwaytoexpressexplicitblockscopes,seeAppendixB.

InChapter4,wewilladdresshoisting,whichtalksaboutdeclarationsbeingtakenasexistingfortheentirescopeinwhich

let

YouDon'tKnowJS:Scope&Closures

29

Chapter3:Functionvs.BlockScope

background image

theyoccur.

However,declarationsmadewith

let

willnothoisttotheentirescopeoftheblocktheyappearin.Suchdeclarationswill

notobservably"exist"intheblockuntilthedeclarationstatement.

{

console

.log(bar);

//ReferenceError!

let

bar=

2

;

}

Anotherreasonblock-scopingisusefulrelatestoclosuresandgarbagecollectiontoreclaimmemory.We'llbrieflyillustrate

here,buttheclosuremechanismisexplainedindetailinChapter5.

Consider:

function

process

(

data

)

{

//dosomethinginteresting

}

var

someReallyBigData={..};

process(someReallyBigData);

var

btn=

document

.getElementById(

"my_button"

);

btn.addEventListener(

"click"

,

function

click

(

evt

)

{

console

.log(

"buttonclicked"

);

},

/*capturingPhase=*/

false

);

The

click

functionclickhandlercallbackdoesn'tneedthe

someReallyBigData

variableatall.Thatmeans,theoretically,

after

process(..)

runs,thebigmemory-heavydatastructurecouldbegarbagecollected.However,it'squitelikely(though

implementationdependent)thattheJSenginewillstillhavetokeepthestructurearound,sincethe

click

functionhasa

closureovertheentirescope.

Block-scopingcanaddressthisconcern,makingitclearertotheenginethatitdoesnotneedtokeep

someReallyBigData

around:

function

process

(

data

)

{

//dosomethinginteresting

}

//anythingdeclaredinsidethisblockcangoawayafter!

{

let

someReallyBigData={..};

process(someReallyBigData);

}

var

btn=

document

.getElementById(

"my_button"

);

btn.addEventListener(

"click"

,

function

click

(

evt

)

{

console

.log(

"buttonclicked"

);

},

/*capturingPhase=*/

false

);

Declaringexplicitblocksforvariablestolocallybindtoisapowerfultoolthatyoucanaddtoyourcodetoolbox.

GarbageCollection

let

Loops

YouDon'tKnowJS:Scope&Closures

30

Chapter3:Functionvs.BlockScope

background image

Aparticularcasewhere

let

shinesisinthefor-loopcaseaswediscussedpreviously.

for

(

let

i=

0

;i<

10

;i++){

console

.log(i);

}

console

.log(i);

//ReferenceError

Notonlydoes

let

inthefor-loopheaderbindthe

i

tothefor-loopbody,butinfact,itre-bindsittoeachiterationofthe

loop,makingsuretore-assignitthevaluefromtheendofthepreviousloopiteration.

Here'sanotherwayofillustratingtheper-iterationbindingbehaviorthatoccurs:

{

let

j;

for

(j=

0

;j<

10

;j++){

let

i=j;

//re-boundforeachiteration!

console

.log(i);

}

}

Thereasonwhythisper-iterationbindingisinterestingwillbecomeclearinChapter5whenwediscussclosures.

Because

let

declarationsattachtoarbitraryblocksratherthantotheenclosingfunction'sscope(orglobal),therecanbe

gotchaswhereexistingcodehasahiddenrelianceonfunction-scoped

var

declarations,andreplacingthe

var

with

let

mayrequireadditionalcarewhenrefactoringcode.

Consider:

var

foo=

true

,baz=

10

;

if

(foo){

var

bar=

3

;

if

(baz>bar){

console

.log(baz);

}

//...

}

Thiscodeisfairlyeasilyre-factoredas:

var

foo=

true

,baz=

10

;

if

(foo){

var

bar=

3

;

//...

}

if

(baz>bar){

console

.log(baz);

}

But,becarefulofsuchchangeswhenusingblock-scopedvariables:

var

foo=

true

,baz=

10

;

YouDon'tKnowJS:Scope&Closures

31

Chapter3:Functionvs.BlockScope

background image

if

(foo){

let

bar=

3

;

if

(baz>bar){

//<--don'tforget`bar`whenmoving!

console

.log(baz);

}

}

SeeAppendixBforanalternate(moreexplicit)styleofblock-scopingwhichmayprovideeasiertomaintain/refactorcode

that'smorerobusttothesescenarios.

Inadditionto

let

,ES6introduces

const

,whichalsocreatesablock-scopedvariable,butwhosevalueisfixed(constant).

Anyattempttochangethatvalueatalatertimeresultsinanerror.

var

foo=

true

;

if

(foo){

var

a=

2

;

const

b=

3

;

//block-scopedtothecontaining`if`

a=

3

;

//justfine!

b=

4

;

//error!

}

console

.log(a);

//3

console

.log(b);

//ReferenceError!

FunctionsarethemostcommonunitofscopeinJavaScript.Variablesandfunctionsthataredeclaredinsideanother

functionareessentially"hidden"fromanyoftheenclosing"scopes",whichisanintentionaldesignprincipleofgood

software.

Butfunctionsarebynomeanstheonlyunitofscope.Block-scopereferstotheideathatvariablesandfunctionscan

belongtoanarbitraryblock(generally,any

{..}

pair)ofcode,ratherthanonlytotheenclosingfunction.

StartingwithES3,the

try/catch

structurehasblock-scopeinthe

catch

clause.

InES6,the

let

keyword(acousintothe

var

keyword)isintroducedtoallowdeclarationsofvariablesinanyarbitrary

blockofcode.

if(..){leta=2;}

willdeclareavariable

a

thatessentiallyhijacksthescopeofthe

if

's

{..}

block

andattachesitselfthere.

Thoughsomeseemtobelieveso,blockscopeshouldnotbetakenasanoutrightreplacementof

var

functionscope.Both

functionalitiesco-exist,anddeveloperscanandshouldusebothfunction-scopeandblock-scopetechniqueswhere

respectivelyappropriatetoproducebetter,morereadable/maintainablecode.

note-leastprivilege

.

PrincipleofLeastPrivilege

const

Review(TL;DR)

YouDon'tKnowJS:Scope&Closures

32

Chapter3:Functionvs.BlockScope

background image

Bynow,youshouldbefairlycomfortablewiththeideaofscope,andhowvariablesareattachedtodifferentlevelsofscope

dependingonwhereandhowtheyaredeclared.Bothfunctionscopeandblockscopebehavebythesamerulesinthis

regard:anyvariabledeclaredwithinascopeisattachedtothatscope.

Butthere'sasubtledetailofhowscopeattachmentworkswithdeclarationsthatappearinvariouslocationswithinascope,

andthatdetailiswhatwewillexaminehere.

There'satemptationtothinkthatallofthecodeyouseeinaJavaScriptprogramisinterpretedline-by-line,top-downin

order,astheprogramexecutes.Whilethatissubstantiallytrue,there'sonepartofthatassumptionwhichcanleadto

incorrectthinkingaboutyourprogram.

Considerthiscode:

a=

2

;

var

a;

console

.log(a);

Whatdoyouexpecttobeprintedinthe

console.log(..)

statement?

Manydeveloperswouldexpect

undefined

,sincethe

vara

statementcomesafterthe

a=2

,anditwouldseemnaturalto

assumethatthevariableisre-defined,andthusassignedthedefault

undefined

.However,theoutputwillbe

2

.

Consideranotherpieceofcode:

console

.log(a);

var

a=

2

;

Youmightbetemptedtoassumethat,sincetheprevioussnippetexhibitedsomeless-than-top-downlookingbehavior,

perhapsinthissnippet,

2

willalsobeprinted.Othersmaythinkthatsincethe

a

variableisusedbeforeitisdeclared,this

mustresultina

ReferenceError

beingthrown.

Unfortunately,bothguessesareincorrect.

undefined

istheoutput.

So,what'sgoingonhere?Itwouldappearwehaveachicken-and-the-eggquestion.Whichcomesfirst,thedeclaration

("egg"),ortheassignment("chicken")?

Toanswerthisquestion,weneedtoreferbacktoChapter1,andourdiscussionofcompilers.RecallthattheEngine

actuallywillcompileyourJavaScriptcodebeforeitinterpretsit.Partofthecompilationphasewastofindandassociateall

declarationswiththeirappropriatescopes.Chapter2showedusthatthisistheheartofLexicalScope.

So,thebestwaytothinkaboutthingsisthatalldeclarations,bothvariablesandfunctions,areprocessedfirst,beforeany

Chapter4:Hoisting

ChickenOrTheEgg?

TheCompilerStrikesAgain

YouDon'tKnowJS:Scope&Closures

33

Chapter4:Hoisting

background image

partofyourcodeisexecuted.

Whenyousee

vara=2;

,youprobablythinkofthatasonestatement.ButJavaScriptactuallythinksofitastwo

statements:

vara;

and

a=2;

.Thefirststatement,thedeclaration,isprocessedduringthecompilationphase.The

secondstatement,theassignment,isleftinplacefortheexecutionphase.

Ourfirstsnippetthenshouldbethoughtofasbeinghandledlikethis:

var

a;

a=

2

;

console

.log(a);

...wherethefirstpartisthecompilationandthesecondpartistheexecution.

Similarly,oursecondsnippetisactuallyprocessedas:

var

a;

console

.log(a);

a=

2

;

So,onewayofthinking,sortofmetaphorically,aboutthisprocess,isthatvariableandfunctiondeclarationsare"moved"

fromwheretheyappearintheflowofthecodetothetopofthecode.Thisgivesrisetothename"Hoisting".

Inotherwords,theegg(declaration)comesbeforethechicken(assignment).

Note:Onlythedeclarationsthemselvesarehoisted,whileanyassignmentsorotherexecutablelogicareleftinplace.If

hoistingweretore-arrangetheexecutablelogicofourcode,thatcouldwreakhavoc.

foo();

function

foo

()

{

console

.log(a);

//undefined

var

a=

2

;

}

Thefunction

foo

'sdeclaration(whichinthiscaseincludestheimpliedvalueofitasanactualfunction)ishoisted,suchthat

thecallonthefirstlineisabletoexecute.

It'salsoimportanttonotethathoistingisper-scope.Sowhileourprevioussnippetsweresimplifiedinthattheyonly

includedglobalscope,the

foo(..)

functionwearenowexaminingitselfexhibitsthat

vara

ishoistedtothetopof

foo(..)

(not,obviously,tothetopoftheprogram).Sotheprogramcanperhapsbemoreaccuratelyinterpretedlikethis:

function

foo

()

{

var

a;

console

.log(a);

//undefined

a=

2

;

YouDon'tKnowJS:Scope&Closures

34

Chapter4:Hoisting

background image

}

foo();

Functiondeclarationsarehoisted,aswejustsaw.Butfunctionexpressionsarenot.

foo();

//notReferenceError,butTypeError!

var

foo=

function

bar

()

{

//...

};

Thevariableidentifier

foo

ishoistedandattachedtotheenclosingscope(global)ofthisprogram,so

foo()

doesn'tfailas

a

ReferenceError

.But

foo

hasnovalueyet(asitwouldifithadbeenatruefunctiondeclarationinsteadofexpression).

So,

foo()

isattemptingtoinvokethe

undefined

value,whichisa

TypeError

illegaloperation.

Alsorecallthateventhoughit'sanamedfunctionexpression,thenameidentifierisnotavailableintheenclosingscope:

foo();

//TypeError

bar();

//ReferenceError

var

foo=

function

bar

()

{

//...

};

Thissnippetismoreaccuratelyinterpreted(withhoisting)as:

var

foo;

foo();

//TypeError

bar();

//ReferenceError

foo=

function

()

{

var

bar=...self...

//...

}

Bothfunctiondeclarationsandvariabledeclarationsarehoisted.Butasubtledetail(thatcanshowupincodewithmultiple

"duplicate"declarations)isthatfunctionsarehoistedfirst,andthenvariables.

Consider:

foo();

//1

var

foo;

function

foo

()

{

console

.log(

1

);

}

foo=

function

()

{

console

.log(

2

);

};

1

isprintedinsteadof

2

!ThissnippetisinterpretedbytheEngineas:

FunctionsFirst

YouDon'tKnowJS:Scope&Closures

35

Chapter4:Hoisting

background image

function

foo

()

{

console

.log(

1

);

}

foo();

//1

foo=

function

()

{

console

.log(

2

);

};

Noticethat

varfoo

wastheduplicate(andthusignored)declaration,eventhoughitcamebeforethe

functionfoo()...

declaration,becausefunctiondeclarationsarehoistedbeforenormalvariables.

Whilemultiple/duplicate

var

declarationsareeffectivelyignored,subsequentfunctiondeclarationsdooverrideprevious

ones.

foo();

//3

function

foo

()

{

console

.log(

1

);

}

var

foo=

function

()

{

console

.log(

2

);

};

function

foo

()

{

console

.log(

3

);

}

Whilethisallmaysoundlikenothingmorethaninterestingacademictrivia,ithighlightsthefactthatduplicatedefinitionsin

thesamescopeareareallybadideaandwilloftenleadtoconfusingresults.

Functiondeclarationsthatappearinsideofnormalblockstypicallyhoisttotheenclosingscope,ratherthanbeing

conditionalasthiscodeimplies:

foo();

//"b"

var

a=

true

;

if

(a){

function

foo

()

{

console

.log(

"a"

);}

}

else

{

function

foo

()

{

console

.log(

"b"

);}

}

However,it'simportanttonotethatthisbehaviorisnotreliableandissubjecttochangeinfutureversionsofJavaScript,so

it'sprobablybesttoavoiddeclaringfunctionsinblocks.

Wecanbetemptedtolookat

vara=2;

asonestatement,buttheJavaScriptEnginedoesnotseeitthatway.Itsees

var

a

and

a=2

astwoseparatestatements,thefirstoneacompiler-phasetask,andthesecondoneanexecution-phase

task.

Whatthisleadstoisthatalldeclarationsinascope,regardlessofwheretheyappear,areprocessedfirstbeforethecode

itselfisexecuted.Youcanvisualizethisasdeclarations(variablesandfunctions)being"moved"tothetopoftheir

respectivescopes,whichwecall"hoisting".

Review(TL;DR)

YouDon'tKnowJS:Scope&Closures

36

Chapter4:Hoisting

background image

Declarationsthemselvesarehoisted,butassignments,evenassignmentsoffunctionexpressions,arenothoisted.

Becarefulaboutduplicatedeclarations,especiallymixedbetweennormalvardeclarationsandfunctiondeclarations--peril

awaitsifyoudo!

YouDon'tKnowJS:Scope&Closures

37

Chapter4:Hoisting

background image

Wearriveatthispointwithhopefullyaveryhealthy,solidunderstandingofhowscopeworks.

Weturnourattentiontoanincrediblyimportant,butpersistentlyelusive,almostmythological,partofthelanguage:closure.

Ifyouhavefollowedourdiscussionoflexicalscopethusfar,thepayoffisthatclosureisgoingtobe,largely,anti-climatic,

almostself-obvious.There'samanbehindthewizard'scurtain,andwe'reabouttoseehim.No,hisnameisnotCrockford!

Ifhoweveryouhavenaggingquestionsaboutlexicalscope,nowwouldbeagoodtimetogobackandreviewChapter2

beforeproceeding.

ForthosewhoaresomewhatexperiencedinJavaScript,buthaveperhapsneverfullygraspedtheconceptofclosures,

understandingclosurecanseemlikeaspecialnirvanathatonemuststriveandsacrificetoattain.

IrecallyearsbackwhenIhadafirmgrasponJavaScript,buthadnoideawhatclosurewas.Thehintthattherewasthis

othersidetothelanguage,onewhichpromisedevenmorecapabilitythanIalreadypossessed,teasedandtauntedme.I

rememberreadingthroughthesourcecodeofearlyframeworkstryingtounderstandhowitactuallyworked.Iremember

thefirsttimesomethingofthe"modulepattern"begantoemergeinmymind.Irememberthea-ha!momentsquitevividly.

WhatIdidn'tknowbackthen,whattookmeyearstounderstand,andwhatIhopetoimparttoyoupresently,isthissecret:

closureisallaroundyouinJavaScript,youjusthavetorecognizeandembraceit.Closuresarenotaspecialopt-in

toolthatyoumustlearnnewsyntaxandpatternsfor.No,closuresarenotevenaweaponthatyoumustlearntowieldand

masterasLuketrainedinTheForce.

Closureshappenasaresultofwritingcodethatreliesonlexicalscope.Theyjusthappen.Youdonotevenreallyhaveto

intentionallycreateclosurestotakeadvantageofthem.Closuresarecreatedandusedforyoualloveryourcode.What

youaremissingisthepropermentalcontexttorecognize,embrace,andleverageclosuresforyourownwill.

Theenlightenmentmomentshouldbe:oh,closuresarealreadyoccurringallovermycode,Icanfinallyseethem

now.UnderstandingclosuresislikewhenNeoseestheMatrixforthefirsttime.

OK,enoughhyperboleandshamelessmoviereferences.

Here'sadown-n-dirtydefinitionofwhatyouneedtoknowtounderstandandrecognizeclosures:

Closureiswhenafunctionisabletorememberandaccessitslexicalscopeevenwhenthatfunctionisexecuting

outsideitslexicalscope.

Let'sjumpintosomecodetoillustratethatdefinition.

function

foo

()

{

var

a=

2

;

function

bar

()

{

console

.log(a);

//2

}

bar();

}

Chapter5:ScopeClosure

Enlightenment

NittyGritty

YouDon'tKnowJS:Scope&Closures

38

Chapter5:ScopeClosures

background image

foo();

ThiscodeshouldlookfamiliarfromourdiscussionsofNestedScope.Function

bar()

hasaccesstothevariable

a

inthe

outerenclosingscopebecauseoflexicalscopelook-uprules(inthiscase,it'sanRHSreferencelook-up).

Isthis"closure"?

Well,technically...perhaps.Butbyourwhat-you-need-to-knowdefinitionabove...notexactly.Ithinkthemostaccurateway

toexplain

bar()

referencing

a

isvialexicalscopelook-uprules,andthoserulesareonly(animportant!)partofwhat

closureis.

Fromapurelyacademicperspective,whatissaidoftheabovesnippetisthatthefunction

bar()

hasaclosureoverthe

scopeof

foo()

(andindeed,evenovertherestofthescopesithasaccessto,suchastheglobalscopeinourcase).Put

slightlydifferently,it'ssaidthat

bar()

closesoverthescopeof

foo()

.Why?Because

bar()

appearsnestedinsideof

foo()

.Plainandsimple.

But,closuredefinedinthiswayisnotdirectlyobservable,nordoweseeclosureexercisedinthatsnippet.Weclearlysee

lexicalscope,butclosureremainssortofamysteriousshiftingshadowbehindthecode.

Letusthenconsidercodewhichbringsclosureintofulllight:

function

foo

()

{

var

a=

2

;

function

bar

()

{

console

.log(a);

}

return

bar;

}

var

baz=foo();

baz();

//2--Whoa,closurewasjustobserved,man.

Thefunction

bar()

haslexicalscopeaccesstotheinnerscopeof

foo()

.Butthen,wetake

bar()

,thefunctionitself,and

passitasavalue.Inthiscase,we

return

thefunctionobjectitselfthat

bar

references.

Afterweexecute

foo()

,weassignthevalueitreturned(ourinner

bar()

function)toavariablecalled

baz

,andthenwe

actuallyinvoke

baz()

,whichofcourseisinvokingourinnerfunction

bar()

,justbyadifferentidentifierreference.

bar()

isexecuted,forsure.Butinthiscase,it'sexecutedoutsideofitsdeclaredlexicalscope.

After

foo()

executed,normallywewouldexpectthattheentiretyoftheinnerscopeof

foo()

wouldgoaway,becausewe

knowthattheEngineemploysaGarbageCollectorthatcomesalongandfreesupmemoryonceit'snolongerinuse.Since

itwouldappearthatthecontentsof

foo()

arenolongerinuse,itwouldseemnaturalthattheyshouldbeconsideredgone.

Butthe"magic"ofclosuresdoesnotletthishappen.Thatinnerscopeisinfactstill"inuse",andthusdoesnotgoaway.

Who'susingit?Thefunction

bar()

itself.

Byvirtueofwhereitwasdeclared,

bar()

hasalexicalscopeclosureoverthatinnerscopeof

foo()

,whichkeepsthat

scopealivefor

bar()

toreferenceatanylatertime.

bar()

stillhasareferencetothatscope,andthatreferenceiscalledclosure.

So,afewmicrosecondslater,whenthevariable

baz

isinvoked(invokingtheinnerfunctionweinitiallylabeled

bar

),itduly

hasaccesstoauthor-timelexicalscope,soitcanaccessthevariable

a

justaswe'dexpect.

YouDon'tKnowJS:Scope&Closures

39

Chapter5:ScopeClosures

background image

Thefunctionisbeinginvokedwelloutsideofitsauthor-timelexicalscope.Closureletsthefunctioncontinuetoaccessthe

lexicalscopeitwasdefinedinatauthor-time.

Ofcourse,anyofthevariouswaysthatfunctionscanbepassedaroundasvalues,andindeedinvokedinotherlocations,

areallexamplesofobserving/exercisingclosure.

function

foo

()

{

var

a=

2

;

function

baz

()

{

console

.log(a);

//2

}

bar(baz);

}

function

bar

(

fn

)

{

fn();

//lookma,Isawclosure!

}

Wepasstheinnerfunction

baz

overto

bar

,andcallthatinnerfunction(labeled

fn

now),andwhenwedo,itsclosure

overtheinnerscopeof

foo()

isobserved,byaccessing

a

.

Thesepassings-aroundoffunctionscanbeindirect,too.

var

fn;

function

foo

()

{

var

a=

2

;

function

baz

()

{

console

.log(a);

}

fn=baz;

//assign`baz`toglobalvariable

}

function

bar

()

{

fn();

//lookma,Isawclosure!

}

foo();

bar();

//2

Whateverfacilityweusetotransportaninnerfunctionoutsideofitslexicalscope,itwillmaintainascopereferenceto

whereitwasoriginallydeclared,andwhereverweexecuteit,thatclosurewillbeexercised.

Thepreviouscodesnippetsaresomewhatacademicandartificiallyconstructedtoillustrateusingclosure.ButIpromised

yousomethingmorethanjustacoolnewtoy.Ipromisedthatclosurewassomethingallaroundyouinyourexistingcode.

Letusnowseethattruth.

function

wait

(

message

)

{

setTimeout(

function

timer

()

{

console

.log(message);

},

1000

);

}

NowICanSee

YouDon'tKnowJS:Scope&Closures

40

Chapter5:ScopeClosures

background image

wait(

"Hello,closure!"

);

Wetakeaninnerfunction(named

timer

)andpassitto

setTimeout(..)

.But

timer

hasascopeclosureoverthescopeof

wait(..)

,indeedkeepingandusingareferencetothevariable

message

.

Athousandmillisecondsafterwehaveexecuted

wait(..)

,anditsinnerscopeshouldotherwisebelonggone,that

anonymousfunctionstillhasclosureoverthatscope.

DeepdowninthegutsoftheEngine,thebuilt-inutility

setTimeout(..)

hasreferencetosomeparameter,probablycalled

fn

or

func

orsomethinglikethat.Enginegoestoinvokethatfunction,whichisinvokingourinner

timer

function,andthe

lexicalscopereferenceisstillintact.

Closure.

Or,ifyou'reofthejQuerypersuasion(oranyJSframework,forthatmatter):

function

setupBot

(

name,selector

)

{

$(selector).click(

function

activator

()

{

console

.log(

"Activating:"

+name);

});

}

setupBot(

"ClosureBot1"

,

"#bot_1"

);

setupBot(

"ClosureBot2"

,

"#bot_2"

);

Iamnotsurewhatkindofcodeyouwrite,butIregularlywritecodewhichisresponsibleforcontrollinganentireglobal

dronearmyofclosurebots,sothisistotallyrealistic!

(Some)jokingaside,essentiallywheneverandwhereveryoutreatfunctions(whichaccesstheirownrespectivelexical

scopes)asfirst-classvaluesandpassthemaround,youarelikelytoseethosefunctionsexercisingclosure.Bethattimers,

eventhandlers,Ajaxrequests,cross-windowmessaging,webworkers,oranyoftheotherasynchronous(orsynchronous!)

tasks,whenyoupassinacallbackfunction,getreadytoslingsomeclosurearound!

Note:Chapter3introducedtheIIFEpattern.WhileitisoftensaidthatIIFE(alone)isanexampleofobservedclosure,I

wouldsomewhatdisagree,byourdefinitionabove.

var

a=

2

;

(

function

IIFE

()

{

console

.log(a);

})();

Thiscode"works",butit'snotstrictlyanobservationofclosure.Why?Becausethefunction(whichwenamed"IIFE"here)

isnotexecutedoutsideitslexicalscope.It'sstillinvokedrightthereinthesamescopeasitwasdeclared(the

enclosing/globalscopethatalsoholds

a

).

a

isfoundvianormallexicalscopelook-up,notreallyviaclosure.

Whileclosuremighttechnicallybehappeningatdeclarationtime,itisnotstrictlyobservable,andso,astheysay,it'satree

fallingintheforestwithnoonearoundtohearit.

ThoughanIIFEisnotitselfanexampleofclosure,itabsolutelycreatesscope,andit'soneofthemostcommontoolswe

usetocreatescopewhichcanbeclosedover.SoIIFEsareindeedheavilyrelatedtoclosure,evenifnotexercisingclosure

themselves.

Putthisbookdownrightnow,dearreader.Ihaveataskforyou.GoopenupsomeofyourrecentJavaScriptcode.Lookfor

yourfunctions-as-valuesandidentifywhereyouarealreadyusingclosureandmaybedidn'tevenknowitbefore.

YouDon'tKnowJS:Scope&Closures

41

Chapter5:ScopeClosures

background image

I'llwait.

Now...yousee!

Themostcommoncanonicalexampleusedtoillustrateclosureinvolvesthehumblefor-loop.

for

(

var

i=

1

;i<=

5

;i++){

setTimeout(

function

timer

()

{

console

.log(i);

},i*

1000

);

}

Note:Lintersoftencomplainwhenyouputfunctionsinsideofloops,becausethemistakesofnotunderstandingclosure

aresocommonamongdevelopers.Weexplainhowtodosoproperlyhere,leveragingthefullpowerofclosure.Butthat

subtletyisoftenlostonlintersandtheywillcomplainregardless,assumingyoudon'tactuallyknowwhatyou'redoing.

Thespiritofthiscodesnippetisthatwewouldnormallyexpectforthebehaviortobethatthenumbers"1","2",.."5"would

beprintedout,oneatatime,onepersecond,respectively.

Infact,ifyourunthiscode,youget"6"printedout5times,attheone-secondintervals.

Huh?

Firstly,let'sexplainwhere

6

comesfrom.Theterminatingconditionoftheloopiswhen

i

isnot

<=5

.Thefirsttimethat's

thecaseiswhen

i

is6.So,theoutputisreflectingthefinalvalueofthe

i

aftertheloopterminates.

Thisactuallyseemsobviousonsecondglance.Thetimeoutfunctioncallbacksareallrunningwellafterthecompletionof

theloop.Infact,astimersgo,evenifitwas

setTimeout(..,0)

oneachiteration,allthosefunctioncallbackswouldstillrun

strictlyafterthecompletionoftheloop,andthusprint

6

eachtime.

Butthere'sadeeperquestionatplayhere.What'smissingfromourcodetoactuallyhaveitbehaveaswesemantically

haveimplied?

What'smissingisthatwearetryingtoimplythateachiterationoftheloop"captures"itsowncopyof

i

,atthetimeofthe

iteration.But,thewayscopeworks,all5ofthosefunctions,thoughtheyaredefinedseparatelyineachloopiteration,all

areclosedoverthesamesharedglobalscope,whichhas,infact,onlyone

i

init.

Putthatway,ofcourseallfunctionsshareareferencetothesame

i

.Somethingabouttheloopstructuretendstoconfuse

usintothinkingthere'ssomethingelsemoresophisticatedatwork.Thereisnot.There'snodifferencethanifeachofthe5

timeoutcallbackswerejustdeclaredonerightaftertheother,withnoloopatall.

OK,so,backtoourburningquestion.What'smissing?Weneedmorecowbellclosuredscope.Specifically,weneedanew

closuredscopeforeachiterationoftheloop.

WelearnedinChapter3thattheIIFEcreatesscopebydeclaringafunctionandimmediatelyexecutingit.

Let'stry:

for

(

var

i=

1

;i<=

5

;i++){

(

function

()

{

setTimeout(

function

timer

()

{

console

.log(i);

},i*

1000

);

Loops+Closure

YouDon'tKnowJS:Scope&Closures

42

Chapter5:ScopeClosures

background image

})();

}

Doesthatwork?Tryit.Again,I'llwait.

I'llendthesuspenseforyou.Nope.Butwhy?Wenowobviouslyhavemorelexicalscope.Eachtimeoutfunctioncallbackis

indeedclosingoveritsownper-iterationscopecreatedrespectivelybyeachIIFE.

It'snotenoughtohaveascopetocloseoverifthatscopeisempty.Lookclosely.OurIIFEisjustanemptydo-nothing

scope.Itneedssomethinginittobeusefultous.

Itneedsitsownvariable,withacopyofthe

i

valueateachiteration.

for

(

var

i=

1

;i<=

5

;i++){

(

function

()

{

var

j=i;

setTimeout(

function

timer

()

{

console

.log(j);

},j*

1000

);

})();

}

Eureka!Itworks!

Aslightvariationsomepreferis:

for

(

var

i=

1

;i<=

5

;i++){

(

function

(

j

)

{

setTimeout(

function

timer

()

{

console

.log(j);

},j*

1000

);

})(i);

}

Ofcourse,sincetheseIIFEsarejustfunctions,wecanpassin

i

,andwecancallit

j

ifweprefer,orwecanevencallit

i

again.Eitherway,thecodeworksnow.

TheuseofanIIFEinsideeachiterationcreatedanewscopeforeachiteration,whichgaveourtimeoutfunctioncallbacks

theopportunitytocloseoveranewscopeforeachiteration,onewhichhadavariablewiththerightper-iterationvalueinit

forustoaccess.

Problemsolved!

Lookcarefullyatouranalysisoftheprevioussolution.WeusedanIIFEtocreatenewscopeper-iteration.Inotherwords,

weactuallyneededaper-iterationblockscope.Chapter3showedusthe

let

declaration,whichhijacksablockand

declaresavariablerightthereintheblock.

Itessentiallyturnsablockintoascopethatwecancloseover.So,thefollowingawesomecode"justworks":

for

(

var

i=

1

;i<=

5

;i++){

let

j=i;

//yay,block-scopeforclosure!

setTimeout(

function

timer

()

{

console

.log(j);

},j*

1000

);

}

BlockScopingRevisited

YouDon'tKnowJS:Scope&Closures

43

Chapter5:ScopeClosures

background image

But,that'snotall!(inmybestBobBarkervoice).There'saspecialbehaviordefinedfor

let

declarationsusedinthehead

ofafor-loop.Thisbehaviorsaysthatthevariablewillbedeclarednotjustoncefortheloop,buteachiteration.And,itwill,

helpfully,beinitializedateachsubsequentiterationwiththevaluefromtheendofthepreviousiteration.

for

(

let

i=

1

;i<=

5

;i++){

setTimeout(

function

timer

()

{

console

.log(i);

},i*

1000

);

}

Howcoolisthat?Blockscopingandclosureworkinghand-in-hand,solvingalltheworld'sproblems.Idon'tknowaboutyou,

butthatmakesmeahappyJavaScripter.

Thereareothercodepatternswhichleveragethepowerofclosurebutwhichdonotonthesurfaceappeartobeabout

callbacks.Let'sexaminethemostpowerfulofthem:themodule.

function

foo

()

{

var

something=

"cool"

;

var

another=[

1

,

2

,

3

];

function

doSomething

()

{

console

.log(something);

}

function

doAnother

()

{

console

.log(another.join(

"!"

));

}

}

Asthiscodestandsrightnow,there'snoobservableclosuregoingon.Wesimplyhavesomeprivatedatavariables

something

and

another

,andacoupleofinnerfunctions

doSomething()

and

doAnother()

,whichbothhavelexicalscope

(andthusclosure!)overtheinnerscopeof

foo()

.

Butnowconsider:

function

CoolModule

()

{

var

something=

"cool"

;

var

another=[

1

,

2

,

3

];

function

doSomething

()

{

console

.log(something);

}

function

doAnother

()

{

console

.log(another.join(

"!"

));

}

return

{

doSomething:doSomething,

doAnother:doAnother

};

}

var

foo=CoolModule();

foo.doSomething();

//cool

foo.doAnother();

//1!2!3

Modules

YouDon'tKnowJS:Scope&Closures

44

Chapter5:ScopeClosures

background image

ThisisthepatterninJavaScriptwecallmodule.Themostcommonwayofimplementingthemodulepatternisoftencalled

"RevealingModule",andit'sthevariationwepresenthere.

Let'sexaminesomethingsaboutthiscode.

Firstly,

CoolModule()

isjustafunction,butithastobeinvokedfortheretobeamoduleinstancecreated.Withoutthe

executionoftheouterfunction,thecreationoftheinnerscopeandtheclosureswouldnotoccur.

Secondly,the

CoolModule()

functionreturnsanobject,denotedbytheobject-literalsyntax

{key:value,...}

.Theobject

wereturnhasreferencesonittoourinnerfunctions,butnottoourinnerdatavariables.Wekeepthosehiddenandprivate.

It'sappropriatetothinkofthisobjectreturnvalueasessentiallyapublicAPIforourmodule.

Thisobjectreturnvalueisultimatelyassignedtotheoutervariable

foo

,andthenwecanaccessthosepropertymethods

ontheAPI,like

foo.doSomething()

.

Note:Itisnotrequiredthatwereturnanactualobject(literal)fromourmodule.Wecouldjustreturnbackaninnerfunction

directly.jQueryisactuallyagoodexampleofthis.The

jQuery

and

$

identifiersarethepublicAPIforthejQuery"module",

buttheyare,themselves,justafunction(whichcanitselfhaveproperties,sinceallfunctionsareobjects).

The

doSomething()

and

doAnother()

functionshaveclosureovertheinnerscopeofthemodule"instance"(arrivedatby

actuallyinvoking

CoolModule()

).Whenwetransportthosefunctionsoutsideofthelexicalscope,bywayofproperty

referencesontheobjectwereturn,wehavenowsetupaconditionbywhichclosurecanbeobservedandexercised.

Tostateitmoresimply,therearetwo"requirements"forthemodulepatterntobeexercised:

1. Theremustbeanouterenclosingfunction,anditmustbeinvokedatleastonce(eachtimecreatesanewmodule

instance).

2. Theenclosingfunctionmustreturnbackatleastoneinnerfunction,sothatthisinnerfunctionhasclosureoverthe

privatescope,andcanaccessand/ormodifythatprivatestate.

Anobjectwithafunctionpropertyonitaloneisnotreallyamodule.Anobjectwhichisreturnedfromafunctioninvocation

whichonlyhasdatapropertiesonitandnoclosuredfunctionsisnotreallyamodule,intheobservablesense.

Thecodesnippetaboveshowsastandalonemodulecreatorcalled

CoolModule()

whichcanbeinvokedanynumberof

times,eachtimecreatinganewmoduleinstance.Aslightvariationonthispatterniswhenyouonlycaretohaveone

instance,a"singleton"ofsorts:

var

foo=(

function

CoolModule

()

{

var

something=

"cool"

;

var

another=[

1

,

2

,

3

];

function

doSomething

()

{

console

.log(something);

}

function

doAnother

()

{

console

.log(another.join(

"!"

));

}

return

{

doSomething:doSomething,

doAnother:doAnother

};

})();

foo.doSomething();

//cool

foo.doAnother();

//1!2!3

Here,weturnedourmodulefunctionintoanIIFE(seeChapter3),andweimmediatelyinvokeditandassigneditsreturn

YouDon'tKnowJS:Scope&Closures

45

Chapter5:ScopeClosures

background image

valuedirectlytooursinglemoduleinstanceidentifier

foo

.

Modulesarejustfunctions,sotheycanreceiveparameters:

function

CoolModule

(

id

)

{

function

identify

()

{

console

.log(id);

}

return

{

identify:identify

};

}

var

foo1=CoolModule(

"foo1"

);

var

foo2=CoolModule(

"foo2"

);

foo1.identify();

//"foo1"

foo2.identify();

//"foo2"

AnotherslightbutpowerfulvariationonthemodulepatternistonametheobjectyouarereturningasyourpublicAPI:

var

foo=(

function

CoolModule

(

id

)

{

function

change

()

{

//modifyingthepublicAPI

publicAPI.identify=identify2;

}

function

identify1

()

{

console

.log(id);

}

function

identify2

()

{

console

.log(id.toUpperCase());

}

var

publicAPI={

change:change,

identify:identify1

};

return

publicAPI;

})(

"foomodule"

);

foo.identify();

//foomodule

foo.change();

foo.identify();

//FOOMODULE

ByretaininganinnerreferencetothepublicAPIobjectinsideyourmoduleinstance,youcanmodifythatmoduleinstance

fromtheinside,includingaddingandremovingmethods,properties,andchangingtheirvalues.

Variousmoduledependencyloaders/managersessentiallywrapupthispatternofmoduledefinitionintoafriendlyAPI.

Ratherthanexamineanyoneparticularlibrary,letmepresentaverysimpleproofofconceptforillustrationpurposes

(only):

var

MyModules=(

function

Manager

()

{

var

modules={};

function

define

(

name,deps,impl

)

{

for

(

var

i=

0

;i<deps.length;i++){

deps[i]=modules[deps[i]];

}

modules[name]=impl.apply(impl,deps);

ModernModules

YouDon'tKnowJS:Scope&Closures

46

Chapter5:ScopeClosures

background image

}

function

get

(

name

)

{

return

modules[name];

}

return

{

define:define,

get:get

};

})();

Thekeypartofthiscodeis

modules[name]=impl.apply(impl,deps)

.Thisisinvokingthedefinitionwrapperfunctionfora

module(passinginanydependencies),andstoringthereturnvalue,themodule'sAPI,intoaninternallistofmodules

trackedbyname.

Andhere'showImightuseittodefinesomemodules:

MyModules.define(

"bar"

,[],

function

()

{

function

hello

(

who

)

{

return

"Letmeintroduce:"

+who;

}

return

{

hello:hello

};

});

MyModules.define(

"foo"

,[

"bar"

],

function

(

bar

)

{

var

hungry=

"hippo"

;

function

awesome

()

{

console

.log(bar.hello(hungry).toUpperCase());

}

return

{

awesome:awesome

};

});

var

bar=MyModules.get(

"bar"

);

var

foo=MyModules.get(

"foo"

);

console

.log(

bar.hello(

"hippo"

)

);

//Letmeintroduce:hippo

foo.awesome();

//LETMEINTRODUCE:HIPPO

Boththe"foo"and"bar"modulesaredefinedwithafunctionthatreturnsapublicAPI."foo"evenreceivestheinstanceof

"bar"asadependencyparameter,andcanuseitaccordingly.

Spendsometimeexaminingthesecodesnippetstofullyunderstandthepowerofclosuresputtouseforourowngood

purposes.Thekeytake-awayisthatthere'snotreallyanyparticular"magic"tomodulemanagers.Theyfulfillboth

characteristicsofthemodulepatternIlistedabove:invokingafunctiondefinitionwrapper,andkeepingitsreturnvalueas

theAPIforthatmodule.

Inotherwords,modulesarejustmodules,evenifyouputafriendlywrappertoolontopofthem.

ES6addsfirst-classsyntaxsupportfortheconceptofmodules.Whenloadedviathemodulesystem,ES6treatsafileasa

separatemodule.EachmodulecanbothimportothermodulesorspecificAPImembers,aswellexporttheirownpublicAPI

members.

FutureModules

YouDon'tKnowJS:Scope&Closures

47

Chapter5:ScopeClosures

background image

Note:Function-basedmodulesaren'tastaticallyrecognizedpattern(somethingthecompilerknowsabout),sotheirAPI

semanticsaren'tconsidereduntilrun-time.Thatis,youcanactuallymodifyamodule'sAPIduringtherun-time(seeearlier

publicAPI

discussion).

Bycontrast,ES6ModuleAPIsarestatic(theAPIsdon'tchangeatrun-time).Sincethecompilerknowsthat,itcan(and

does!)checkduring(fileloadingand)compilationthatareferencetoamemberofanimportedmodule'sAPIactuallyexists.

IftheAPIreferencedoesn'texist,thecompilerthrowsan"early"erroratcompile-time,ratherthanwaitingfortraditional

dynamicrun-timeresolution(anderrors,ifany).

ES6modulesdonothavean"inline"format,theymustbedefinedinseparatefiles(onepermodule).The

browsers/engineshaveadefault"moduleloader"(whichisoverridable,butthat'swell-beyondourdiscussionhere)which

synchronouslyloadsamodulefilewhenit'simported.

Consider:

bar.js

function

hello

(

who

)

{

return

"Letmeintroduce:"

+who;

}

export

hello;

foo.js

//importonly`hello()`fromthe"bar"module

import

hello

from

"bar"

;

var

hungry=

"hippo"

;

function

awesome

()

{

console

.log(

hello(hungry).toUpperCase()

);

}

export

awesome;

//importtheentire"foo"and"bar"modules

module

foofrom

"foo"

;

module

barfrom

"bar"

;

console

.log(

bar.hello(

"rhino"

)

);

//Letmeintroduce:rhino

foo.awesome();

//LETMEINTRODUCE:HIPPO

Note:Separatefiles"foo.js"and"bar.js"wouldneedtobecreated,withthecontentsasshowninthefirsttwosnippets,

respectively.Then,yourprogramwouldload/importthosemodulestousethem,asshowninthethirdsnippet.

import

importsoneormoremembersfromamodule'sAPIintothecurrentscope,eachtoaboundvariable(

hello

inour

case).

module

importsanentiremoduleAPItoaboundvariable(

foo

,

bar

inourcase).

export

exportsanidentifier

(variable,function)tothepublicAPIforthecurrentmodule.Theseoperatorscanbeusedasmanytimesinamodule's

definitionasisnecessary.

Thecontentsinsidethemodulefilearetreatedasifenclosedinascopeclosure,justlikewiththefunction-closuremodules

seenearlier.

YouDon'tKnowJS:Scope&Closures

48

Chapter5:ScopeClosures

background image

Closureseemstotheun-enlightenedlikeamysticalworldsetapartinsideofJavaScriptwhichonlythefewbravestsouls

canreach.Butit'sactuallyjustastandardandalmostobviousfactofhowwewritecodeinalexicallyscopedenvironment,

wherefunctionsarevaluesandcanbepassedaroundatwill.

Closureiswhenafunctioncanrememberandaccessitslexicalscopeevenwhenit'sinvokedoutsideitslexical

scope.

Closurescantripusup,forinstancewithloops,ifwe'renotcarefultorecognizethemandhowtheywork.Buttheyarealso

animmenselypowerfultool,enablingpatternslikemodulesintheirvariousforms.

Modulesrequiretwokeycharacteristics:1)anouterwrappingfunctionbeinginvoked,tocreatetheenclosingscope2)the

returnvalueofthewrappingfunctionmustincludereferencetoatleastoneinnerfunctionthatthenhasclosureoverthe

privateinnerscopeofthewrapper.

Nowwecanseeclosuresallaroundourexistingcode,andwehavetheabilitytorecognizeandleveragethemtoourown

benefit!

Review(TL;DR)

YouDon'tKnowJS:Scope&Closures

49

Chapter5:ScopeClosures

background image

InChapter2,wetalkedabout"DynamicScope"asacontrasttothe"LexicalScope"model,whichishowscopeworksin

JavaScript(andinfact,mostotherlanguages).

Wewillbrieflyexaminedynamicscope,tohammerhomethecontrast.But,moreimportantly,dynamicscopeactuallyisa

nearcousintoanothermechanism(

this

)inJavaScript,whichwecoveredinthe"this&ObjectPrototypes"titleofthis

bookseries.

AswesawinChapter2,lexicalscopeisthesetofrulesabouthowtheEnginecanlook-upavariableandwhereitwillfind

it.Thekeycharacteristicoflexicalscopeisthatitisdefinedatauthor-time,whenthecodeiswritten(assumingyoudon't

cheatwith

eval()

or

with

).

Dynamicscopeseemstoimply,andforgoodreason,thatthere'samodelwherebyscopecanbedetermineddynamically

atruntime,ratherthanstaticallyatauthor-time.Thatisinfactthecase.Let'sillustrateviacode:

function

foo

()

{

console

.log(a);

//2

}

function

bar

()

{

var

a=

3

;

foo();

}

var

a=

2

;

bar();

LexicalscopeholdsthattheRHSreferenceto

a

in

foo()

willberesolvedtotheglobalvariable

a

,whichwillresultin

value

2

beingoutput.

Dynamicscope,bycontrast,doesn'tconcernitselfwithhowandwherefunctionsandscopesaredeclared,butrather

wheretheyarecalledfrom.Inotherwords,thescopechainisbasedonthecall-stack,notthenestingofscopesincode.

So,ifJavaScripthaddynamicscope,when

foo()

isexecuted,theoreticallythecodebelowwouldinsteadresultin

3

as

theoutput.

function

foo

()

{

console

.log(a);

//3(not2!)

}

function

bar

()

{

var

a=

3

;

foo();

}

var

a=

2

;

bar();

Howcanthisbe?Becausewhen

foo()

cannotresolvethevariablereferencefor

a

,insteadofsteppingupthenested

(lexical)scopechain,itwalksupthecall-stack,tofindwhere

foo()

wascalledfrom.Since

foo()

wascalledfrom

bar()

,it

checksthevariablesinscopefor

bar()

,andfindsan

a

therewithvalue

3

.

Strange?You'reprobablythinkingso,atthemoment.

AppendixA:DynamicScope

YouDon'tKnowJS:Scope&Closures

50

AppendixA:DynamicScope

background image

Butthat'sjustbecauseyou'veprobablyonlyeverworkedon(oratleastdeeplyconsidered)codewhichislexicallyscoped.

Sodynamicscopingseemsforeign.Ifyouhadonlyeverwrittencodeinadynamicallyscopedlanguage,itwouldseem

natural,andlexicalscopewouldbetheodd-ball.

Tobeclear,JavaScriptdoesnot,infact,havedynamicscope.Ithaslexicalscope.Plainandsimple.Butthe

this

mechanismiskindoflikedynamicscope.

Thekeycontrast:lexicalscopeiswrite-time,whereasdynamicscope(and

this

!)areruntime.Lexicalscopecares

whereafunctionwasdeclared,butdynamicscopecareswhereafunctionwascalledfrom.

Finally:

this

careshowafunctionwascalled,whichshowshowcloselyrelatedthe

this

mechanismistotheideaof

dynamicscoping.Todigmoreinto

this

,readthetitle"this&ObjectPrototypes".

YouDon'tKnowJS:Scope&Closures

51

AppendixA:DynamicScope

background image

InChapter3,weexploredBlockScope.Wesawthat

with

andthe

catch

clausearebothtinyexamplesofblockscope

thathaveexistedinJavaScriptsinceatleasttheintroductionofES3.

Butit'sES6'sintroductionof

let

thatfinallygivesfull,unfetteredblock-scopingcapabilitytoourcode.Therearemany

excitingthings,bothfunctionallyandcode-stylistically,thatblockscopewillenable.

Butwhatifwewantedtouseblockscopeinpre-ES6environments?

Considerthiscode:

{

let

a=

2

;

console

.log(a);

//2

}

console

.log(a);

//ReferenceError

ThiswillworkgreatinES6environments.Butcanwedosopre-ES6?

catch

istheanswer.

try

{

throw

2

}

catch

(a){

console

.log(a);

//2

}

console

.log(a);

//ReferenceError

Whoa!That'ssomeugly,weirdlookingcode.Weseea

try/catch

thatappearstoforciblythrowanerror,butthe"error"it

throwsisjustavalue

2

,andthenthevariabledeclarationthatreceivesitisinthe

catch(a)

clause.Mind:blown.

That'sright,the

catch

clausehasblock-scopingtoit,whichmeansitcanbeusedasapolyfillforblockscopeinpre-ES6

environments.

"But...",yousay."...noonewantstowriteuglycodelikethat!"That'strue.Noonewrites(someof)thecodeoutputbythe

CoffeeScriptcompiler,either.That'snotthepoint.

ThepointisthattoolscantranspileES6codetoworkinpre-ES6environments.Youcanwritecodeusingblock-scoping,

andbenefitfromsuchfunctionality,andletabuild-steptooltakecareofproducingcodethatwillactuallyworkwhen

deployed.

Thisisactuallythepreferredmigrationpathforall(ahem,most)ofES6:touseacodetranspilertotakeES6codeand

produceES5-compatiblecodeduringthetransitionfrompre-ES6toES6.

Googlemaintainsaprojectcalled"Traceur"

note-traceur

,whichisexactlytaskedwithtranspilingES6featuresintopre-ES6

(mostlyES5,butnotall!)forgeneralusage.TheTC39committeereliesonthistool(andothers)totestoutthesemanticsof

thefeaturestheyspecify.

WhatdoesTraceurproducefromoursnippet?Youguessedit!

{

AppendixB:PolyfillingBlockScope

Traceur

YouDon'tKnowJS:Scope&Closures

52

AppendixB:PolyfillingBlockScope

background image

try

{

throw

undefined

;

}

catch

(a){

a=

2

;

console

.log(a);

}

}

console

.log(a);

So,withtheuseofsuchtools,wecanstarttakingadvantageofblockscoperegardlessofifwearetargetingES6ornot,

because

try/catch

hasbeenaround(andworkedthisway)fromES3days.

InChapter3,weidentifiedsomepotentialpitfallstocodemaintainability/refactorabilitywhenweintroduceblock-scoping.Is

thereanotherwaytotakeadvantageofblockscopebuttoreducethisdownside?

Considerthisalternateformof

let

,calledthe"letblock"or"letstatement"(contrastedwith"letdeclarations"frombefore).

let

(a=

2

){

console

.log(a);

//2

}

console

.log(a);

//ReferenceError

Insteadofimplicitlyhijackinganexistingblock,thelet-statementcreatesanexplicitblockforitsscopebinding.Notonly

doestheexplicitblockstandoutmore,andperhapsfaremorerobustlyincoderefactoring,itproducessomewhatcleaner

codeby,grammatically,forcingallthedeclarationstothetopoftheblock.Thismakesiteasiertolookatanyblockand

knowwhat'sscopedtoitandnot.

Asapattern,itmirrorstheapproachmanypeopletakeinfunction-scopingwhentheymanuallymove/hoistalltheir

var

declarationstothetopofthefunction.Thelet-statementputsthemthereatthetopoftheblockbyintent,andifyoudon't

use

let

declarationsstrewnthroughout,yourblock-scopingdeclarationsaresomewhateasiertoidentifyandmaintain.

But,there'saproblem.Thelet-statementformisnotincludedinES6.NeitherdoestheofficialTraceurcompileracceptthat

formofcode.

Wehavetwooptions.WecanformatusingES6-validsyntaxandalittlesprinkleofcodediscipline:

/*let*/

{

let

a=

2

;

console

.log(a);

}

console

.log(a);

//ReferenceError

But,toolsaremeanttosolveourproblems.Sotheotheroptionistowriteexplicitletstatementblocks,andletatoolconvert

themtovalid,workingcode.

So,Ibuiltatoolcalled"let-er"

note-let_er

toaddressjustthisissue.let-erisabuild-stepcodetranspiler,butitsonlytaskisto

findlet-statementformsandtranspilethem.Itwillleavealoneanyoftherestofyourcode,includinganylet-declarations.

Youcansafelyuselet-erasthefirstES6transpilerstep,andthenpassyourcodethroughsomethinglikeTraceurif

necessary.

Moreover,let-erhasaconfigurationflag

--es6

,whichwhenturnedon(offbydefault),changesthekindofcodeproduced.

Insteadofthe

try/catch

ES3polyfillhack,let-erwouldtakeoursnippetandproducethefullyES6-compliant,non-hacky:

Implicitvs.ExplicitBlocks

YouDon'tKnowJS:Scope&Closures

53

AppendixB:PolyfillingBlockScope

background image

{

let

a=

2

;

console

.log(a);

}

console

.log(a);

//ReferenceError

So,youcanstartusinglet-errightaway,andtargetallpre-ES6environments,andwhenyouonlycareaboutES6,youcan

addtheflagandinstantlytargetonlyES6.

Andmostimportantly,youcanusethemorepreferableandmoreexplicitlet-statementformeventhoughitisnotan

officialpartofanyESversion(yet).

Letmeaddonelastquicknoteontheperformanceof

try/catch

,and/ortoaddressthequestion,"whynotjustuseanIIFE

tocreatethescope?"

Firstly,theperformanceof

try/catch

isslower,butthere'snoreasonableassumptionthatithastobethatway,oreven

thatitalwayswillbethatway.SincetheofficialTC39-approvedES6transpileruses

try/catch

,theTraceurteamhas

askedChrometoimprovetheperformanceof

try/catch

,andtheyareobviouslymotivatedtodoso.

Secondly,IIFEisnotafairapples-to-applescomparisonwith

try/catch

,becauseafunctionwrappedaroundanyarbitrary

codechangesthemeaning,insideofthatcode,of

this

,

return

,

break

,and

continue

.IIFEisnotasuitablegeneral

substitute.Itcouldonlybeusedmanuallyincertaincases.

Thequestionreallybecomes:doyouwantblock-scoping,ornot.Ifyoudo,thesetoolsprovideyouthatoption.Ifnot,keep

using

var

andgoonaboutyourcoding!

note-traceur

.

GoogleTraceur

note-let_er

.

let-er

Performance

YouDon'tKnowJS:Scope&Closures

54

AppendixB:PolyfillingBlockScope

background image

Thoughthistitledoesnotaddressthe

this

mechanisminanydetail,there'soneES6topicwhichrelates

this

tolexical

scopeinanimportantway,whichwewillquicklyexamine.

ES6addsaspecialsyntacticformoffunctiondeclarationcalledthe"arrowfunction".Itlookslikethis:

var

foo=a=>{

console

.log(a);

};

foo(

2

);

//2

Theso-called"fatarrow"isoftenmentionedasashort-handforthetediouslyverbose(sarcasm)

function

keyword.

Butthere'ssomethingmuchmoreimportantgoingonwitharrow-functionsthathasnothingtodowithsavingkeystrokesin

yourdeclaration.

Briefly,thiscodesuffersaproblem:

var

obj={

id:

"awesome"

,

cool:

function

coolFn

()

{

console

.log(

this

.id);

}

};

var

id=

"notawesome"

;

obj.cool();

//awesome

setTimeout(obj.cool,

100

);

//notawesome

Theproblemisthelossof

this

bindingonthe

cool()

function.Therearevariouswaystoaddressthatproblem,butone

often-repeatedsolutionis

varself=this;

.

Thatmightlooklike:

var

obj={

count:

0

,

cool:

function

coolFn

()

{

var

self=

this

;

if

(self.count<

1

){

setTimeout(

function

timer

()

{

self.count++;

console

.log(

"awesome?"

);

},

100

);

}

}

};

obj.cool();

//awesome?

Withoutgettingtoomuchintotheweedshere,the

varself=this

"solution"justends-aroundthewholeproblemof

understandingandproperlyusing

this

binding,andinsteadfallsbacktosomethingwe'reperhapsmorecomfortablewith:

lexicalscope.

self

becomesjustanidentifierthatcanberesolvedvialexicalscopeandclosure,andcaresnotwhat

AppendixC:Lexical-this

YouDon'tKnowJS:Scope&Closures

55

AppendixC:Lexical-this

background image

happenedtothe

this

bindingalongtheway.

Peopledon'tlikewritingverbosestuff,especiallywhentheydoitoverandoveragain.So,amotivationofES6istohelp

alleviatethesescenarios,andindeed,fixcommonidiomproblems,suchasthisone.

TheES6solution,thearrow-function,introducesabehaviorcalled"lexicalthis".

var

obj={

count:

0

,

cool:

function

coolFn

()

{

if

(

this

.count<

1

){

setTimeout(()=>{

//arrow-functionftw?

this

.count++;

console

.log(

"awesome?"

);

},

100

);

}

}

};

obj.cool();

//awesome?

Theshortexplanationisthatarrow-functionsdonotbehaveatalllikenormalfunctionswhenitcomestotheir

this

binding.

Theydiscardallthenormalrulesfor

this

binding,andinsteadtakeonthe

this

valueoftheirimmediatelexicalenclosing

scope,whateveritis.

So,inthatsnippet,thearrow-functiondoesn'tgetits

this

unboundinsomeunpredictableway,itjust"inherits"the

this

bindingofthe

cool()

function(whichiscorrectifweinvokeitasshown!).

Whilethismakesforshortercode,myperspectiveisthatarrow-functionsarereallyjustcodifyingintothelanguagesyntaxa

commonmistakeofdevelopers,whichistoconfuseandconflate"thisbinding"ruleswith"lexicalscope"rules.

Putanotherway:whygotothetroubleandverbosityofusingthe

this

stylecodingparadigm,onlytocutitoffattheknees

bymixingitwithlexicalreferences.Itseemsnaturaltoembraceoneapproachortheotherforanygivenpieceofcode,and

notmixtheminthesamepieceofcode.

Note:oneotherdetractionfromarrow-functionsisthattheyareanonymous,notnamed.SeeChapter3forthereasons

whyanonymousfunctionsarelessdesirablethannamedfunctions.

Amoreappropriateapproach,inmyperspective,tothis"problem",istouseandembracethe

this

mechanismcorrectly.

var

obj={

count:

0

,

cool:

function

coolFn

()

{

if

(

this

.count<

1

){

setTimeout(

function

timer

()

{

this

.count++;

//`this`issafebecauseof`bind(..)`

console

.log(

"moreawesome"

);

}.bind(

this

),

100

);

//look,`bind()`!

}

}

};

obj.cool();

//moreawesome

Whetheryoupreferthenewlexical-thisbehaviorofarrow-functions,oryoupreferthetried-and-true

bind()

,it'simportant

tonotethatarrow-functionsarenotjustaboutlesstypingof"function".

Theyhaveanintentionalbehavioraldifferencethatweshouldlearnandunderstand,andifwesochoose,leverage.

Nowthatwefullyunderstandlexicalscoping(andclosure!),understandinglexical-thisshouldbeabreeze!

YouDon'tKnowJS:Scope&Closures

56

AppendixC:Lexical-this

background image

YouDon'tKnowJS:Scope&Closures

57

AppendixC:Lexical-this

background image

Ihavemanypeopletothankformakingthisbooktitleandtheoverallserieshappen.

First,ImustthankmywifeChristenSimpson,andmytwokidsEthanandEmily,forputtingupwithDadalwayspecking

awayatthecomputer.Evenwhennotwritingbooks,myobsessionwithJavaScriptgluesmyeyestothescreenfarmore

thanitshould.ThattimeIborrowfrommyfamilyisthereasonthesebookscansodeeplyandcompletelyexplain

JavaScripttoyou,thereader.Iowemyfamilyeverything.

I'dliketothankmyeditorsatO'Reilly,namelySimonSt.LaurentandBrianMacDonald,aswellastherestoftheeditorial

andmarketingstaff.Theyarefantastictoworkwith,andhavebeenespeciallyaccommodatingduringthisexperimentinto

"opensource"bookwriting,editing,andproduction.

Thankyoutothemanyfolkswhohaveparticipatedinmakingthisbookseriesbetterbyprovidingeditorialsuggestionsand

corrections,includingShelleyPowers,TimFerro,EvanBorden,ForrestL.Norvell,JenniferDavis,JesseHarlin,andmany

others.AbigthankyoutoShaneHudsonforwritingtheForewordforthistitle.

Thankyoutothecountlessfolksinthecommunity,includingmembersoftheTC39committee,whohavesharedsomuch

knowledgewiththerestofus,andespeciallytoleratedmyincessantquestionsandexplorationswithpatienceanddetail.

John-DavidDalton,Juriy"kangax"Zaytsev,MathiasBynens,AxelRauschmayer,NicholasZakas,AngusCroll,Reginald

Braithwaite,DaveHerman,BrendanEich,AllenWirfs-Brock,BradleyMeck,DomenicDenicola,DavidWalsh,TimDisney,

PetervanderZee,AndreaGiammarchi,KitCambridge,EricElliott,andsomanyothers,Ican'tevenscratchthesurface.

TheYouDon'tKnowJSbookserieswasbornonKickstarter,soIalsowishtothankallmy(nearly)500generousbackers,

withoutwhomthisbookseriescouldnothavehappened:

JanSzpila,nokiko,MuraliKrishnamoorthy,RyanJoy,CraigPatchett,pdqtrader,DaleFukami,rayhatfield,R0drigo

Perez[Mx],DanPetitt,JackFranklin,AndrewBerry,BrianGrinstead,RobSutherland,SergiMeseguer,Phillip

Gourley,MarkWatson,JeffCarouth,AlfredoSumaran,MartinSachse,MarcioBarrios,Dan,AimelyneM,Matt

Sullivan,DelnattePierre-Antoine,JakeSmith,EugenTudorancea,Iris,DavidTrinh,simonstl,RayDaly,UrosGruber,

JustinMyers,ShaiZonis,Mom&Dad,DevinClark,DennisPalmer,BrianPanahiJohnson,JoshMarshall,Marshall,

DennisKerr,MattSteele,ErikSlagter,Sacah,JustinRainbow,ChristianNilsson,Delapouite,D.Pereira,Nicolas

Hoizey,GeorgeV.Reilly,DanReeves,BrunoLaturner,ChadJennings,ShaneKing,JeremiahLeeCohick,od3n,

StanYamane,MarkoVucinic,JimB,StephenCollins,ÆgirÞorsteinsson,EricPederson,Owain,NathanSmith,

Jeanetteurphy,AlexandreELISÉ,ChrisPeterson,RikWatson,LukeMatthews,JustinLowery,MortenNielsen,

VernonKesner,ChetanShenoy,PaulTregoing,MarcGrabanski,DionAlmaer,AndrewSullivan,KeithElsass,Tom

Burke,BrianAshenfelter,DavidStuart,KarlSwedberg,Graeme,BrandonHays,JohnChristopher,Gior,manojreddy,

ChadSmith,JaredHarbour,MinoruTODA,ChrisWigley,DanielMee,Mike,Handyface,AlexJahraus,CarlFurrow,

RobFoulkrod,MaxShishkin,LeighPennyJr.,RobertFerguson,MikevanHoenselaar,HasseSchougaard,rajan

venkataguru,JeffAdams,TraeRobbins,RolfLangenhuijzen,JorgeAntunes,AlexKoloskov,HughGreenish,Tim

Jones,JoseOchoa,MichaelBrennan-White,NagaHarishMuvva,BarkócziDávid,KittHodsden,PaulMcGraw,

SaschaGoldhofer,AndrewMetcalf,MarkusKrogh,MichaelMathews,MattJared,Juanfran,GeorgieKirschner,

KennyLee,TedZhang,AmitPahwa,InbalSinai,DanRaine,SchabseLaks,MichaelTervoort,AlexandreAbreu,Alan

JosephWilliams,NicolasD,CindyWong,RegBraithwaite,LocalPCGuy,JonFriskics,ChrisMerriman,JohnPena,

JacobKatz,SueLockwood,MagnusJohansson,JeremyCrapsey,GrzegorzPawłowski,niconuzzaci,Christine

Wilks,HansBergren,charlesmontgomery,Arielבבל-רבFogel,IvanKolev,DanielCampos,HughWood,Christian

Bradford,FrédéricHarper,IonuţDanPopa,JeffTrimble,RupertWood,TreyCarrico,PanchoLopez,Joëlkuijten,

TomAMarra,JeffJewiss,JacobRios,PaoloDiStefano,SoledadPenades,ChrisGerber,AndreyDolganov,Wil

MooreIII,ThomasMartineau,Kareem,BenThouret,UdiNir,MorganLaupies,jorycarson-burson,NathanLSmith,

EricDamonWalters,DerryLozano-Hoyland,GeoffreyWiseman,mkeehner,KatieK,ScottMacFarlane,Brian

LaShomb,AdrienMas,christopherross,IanLittman,DanAtkinson,ElliotJobe,NickDozier,PeterWooley,John

Hoover,dan,MartinA.Jackson,HéctorFernandoHurtado,andyennamorato,PaulSeltmann,MelissaGore,Dave

AppendixD:Acknowledgments

YouDon'tKnowJS:Scope&Closures

58

AppendixD:ThankYou's!

background image

Pollard,JackSmith,PhilipDaSilva,GuyIsraeli,@megalithic,DamianCrawford,FelixGliesche,AprilCarterGrant,

Heidi,jimtierney,AndreaGiammarchi,NicoVignola,DonJones,ChrisHartjes,AlexHowes,johngibbon,DavidJ.

Groom,BBox,Yu'Dilys'Sun,NateSteiner,BrandonSatrom,BrianWyant,WesleyHales,IanPouncey,Timothy

KevinOxley,GeorgeTerezakis,sanjayraj,JordanHarband,MarkoMcLion,WolfgangKaufmann,PascalPeuckert,

DaveNugent,MarkusLiebelt,WellingGuzman,NickCooley,DanielMesquita,RobertSyvarth,ChrisCoyier,Rémy

Bach,AdamDougal,AlistairDuggin,DavidLoidolt,EdRicher,BrianChenault,GoldFireStudios,CarlesAndrés,

CarlosCabo,YuyaSaito,robertoricardo,BarnettKlane,MikeMoore,KevinMarx,JustinLove,JoeTaylor,Paul

Dijou,MichaelKohler,RobCassie,MikeTierney,CodyLeroyLindley,tofuji,ShimonSchwartz,Raymond,LucDe

Brouwer,DavidHayes,RhysBrett-Bowen,Dmitry,AzizKhoury,Dean,ScottTolinski-LevelUp,ClementBoirie,

DjordjeLukic,AntonKotenko,RafaelCorral,PhilipHurwitz,JonathanPidgeon,JasonCampbell,JosephC.,

SwiftOne,JanHohner,DerickBailey,getify,DanielCousineau,ChrisCharlton,EricTurner,DavidTurner,Joël

Galeran,DharmaVagabond,adam,DirkvanBergen,dave♥♫

★furf,VedranZakanj,RyanMcAllen,NataliePatrice

Tucker,EricJ.Bivona,AdamSpooner,AaronCavano,KellyPacker,EricJ,MartinDrenovac,Emilis,Michael

Pelikan,ScottF.Walter,JoshFreeman,BrandonHudgeons,vijaychennupati,BillGlennon,RobinR.,TroyForster,

otaku_coder,Brad,Scott,FrederickOstrander,AdamBrill,SebFlippence,MichaelAnderson,Jacob,AdamRandlett,

Standard,JoshuaClanton,SebastianKouba,ChrisDeck,SwordFire,HannesPapenberg,RichardWoeber,hnzz,

RobCrowther,JedidiahBroadbent,SergeyChernyshev,Jay-ArJamon,BenCombee,lucianobonachela,Mark

Tomlinson,KitCambridge,MichaelMelgares,JacobAdams,AdrianBruinhout,BevWieber,ScottPuleo,Thomas

Herzog,AprilLeone,DanielMizieliński,KeesvanGinkel,JonAbrams,ErwinHeiser,AviLaviad,Davidnewell,Jean-

FrancoisTurcot,NikoRoberts,ErikDana,CharlesNeill,AaronHolmes,GrzegorzZiółkowski,NathanYoungman,

Timothy,JacobMather,MichaelAllan,MohitSeth,RyanEwing,BenjaminVanTreese,MarceloSantos,DenisWolf,

PhilKeys,ChrisYung,TimoTijhof,MartinLekvall,Agendine,GregWhitworth,HelenHumphrey,DougalCampbell,

JohannesHarth,BrunoGirin,BrianHough,DarrenNewton,CraigMcPheat,OlivierTille,DennisRoethig,Mathias

Bynens,BrendanStromberger,sundeep,JohnMeyer,RonMale,JohnFCrostonIII,gigante,CarlBergenhem,B.J.

May,RebekahTyler,TedFoxberry,JordanReese,TerrySuitor,afeliz,TomKiefer,DarraghDuffy,Kevin

Vanderbeken,AndyPearson,SimonMacDonald,AbidDin,ChrisJoel,TomasTheunissen,DavidDick,PaulGrock,

BrandonWood,JohnWeis,dgrebb,NickJenkins,ChuckLane,JohnnyMegahan,marzsman,TatuTamminen,

GeoffreyKnauth,AlexanderTarmolov,JeremyTymes,ChadAuld,SeanParmelee,RobStaenke,DanBender,

Yannickderwa,JoshuaJones,GeertPlaisier,TomLeZotte,ChristenSimpson,StefanBruvik,JustinFalcone,Carlos

Santana,MichaelWeiss,PabloVilloslada,PeterdeHaan,DimitrisIliopoulos,seyDoggy,AdamJordens,Noah

Kantrowitz,AmolM,MatthewWinnard,DirkGinader,PhinamBui,DavidRapson,AndrewBaxter,FlorianBougel,

MichaelGeorge,AlbanEscalier,DanielSellers,SashaRudan,JohnGreen,RobertKowalski,DavidI.Teixeira

(@ditma,CharlesCarpenter,JustinYost,SamS,DenisCiccale,KevinSheurs,YannickCroissant,PauFracés,

StephenMcGowan,ShawnSearcy,ChrisRuppel,KevinLamping,JessicaCampbell,ChristopherSchmitt,Sablons,

JonathanReisdorf,BunniGek,TeddyHuff,MichaelMullany,MichaelFürstenberg,CarlHenderson,RickYoesting,

ScottNichols,HernánCiudad,AndrewMaier,MikeStapp,JesseShawl,SérgioLopes,jsulak,ShawnPrice,Joel

Clermont,ChrisRidmann,SeanTimm,JasonFinch,AidenMontgomery,ElijahManor,DerekGathright,JesseHarlin,

DillonCurry,CourtneyMyers,DiegoCadenas,ArnedeBree,JoãoPauloDubas,JamesTaylor,PhilippKraeutli,

MihaiPăun,SamGharegozlou,joshjs,MattMurchison,EricWindham,TimoBehrmann,AndrewHall,joshuaprice,

ThéophileVillard

Thisbookseriesisbeingproducedinanopensourcefashion,includingeditingandproduction.WeoweGitHubadebtof

gratitudeformakingthatsortofthingpossibleforthecommunity!

ThankyouagaintoallthecountlessfolksIdidn'tnamebutwhoInonethelessowethanks.Maythisbookseriesbe"owned"

byallofusandservetocontributetoincreasingawarenessandunderstandingoftheJavaScriptlanguage,tothebenefitof

allcurrentandfuturecommunitycontributors.

YouDon'tKnowJS:Scope&Closures

59

AppendixD:ThankYou's!


Document Outline


Wyszukiwarka

Podobne podstrony:
you don t know js up going
Adventure Midnight RPG Game Trade Magazine What You Don't Know
Dean Ing Devil You Don t Know
Spartiti Simply Red If You Don t Know Me By Now Sheet Music (Piano)
Computer Malware What You Don t Know Can Hurt You
Money Management for Women, Discover What You Should Know about Managing Your Money, but Don t!
Bearden Slides Visual Tour of what they don t want you to know about electrical circuits (www chen
Ernie Zelinski 101 Really Important Things You Already Know But Keep Forgetting
0210 You don't fool me Queen
things you don t always see
Teaching What You Dont Know
If You Don
Kate Sherwood Dark Horse SS (3) Sometimes You Just Know
What the EPA Don t Know Won t H Suzette Haden Elgin
Lewis Shiner You Never Know
I Don t know inst in C
What You Must Know About Shelving2
106 Partituras Norah Jones Don t Know Why

więcej podobnych podstron