A Re-Introduction To JavaScript (JS Tutorial) - JavaScript - MDN
A Re-Introduction To JavaScript (JS Tutorial) - JavaScript - MDN
tutorial)
by109contributors:
Showall
Introduction
Whyareintroduction?BecauseJavaScriptisnotoriousforbeing theworld'smostmisunderstood
programminglanguage.Whileitisoftenderidedasatoy,beneathitsdeceptivesimplicityliesomepowerful
languagefeatures.JavaScriptisnowusedbyanincrediblenumberofhighprofileapplications,showingthat
deeperknowledgeofthistechnologyisanimportantskillforanywebormobiledeveloper.
It'susefultostartwithanoverviewofthelanguage'shistory.JavaScriptwascreatedin1995byBrendan
Eich,anengineeratNetscape,andfirstreleasedwithNetscape2earlyin1996.(Itwasoriginallygoingtobe
calledLiveScript,butwasrenamedinanillfatedmarketingdecisioninanattempttocapitalizeonthe
popularityofSunMicrosystem'sJavalanguagedespitethetwohavingverylittleincommon.Thishas
beenasourceofconfusioneversince.)
Severalmonthslater,MicrosoftreleasedJScript,amostlycompatibleJavaScriptworkalike,withInternet
Explorer3.Severalmonthsafterthat,NetscapesubmittedJavaScriptto EcmaInternational,aEuropean
standardsorganization,whichresultedinthefirsteditionoftheECMAScriptstandardthatyear.The
standardreceivedasignificantupdateas ECMAScriptedition3in1999,andhasstayedprettymuchstable
eversince.Thefourtheditionwasabandoned,duetopoliticaldifferencesconcerninglanguagecomplexity.
ManypartsofthefourtheditionformedthebasisforECMAScriptedition5,publishedinDecemberof2009,
andforthe6thmajoreditionofthestandard,waspublishedinJuneof2015.
Forfamiliarity,IwillrefertoECMAScriptas"JavaScript"fromthispointon.
Unlikemostprogramminglanguages,theJavaScriptlanguagehasnoconceptofinputoroutput.Itis
designedtorunasascriptinglanguageinahostenvironment,anditisuptothehostenvironmentto
providemechanismsforcommunicatingwiththeoutsideworld.Themostcommonhostenvironmentisthe
browser,butJavaScriptinterpreterscanalsobefoundinahugelistofotherplaces,includingAdobe
Acrobat,AbobePhotoshop,SVGimages,Yahoo'sWidgetengine,serversideenvironmentssuchas
Node.js,NoSQLdatabasesliketheopensource ApacheCouchDB,embeddedcomputers,complete
desktopenvironmentslike GNOME(oneofthemostpopularGUIsforGNU/Linuxoperatingsystems),and
thelistgoeson.
Overview
JavaScriptisanobjectorienteddynamiclanguagewithtypesandoperators,standardbuiltinobjects,and
methods.ItssyntaxcomesfromtheJavaandClanguages,somanystructuresfromthoselanguagesapply
toJavaScriptaswell.OneofthekeydifferencesisthatJavaScriptdoesnothaveclassesinstead,theclass
functionalityisaccomplishedbyobjectprototypes.Theothermaindifferenceisthatfunctionsareobjects,
givingfunctionsthecapacitytoholdexecutablecodeandbepassedaroundlikeanyotherobject.
Let'sstartoffbylookingatthebuildingblockofanylanguage:thetypes.JavaScriptprogramsmanipulate
values,andthosevaluesallbelongtoatype.JavaScript'stypesare:
Number
String
Boolean
Function
Object
Symbol(newinEdition6)
...oh,andundefinedandnull,whichare...slightlyodd.AndArray,whichareaspecialkindofobject.And
DateandRegExp,whichareobjectsthatyougetforfree.Andtobetechnicallyaccurate,functionsarejusta
specialtypeofobject.Sothetypediagramlooksmorelikethis:
Number
String
Boolean
Symbol(newinEdition6)
Object
Function
Array
Date
RegExp
null
undefined
AndtherearesomebuiltinErrortypesaswell.Thingsarealoteasierifwestickwiththefirstdiagram,
though.
Numbers
NumbersinJavaScriptare"doubleprecision64bitformatIEEE754values",accordingtothespec.Thishas
someinterestingconsequences.There'snosuchthingasanintegerinJavaScript,soyouhavetobealittle
carefulwithyourarithmeticifyou'reusedtomathinCorJava.Watchoutforstufflike:
0.1+0.2==0.30000000000000004
Inpractice,integervaluesaretreatedas32bitints(andarestoredthatwayinsomebrowser
implementations),whichcanbeimportantforbitwiseoperations.
Thestandardarithmeticoperatorsaresupported,includingaddition,subtraction,modulus(orremainder)
arithmeticandsoforth.There'salsoabuiltinobjectthatIforgottomentionearliercalledMathifyouwantto
performmoreadvancedmathematicalfunctionsandconstants:
1
2
Math.sin(3.5);
varcircumference=Math.PI*(r+r);
YoucanconvertastringtoanintegerusingthebuiltinparseInt()function.Thistakesthebaseforthe
conversionasanoptionalsecondargument,whichyoushouldalwaysprovide:
1
2
parseInt("123",10);//123
parseInt("010",10);//10
Ifyoudon'tprovidethebase,youcangetsurprisingresultsinolderbrowsers(pre2013):
parseInt("010");//8
ThathappenedbecausetheparseInt()functiondecidedtotreatthestringasoctalduetotheleading0.
Ifyouwanttoconvertabinarynumbertoaninteger,justchangethebase:
parseInt("11",2);//3
Similarly,youcanparsefloatingpointnumbersusingthebuiltinparseFloat()functionwhichusesbase10
alwaysunlikeitsparseInt()cousin.
Youcanalsousetheunary+operatortoconvertvaluestonumbers:
+"42";//42
AspecialvaluecalledNaN(shortfor"NotaNumber")isreturnedifthestringisnonnumeric:
parseInt("hello",10);//NaN
NaNistoxic:ifyouprovideitasaninputtoanymathematicaloperationtheresultwillalsobeNaN:
NaN+5;//NaN
YoucantestforNaNusingthebuiltinisNaN()function:
isNaN(NaN);//true
JavaScriptalsohasthespecialvaluesInfinityandInfinity:
1
2
1/0;//Infinity
1/0;//Infinity
YoucantestforInfinity,InfinityandNaNvaluesusingthebuiltinisFinite()function:
1
2
3
isFinite(1/0);//false
isFinite(Infinity);//false
isFinite(NaN);//false
Note: The parseInt() and parseFloat() functions parse a string until they reach a character that
isn'tvalidforthespecifiednumberformat,thenreturnthenumberparseduptothatpoint.Howeverthe"+"operator
simplyconvertsthestringtoNaNifthereisanyinvalidcharacterinit.Justtryparsingthestring"10.2abc"witheach
methodbyyourselfintheconsoleandyou'llunderstandthedifferencesbetter.
Strings
StringsinJavaScriptaresequencesofcharacters.Moreaccurately,theyaresequencesofUnicode
characters,witheachcharacterrepresentedbya16bitnumber.Thisshouldbewelcomenewstoanyone
whohashadtodealwithinternationalization.
Ifyouwanttorepresentasinglecharacter,youjustuseastringoflength1.
Tofindthelengthofastring,accessitslengthproperty:
"hello".length;//5
There'sourfirstbrushwithJavaScriptobjects!DidImentionthatyoucanusestringslikeobjectstoo?They
havemethodsaswellthatallowyoutomanipulatethestringandaccessinformationaboutthestring:
1
2
3
"hello".charAt(0);//"h"
"hello,world".replace("hello","goodbye");//"goodbye,world"
"hello".toUpperCase();//"HELLO"
Othertypes
JavaScriptdistinguishesbetweennull,whichisavaluethatindicatesadeliberatenonvalue(andisonly
accessiblethroughthenullkeyword),andundefined,whichisavalueoftype'undefined'thatindicatesan
uninitializedvaluethatis,avaluehasn'tevenbeenassignedyet.We'lltalkaboutvariableslater,butin
JavaScriptitispossibletodeclareavariablewithoutassigningavaluetoit.Ifyoudothis,thevariable'stype
isundefined.undefinedisactuallyaconstant.
JavaScripthasabooleantype,withpossiblevaluestrueandfalse(bothofwhicharekeywords).Any
valuecanbeconvertedtoabooleanaccordingtothefollowingrules:
1. false,0,theemptystring(""),NaN,null,andundefinedallbecomefalse.
2. allothervaluesbecometrue.
YoucanperformthisconversionexplicitlyusingtheBoolean()function:
1
2
Boolean("");//false
Boolean(234);//true
However,thisisrarelynecessary,asJavaScriptwillsilentlyperformthisconversionwhenitexpectsa
boolean,suchasinanifstatement(seebelow).Forthisreason,wesometimesspeaksimplyof"true
values"and"falsevalues,"meaningvaluesthatbecometrueandfalse,respectively,whenconvertedto
booleans.Alternatively,suchvaluescanbecalled"truthy"and"falsy",respectively.
Booleanoperationssuchas&&(logicaland),||(logicalor),and!(logicalnot)aresupportedseebelow.
Variables
NewvariablesinJavaScriptaredeclaredusingthevarkeyword:
1
2
vara;
varname="simon";
Ifyoudeclareavariablewithoutassigninganyvaluetoit,itstypeisundefined.
AnimportantdifferencefromotherlanguageslikeJavaisthatinJavaScript,blocksdonothavescopeonly
functionshavescope.Soifavariableisdefinedusingvarinacompoundstatement(forexampleinsidean
ifcontrolstructure),itwillbevisibletotheentirefunction.However,startingwithECMAScriptEdition6,let
andconstdeclarationsallowyoutocreateblockscopedvariables.
Operators
JavaScript'snumericoperatorsare+,,*,/and%whichistheremainderoperator.Valuesareassigned
using=,andtherearealsocompoundassignmentstatementssuchas+=and=.Theseextendouttox=
xoperatory.
1
2
x+=5
x=x+5
Youcanuse++andtoincrementanddecrementrespectively.Thesecanbeusedasprefixorpostfix
operators.
The+operatoralsodoesstringconcatenation:
"hello"+"world";//"helloworld"
Ifyouaddastringtoanumber(orothervalue)everythingisconvertedintoastringfirst.Thismightcatch
youup:
1
2
"3"+4+5;//"345"
3+4+"5";//"75"
Addinganemptystringtosomethingisausefulwayofconvertingit.
ComparisonsinJavaScriptcanbemadeusing<,>,<=and>=.Theseworkforbothstringsandnumbers.
Equalityisalittlelessstraightforward.Thedoubleequalsoperatorperformstypecoercionifyougiveit
differenttypes,withsometimesinterestingresults:
1
2
"dog"=="dog";//true
1==true;//true
Toavoidtypecoercion,usethetripleequalsoperator:
1
2
1===true;//false
true===true;//true
Therearealso!=and!==operators.
JavaScriptalsohasbitwiseoperations.Ifyouwanttousethem,they'rethere.
Controlstructures
JavaScripthasasimilarsetofcontrolstructurestootherlanguagesintheCfamily.Conditionalstatements
aresupportedbyifandelseyoucanchainthemtogetherifyoulike:
1
2
3
4
5
6
varname="kittens";
if(name=="puppies"){
name+="!";
}elseif(name=="kittens"){
name+="!!";
}else{
7
8
9
name="!"+name;
}
name=="kittens!!"
JavaScripthaswhileloopsanddowhileloops.Thefirstisgoodforbasicloopingthesecondforloops
whereyouwishtoensurethatthebodyoftheloopisexecutedatleastonce:
1
2
3
while(true){
//aninfiniteloop!
}
4
5
6
7
8
varinput;
do{
input=get_input();
}while(inputIsNotValid(input))
JavaScript'sforloopisthesameasthatinCandJava:itletsyouprovidethecontrolinformationforyour
looponasingleline.
1
2
3
for(vari=0;i<5;i++){
//Willexecute5times
}
The&&and||operatorsuseshortcircuitlogic,whichmeanswhethertheywillexecutetheirsecondoperand
isdependentonthefirst.Thisisusefulforcheckingfornullobjectsbeforeaccessingtheirattributes:
varname=o&&o.getName();
Orforsettingdefaultvalues:
varname=otherName||"default";
JavaScripthasaternaryoperatorforconditionalexpressions:
varallowed=(age>18)?"yes":"no";
Theswitchstatementcanbeusedformultiplebranchesbasedonanumberorstring:
1
2
3
4
5
6
7
8
9
10
switch(action){
case'draw':
drawIt();
break;
case'eat':
eatIt();
break;
default:
doNothing();
}
Ifyoudon'taddabreakstatement,executionwill"fallthrough"tothenextlevel.Thisisveryrarelywhatyou
wantinfactit'sworthspecificallylabelingdeliberatefallthroughwithacommentifyoureallymeantitto
aiddebugging:
1
2
3
4
5
6
7
8
switch(a){
case1://fallthrough
case2:
eatIt();
break;
default:
doNothing();
}
Thedefaultclauseisoptional.Youcanhaveexpressionsinboththeswitchpartandthecasesifyoulike
comparisonstakeplacebetweenthetwousingthe===operator:
1
2
3
4
5
6
7
switch(1+3){
case2+2:
yay();
break;
default:
neverhappens();
}
Objects
JavaScriptobjectscanbethoughtofassimplecollectionsofnamevaluepairs.Assuch,theyaresimilarto:
DictionariesinPython
HashesinPerlandRuby
HashtablesinCandC++
HashMapsinJava
AssociativearraysinPHP
Thefactthatthisdatastructureissowidelyusedisatestamenttoitsversatility.Sinceeverything(barcore
types)inJavaScriptisanobject,anyJavaScriptprogramnaturallyinvolvesagreatdealofhashtable
lookups.It'sagoodthingthey'resofast!
The"name"partisaJavaScriptstring,whilethevaluecanbeanyJavaScriptvalueincludingmore
objects.Thisallowsyoutobuilddatastructuresofarbitrarycomplexity.
Therearetwobasicwaystocreateanemptyobject:
varobj=newObject();
And:
varobj={};
Thesearesemanticallyequivalentthesecondiscalledobjectliteralsyntax,andismoreconvenient.This
syntaxisalsothecoreofJSONformatandshouldbepreferredatalltimes.
Objectliteralsyntaxcanbeusedtoinitializeanobjectinitsentirety:
1
2
3
4
5
6
7
8
varobj={
name:"Carrot",
"for":"Max",
details:{
color:"orange",
size:12
}
}
Attributeaccesscanbechainedtogether:
1
2
obj.details.color;//orange
obj["details"]["size"];//12
Thefollowingexamplecreatesanobjectprototype,Person,andinstanceofthatprototype,You.
1
2
3
4
functionPerson(name,age){
this.name=name;
this.age=age;
}
5
6
7
8
9
//Defineanobject
varYou=newPerson("You",24);
//Wearecreatinganewpersonnamed"You"
//(thatwasthefirstparameter,andtheage..)
Oncecreated,anobject'spropertiescanagainbeaccessedinoneoftwoways:
1
2
obj.name="Simon";
varname=obj.name;
And...
1
2
obj["name"]="Simon";
varname=obj["name"];
Thesearealsosemanticallyequivalent.Thesecondmethodhastheadvantagethatthenameofthe
propertyisprovidedasastring,whichmeansitcanbecalculatedatruntimethoughusingthismethod
preventssomeJavaScriptengineandminifieroptimizationsbeingapplied.Itcanalsobeusedtosetandget
propertieswithnamesthatarereservedwords:
1
2
obj.for="Simon";//Syntaxerror,because'for'isareservedword
obj["for"]="Simon";//worksfine
Note:StartingfromEcmaScript5,reservedwordsmaybeusedasobjectpropertynames"inthebuff".
Thismeansthattheydon'tneedtobe"clothed"inquoteswhendefiningobjectliterals.SeeES5
Spec.
Formoreonobjectsandprototypessee:Object.prototype.
Arrays
ArraysinJavaScriptareactuallyaspecialtypeofobject.Theyworkverymuchlikeregularobjects
(numericalpropertiescannaturallybeaccessedonlyusing[]syntax)buttheyhaveonemagicproperty
called'length'.Thisisalwaysonemorethanthehighestindexinthearray.
Onewayofcreatingarraysisasfollows:
1
2
3
4
5
vara=newArray();
a[0]="dog";
a[1]="cat";
a[2]="hen";
a.length;//3
Amoreconvenientnotationistouseanarrayliteral:
1
2
vara=["dog","cat","hen"];
a.length;//3
Notethatarray.lengthisn'tnecessarilythenumberofitemsinthearray.Considerthefollowing:
1
2
3
vara=["dog","cat","hen"];
a[100]="fox";
a.length;//101
Rememberthelengthofthearrayisonemorethanthehighestindex.
Ifyouqueryanonexistentarrayindex,yougetundefined:
typeofa[90];//undefined
Ifyoutaketheaboveintoaccount,youcaniterateoveranarrayusingthefollowing:
1
2
for(vari=0;i<a.length;i++){
//Dosomethingwitha[i]
Thisisslightlyinefficientasyouarelookingupthelengthpropertyonceeveryloop.Animprovementisthis:
1
2
3
for(vari=0,len=a.length;i<len;i++){
//Dosomethingwitha[i]
}
Anicerlookingbutlimitedidiomis:
1
2
3
for(vari=0,item;item=a[i++];){
//Dosomethingwithitem
}
Herewearesettinguptwovariables.Theassignmentinthemiddlepartoftheforloopisalsotestedfor
truthfulnessifitsucceeds,theloopcontinues.Sinceiisincrementedeachtime,itemsfromthearraywill
beassignedtoiteminsequentialorder.Theloopstopswhena"falsy"itemisfound(suchasundefined).
Thistrickshouldonlybeusedforarrayswhichyouknowdonotcontain"falsy"values(arraysofobjectsor
DOMnodesforexample).Ifyouareiteratingovernumericdatathatmightincludea0orstringdatathat
mightincludetheemptystringyoushouldusethei,lenidiominstead.
Youcaniterateoveranarrayusingafor...inloop.Notethatifsomeoneaddednewpropertiesto
Array.prototype,theywillalsobeiteratedoverbythisloop.Thereforethismethodis"not"recommended.
AnotherwayofiteratingoveranarraythatwasaddedwithECMAScript5isforEach():
1
2
3
["dog","cat","hen"].forEach(function(currentValue,index,array){
//DosomethingwithcurrentValueorarray[index]
});
Ifyouwanttoappendanitemtoanarraysimplydoitlikethis:
a.push(item);
Arrayscomewithanumberofmethods.Seealsothefulldocumentationforarraymethods.
Methodname
a.toString()
a.toLocaleString()
a.concat(item1[,item2[,...[,
itemN]]])
a.join(sep)
Description
ReturnsastringwiththetoString()ofeachelement
separatedbycommas.
ReturnsastringwiththetoLocaleString()ofeach
elementseparatedbycommas.
Returnsanewarraywiththeitemsaddedontoit.
Convertsthearraytoastringvaluesdelimitedbythesep
param
a.pop()
Removesandreturnsthelastitem.
a.push(item1,...,itemN)
Pushaddsoneormoreitemstotheend.
a.reverse()
Reversethearray.
a.shift()
Removesandreturnsthefirstitem.
a.slice(start,end)
Returnsasubarray.
a.sort([cmpfn])
Takesanoptionalcomparisonfunction.
a.splice(start,delcount[,item1[,
Letsyoumodifyanarraybydeletingasectionand
...[,itemN]]])
replacingitwithmoreitems.
a.unshift([item])
Prependsitemstothestartofthearray.
Functions
Alongwithobjects,functionsarethecorecomponentinunderstandingJavaScript.Themostbasicfunction
couldn'tbemuchsimpler:
1
2
3
4
functionadd(x,y){
vartotal=x+y;
returntotal;
}
Thisdemonstratesabasicfunction.AJavaScriptfunctioncantake0ormorenamedparameters.The
functionbodycancontainasmanystatementsasyoulike,andcandeclareitsownvariableswhicharelocal
tothatfunction.Thereturnstatementcanbeusedtoreturnavalueatanytime,terminatingthefunction.If
noreturnstatementisused(oranemptyreturnwithnovalue),JavaScriptreturnsundefined.
Thenamedparametersturnouttobemorelikeguidelinesthananythingelse.Youcancallafunction
withoutpassingtheparametersitexpects,inwhichcasetheywillbesettoundefined.
1
2
add();//NaN
//Youcan'tperformadditiononundefined
Youcanalsopassinmoreargumentsthanthefunctionisexpecting:
1
2
add(2,3,4);//5
//addedthefirsttwo;4wasignored
Thatmayseemalittlesilly,butfunctionshaveaccesstoanadditionalvariableinsidetheirbodycalled
arguments,whichisanarraylikeobjectholdingallofthevaluespassedtothefunction.Let'srewritethe
addfunctiontotakeasmanyvaluesaswewant:
1
2
3
4
5
6
7
functionadd(){
varsum=0;
for(vari=0,j=arguments.length;i<j;i++){
sum+=arguments[i];
}
returnsum;
}
8
9
add(2,3,4,5);//14
That'sreallynotanymoreusefulthanwriting2+3+4+5though.Let'screateanaveragingfunction:
1
2
3
4
5
6
7
functionavg(){
varsum=0;
for(vari=0,j=arguments.length;i<j;i++){
sum+=arguments[i];
}
returnsum/arguments.length;
}
8
9
avg(2,3,4,5);//3.5
Thisisprettyuseful,butintroducesanewproblem.Theavg()functiontakesacommaseparatedlistof
argumentsbutwhatifyouwanttofindtheaverageofanarray?Youcouldjustrewritethefunctionas
follows:
1
2
3
4
5
6
7
functionavgArray(arr){
varsum=0;
for(vari=0,j=arr.length;i<j;i++){
sum+=arr[i];
}
returnsum/arr.length;
}
8
9
avgArray([2,3,4,5]);//3.5
Butitwouldbenicetobeabletoreusethefunctionthatwe'vealreadycreated.Luckily,JavaScriptletsyou
callafunctionandcallitwithanarbitraryarrayofarguments,usingtheapply()methodofanyfunction
object.
avg.apply(null,[2,3,4,5]);//3.5
Thesecondargumenttoapply()isthearraytouseasargumentsthefirstwillbediscussedlateron.This
emphasizesthefactthatfunctionsareobjectstoo.
JavaScriptletsyoucreateanonymousfunctions.
1
2
3
4
5
6
7
varavg=function(){
varsum=0;
for(vari=0,j=arguments.length;i<j;i++){
sum+=arguments[i];
}
returnsum/arguments.length;
};
Thisissemanticallyequivalenttothefunctionavg()form.It'sextremelypowerful,asitletsyouputafull
functiondefinitionanywherethatyouwouldnormallyputanexpression.Thisenablesallsortsofclever
tricks.Here'sawayof"hiding"somelocalvariableslikeblockscopeinC:
1
2
vara=1;
varb=2;
3
4
5
6
7
(function(){
varb=3;
a+=b;
})();
8
9
10
a;//4
b;//2
JavaScriptallowsyoutocallfunctionsrecursively.Thisisparticularlyusefulfordealingwithtreestructures,
suchasyougetinthebrowserDOM.
1
2
3
4
5
6
7
8
9
10
functioncountChars(elm){
if(elm.nodeType==3){//TEXT_NODE
returnelm.nodeValue.length;
}
varcount=0;
for(vari=0,child;child=elm.childNodes[i];i++){
count+=countChars(child);
}
returncount;
}
Thishighlightsapotentialproblemwithanonymousfunctions:howdoyoucallthemrecursivelyiftheydon't
haveaname?JavaScriptletsyounamefunctionexpressionsforthis.YoucanusenamedIIFEs
(ImmediatelyInvokedFunctionExpressions)asbelow:
1
2
3
4
5
6
7
8
9
10
varcharsInBody=(functioncounter(elm){
if(elm.nodeType==3){//TEXT_NODE
returnelm.nodeValue.length;
}
varcount=0;
for(vari=0,child;child=elm.childNodes[i];i++){
count+=counter(child);
}
returncount;
})(document.body);
Thenameprovidedtoafunctionexpressionasaboveisonlyavailabletothefunction'sownscope.This
bothallowsmoreoptimizationstobedonebytheengineandamorereadablecode.Thenamealsoshows
upinthedebuggerandsomestacktraceswhichcansaveyoutime.
NotethatJavaScriptfunctionsarethemselvesobjectsandyoucanaddorchangepropertiesonthemjust
likeonobjectswe'veseenintheObjectssection.
Customobjects
Note: For a more detailed discussion of object-oriented programming in JavaScript, see Introduction to
ObjectOrientedJavaScript.
InclassicObjectOrientedProgramming,objectsarecollectionsofdataandmethodsthatoperateonthat
data.JavaScriptisaprototypebasedlanguagewhichcontainsnoclassstatement,suchasisfoundinC++
orJava.(Thisissometimesconfusingforprogrammersaccustomedtolanguageswithaclassstatement.)
Instead,JavaScriptusesfunctionsasclasses.Let'sconsiderapersonobjectwithfirstandlastnamefields.
Therearetwowaysinwhichthenamemightbedisplayed:as"firstlast"oras"last,first".Usingthe
functionsandobjectsthatwe'vediscussedpreviously,here'sonewayofdoingit:
1
2
3
4
5
6
7
8
9
10
11
12
functionmakePerson(first,last){
return{
first:first,
last:last
};
}
functionpersonFullName(person){
returnperson.first+''+person.last;
}
functionpersonFullNameReversed(person){
returnperson.last+','+person.first;
}
13
14
15
16
s=makePerson("Simon","Willison");
personFullName(s);//"SimonWillison"
personFullNameReversed(s);"Willison,Simon"
Thisworks,butit'sprettyugly.Youendupwithdozensoffunctionsinyourglobalnamespace.Whatwe
reallyneedisawaytoattachafunctiontoanobject.Sincefunctionsareobjects,thisiseasy:
1
2
3
4
5
6
7
functionmakePerson(first,last){
return{
first:first,
last:last,
fullName:function(){
returnthis.first+''+this.last;
},
8
9
10
11
12
fullNameReversed:function(){
returnthis.last+','+this.first;
}
};
}
13
14
15
16
s=makePerson("Simon","Willison")
s.fullName();//"SimonWillison"
s.fullNameReversed();//"Willison,Simon"
There'ssomethingherewehaven'tseenbefore:thethiskeyword.Usedinsideafunction,thisrefersto
thecurrentobject.Whatthatactuallymeansisspecifiedbythewayinwhichyoucalledthatfunction.Ifyou
calleditusingdotnotationorbracketnotationonanobject,thatobjectbecomesthis.Ifdotnotationwasn't
usedforthecall,thisreferstotheglobalobject.
Notethatthisisafrequentcauseofmistakes.Forexample:
1
2
3
s=makePerson("Simon","Willison");
varfullName=s.fullName;
fullName();//undefinedundefined
WhenwecallfullName()alone,withoutusings.fullName(),thisisboundtotheglobalobject.Since
therearenoglobalvariablescalledfirstorlastwegetundefinedforeachone.
WecantakeadvantageofthethiskeywordtoimproveourmakePersonfunction:
1
2
3
4
5
6
7
8
9
10
11
functionPerson(first,last){
this.first=first;
this.last=last;
this.fullName=function(){
returnthis.first+''+this.last;
};
this.fullNameReversed=function(){
returnthis.last+','+this.first;
};
}
vars=newPerson("Simon","Willison");
Wehaveintroducedanotherkeyword:new.newisstronglyrelatedtothis.Whatitdoesisitcreatesabrand
newemptyobject,andthencallsthefunctionspecified,withthissettothatnewobject.Noticethoughthat
thefunctionspecifiedwiththisdoesnotreturnavaluebutmerelymodifiesthethisobject.It'snewthat
returnsthethisobjecttothecallingsite.Functionsthataredesignedtobecalledbynewarecalled
constructorfunctions.Commonpracticeistocapitalizethesefunctionsasaremindertocallthemwithnew.
TheimprovedfunctionstillhasthesamepitfallwithcallingfullName()alone.
Ourpersonobjectsaregettingbetter,buttherearestillsomeuglyedgestothem.Everytimewecreatea
personobjectwearecreatingtwobrandnewfunctionobjectswithinitwouldn'titbebetterifthiscode
wasshared?
1
2
3
4
5
6
7
8
9
10
11
12
functionpersonFullName(){
returnthis.first+''+this.last;
}
functionpersonFullNameReversed(){
returnthis.last+','+this.first;
}
functionPerson(first,last){
this.first=first;
this.last=last;
this.fullName=personFullName;
this.fullNameReversed=personFullNameReversed;
}
That'sbetter:wearecreatingthemethodfunctionsonlyonce,andassigningreferencestotheminsidethe
constructor.Canwedoanybetterthanthat?Theanswerisyes:
1
2
3
4
5
6
7
8
9
10
functionPerson(first,last){
this.first=first;
this.last=last;
}
Person.prototype.fullName=function(){
returnthis.first+''+this.last;
};
Person.prototype.fullNameReversed=function(){
returnthis.last+','+this.first;
};
Person.prototypeisanobjectsharedbyallinstancesofPerson.Itformspartofalookupchain(thathasa
specialname,"prototypechain"):anytimeyouattempttoaccessapropertyofPersonthatisn'tset,
JavaScriptwillcheckPerson.prototypetoseeifthatpropertyexiststhereinstead.Asaresult,anything
assignedtoPerson.prototypebecomesavailabletoallinstancesofthatconstructorviathethisobject.
Thisisanincrediblypowerfultool.JavaScriptletsyoumodifysomething'sprototypeatanytimeinyour
program,whichmeansyoucanaddextramethodstoexistingobjectsatruntime:
1
2
s=newPerson("Simon","Willison");
s.firstNameCaps();//TypeErroronline1:s.firstNameCapsisnotafunction
3
4
5
6
7
Person.prototype.firstNameCaps=function(){
returnthis.first.toUpperCase()
};
s.firstNameCaps();//"SIMON"
Interestingly,youcanalsoaddthingstotheprototypeofbuiltinJavaScriptobjects.Let'saddamethodto
Stringthatreturnsthatstringinreverse:
1
2
vars="Simon";
s.reversed();//TypeErroronline1:s.reversedisnotafunction
3
4
5
6
7
8
9
10
String.prototype.reversed=function(){
varr="";
for(vari=this.length1;i>=0;i){
r+=this[i];
}
returnr;
};
11
12
s.reversed();//nomiS
Ournewmethodevenworksonstringliterals!
"Thiscannowbereversed".reversed();//desreverebwonnacsihT
AsImentionedbefore,theprototypeformspartofachain.TherootofthatchainisObject.prototype,
whosemethodsincludetoString()itisthismethodthatiscalledwhenyoutrytorepresentanobjectas
astring.ThisisusefulfordebuggingourPersonobjects:
1
2
vars=newPerson("Simon","Willison");
s;//[objectObject]
3
4
5
Person.prototype.toString=function(){
return'<Person:'+this.fullName()+'>';
7
8
s.toString();//"<Person:SimonWillison>"
Rememberhowavg.apply()hadanullfirstargument?Wecanrevisitthatnow.Thefirstargumentto
apply()istheobjectthatshouldbetreatedas'this'.Forexample,here'satrivialimplementationofnew:
1
2
3
4
5
functiontrivialNew(constructor,...args){
varo={};//Createanobject
constructor.apply(o,args);
returno;
}
Thisisn'tanexactreplicaofnewasitdoesn'tsetuptheprototypechain(itwouldbedifficulttoillustrate).
Thisisnotsomethingyouuseveryoften,butit'susefultoknowabout.Inthissnippet,...args(including
theellipsis)iscalledthe"restarguments"asthenameimplies,thiscontainstherestofthearguments.
Calling
varbill=trivialNew(Person,"William","Orange");
isthereforealmostequivalentto
varbill=newPerson("William","Orange");
apply()hasasisterfunctionnamedcall,whichagainletsyousetthisbuttakesanexpandedargument
listasopposedtoanarray.
1
2
3
4
5
6
7
8
functionlastNameCaps(){
returnthis.last.toUpperCase();
}
vars=newPerson("Simon","Willison");
lastNameCaps.call(s);
//Isthesameas:
s.lastNameCaps=lastNameCaps;
s.lastNameCaps();
Innerfunctions
Innerfunctions
JavaScriptfunctiondeclarationsareallowedinsideotherfunctions.We'veseenthisoncebefore,withan
earliermakePerson()function.AnimportantdetailofnestedfunctionsinJavaScriptisthattheycanaccess
variablesintheirparentfunction'sscope:
1
2
3
4
5
6
7
functionbetterExampleNeeded(){
vara=1;
functiononeMoreThanA(){
returna+1;
}
returnoneMoreThanA();
}
Thisprovidesagreatdealofutilityinwritingmoremaintainablecode.Ifafunctionreliesononeortwoother
functionsthatarenotusefultoanyotherpartofyourcode,youcannestthoseutilityfunctionsinsidethe
functionthatwillbecalledfromelsewhere.Thiskeepsthenumberoffunctionsthatareintheglobalscope
down,whichisalwaysagoodthing.
Thisisalsoagreatcountertothelureofglobalvariables.Whenwritingcomplexcodeitisoftentemptingto
useglobalvariablestosharevaluesbetweenmultiplefunctionswhichleadstocodethatishardto
maintain.Nestedfunctionscansharevariablesintheirparent,soyoucanusethatmechanismtocouple
functionstogetherwhenitmakessensewithoutpollutingyourglobalnamespace'localglobals'ifyoulike.
Thistechniqueshouldbeusedwithcaution,butit'sausefulabilitytohave.
Closures
ThisleadsustooneofthemostpowerfulabstractionsthatJavaScripthastoofferbutalsothemost
potentiallyconfusing.Whatdoesthisdo?
1
2
3
4
5
6
7
8
9
functionmakeAdder(a){
returnfunction(b){
returna+b;
};
}
varx=makeAdder(5);
vary=makeAdder(20);
x(6);//?
y(7);//?
ThenameofthemakeAdderfunctionshouldgiveitaway:itcreatesnew'adder'functions,whichwhencalled
withoneargumentaddittotheargumentthattheywerecreatedwith.
What'shappeninghereisprettymuchthesameaswashappeningwiththeinnerfunctionsearlieron:a
functiondefinedinsideanotherfunctionhasaccesstotheouterfunction'svariables.Theonlydifferencehere
isthattheouterfunctionhasreturned,andhencecommonsensewouldseemtodictatethatitslocal
variablesnolongerexist.Buttheydostillexistotherwisetheadderfunctionswouldbeunabletowork.
What'smore,therearetwodifferent"copies"ofmakeAdder'slocalvariablesoneinwhichais5andone
inwhichais20.Sotheresultofthosefunctioncallsisasfollows:
1
2
x(6);//returns11
y(7);//returns27
Here'swhat'sactuallyhappening.WheneverJavaScriptexecutesafunction,a'scope'objectiscreatedto
holdthelocalvariablescreatedwithinthatfunction.Itisinitialisedwithanyvariablespassedinasfunction
parameters.Thisissimilartotheglobalobjectthatallglobalvariablesandfunctionslivein,butwithacouple
ofimportantdifferences:firstly,abrandnewscopeobjectiscreatedeverytimeafunctionstartsexecuting,
andsecondly,unliketheglobalobject(whichisaccessibleasthisandinbrowsersisaccessibleaswindow)
thesescopeobjectscannotbedirectlyaccessedfromyourJavaScriptcode.Thereisnomechanismfor
iteratingoverthepropertiesofthecurrentscopeobject,forexample.
SowhenmakeAdderiscalled,ascopeobjectiscreatedwithoneproperty:a,whichistheargumentpassed
tothemakeAdderfunction.makeAdderthenreturnsanewlycreatedfunction.NormallyJavaScript'sgarbage
collectorwouldcleanupthescopeobjectcreatedformakeAdderatthispoint,butthereturnedfunction
maintainsareferencebacktothatscopeobject.Asaresult,thescopeobjectwillnotbegarbagecollected
untiltherearenomorereferencestothefunctionobjectthatmakeAdderreturned.
Scopeobjectsformachaincalledthescopechain,similartotheprototypechainusedbyJavaScript'sobject
system.
Aclosureisthecombinationofafunctionandthescopeobjectinwhichitwascreated.
Closuresletyousavestateassuch,theycanoftenbeusedinplaceofobjects.Severalexcellent
introductionstoclosurescanbefound here.
Memoryleaks
AnunfortunatesideeffectofclosuresisthattheymakeittriviallyeasytoleakmemoryinInternetExplorer.
JavaScriptisagarbagecollectedlanguageobjectsareallocatedmemoryupontheircreationandthat
memoryisreclaimedbythebrowserwhennoreferencestoanobjectremain.Objectsprovidedbythehost
environmentarehandledbythatenvironment.
BrowserhostsneedtomanagealargenumberofobjectsrepresentingtheHTMLpagebeingpresented
theobjectsoftheDOM.Itisuptothebrowsertomanagetheallocationandrecoveryofthese.
InternetExplorerusesitsowngarbagecollectionschemeforthis,separatefromthemechanismusedfor
JavaScript.Itistheinteractionbetweenthetwothatcancausememoryleaks.
AmemoryleakinIEoccursanytimeacircularreferenceisformedbetweenaJavaScriptobjectandanative
object.Considerthefollowing:
1
2
3
4
5
functionleakMemory(){
varel=document.getElementById('el');
varo={'el':el};
el.o=o;
}
ThecircularreferenceformedabovecreatesamemoryleakIEwillnotfreethememoryusedbyelando
untilthebrowseriscompletelyrestarted.
Theabovecaseislikelytogounnoticedmemoryleaksonlybecomearealconcerninlongrunning
applicationsorapplicationsthatleaklargeamountsofmemoryduetolargedatastructuresorleakpatterns
withinloops.
Leaksarerarelythisobviousoftentheleakeddatastructurecanhavemanylayersofreferences,
obscuringthecircularreference.
Closuresmakeiteasytocreateamemoryleakwithoutmeaningto.Considerthis:
1
2
3
4
5
6
functionaddHandler(){
varel=document.getElementById('el');
el.onclick=function(){
el.style.backgroundColor='red';
};
}
Theabovecodesetsuptheelementtoturnredwhenitisclicked.Italsocreatesamemoryleak.Why?
Becausethereferencetoelisinadvertentlycaughtintheclosurecreatedfortheanonymousinnerfunction.
ThiscreatesacircularreferencebetweenaJavaScriptobject(thefunction)andanativeobject(el).
Thereareanumberofworkaroundsforthisproblem.Thesimplestisnottousetheelvariable:
1
2
3
4
5
functionaddHandler(){
document.getElementById('el').onclick=function(){
this.style.backgroundColor='red';
};
}
Surprisingly,onetrickforbreakingcircularreferencesintroducedbyaclosureistoaddanotherclosure:
1
2
3
4
5
6
7
8
9
functionaddHandler(){
varclickHandler=function(){
this.style.backgroundColor='red';
};
(function(){
varel=document.getElementById('el');
el.onclick=clickHandler;
})();
}
Theinnerfunctionisexecutedstraightaway,andhidesitscontentsfromtheclosurecreatedwith
clickHandler.
Anothergoodtrickforavoidingclosuresisbreakingcircularreferencesduringthewindow.onunloadevent.
Manyeventlibrarieswilldothisforyou.NotethatdoingsodisablesthebackforwardcacheinFirefox,so
youshouldnotregisteranunloadlistenerinFirefox,unlessyouhaveotherreasonstodoso.