JavaScript key concepts & syntax

JavaScript like all programming languages has certain concepts and syntax you need to understand in order to become proficient in its overall use. In this chapter, you'll learn key concepts & syntax that apply equally across any JavaScript application -- whether it's designed for user interfaces on browsers or server side business logic -- as well as key concepts & syntax that crosscut into other functional JavaScript topics, such as JavaScript data types, JavaScript object-orientated and prototype-based programming, JavaScript for loops and JavaScript asynchronous behavior.

Running JavaScript

There are two broad ways to run JavaScript. One option is to use an interactive environment (a.k.a. REPL Read–eval–print loop) -- which in itself is backed by a JavaScript engine -- to run JavaScript in a piecemeal fashion. While the other more typical option is to place JavaScript in .js files and run these files through a JavaScript engine.

In both cases, a JavaScript engine is provided by either an application like a browser or a non-graphical user interface(GUI) piece of software.

JavaScript REPLs

There are two types of JavaScript REPLs. There are online JavaScript REPLs, like those accompanying the examples in this book, which allow you to run JavaScript in a few clicks relying on a third party service like https://babeljs.io/repl/ or https://replit.com/languages/javascript. And there are offline JavaScript REPLs that allow you to run JavaScript on a non-networked computer, like those included in the Google Chrome browser and Firefox browser or those provided by non-GUI software such as Node JS and Deno.

For example, to access the JavaScript REPL on a Google Chrome browser, go to the 'More Tools' menu -> 'Developer Tools' option or use the Ctrl-Shift-I keyboard combination, at which time a browser pane is open at the bottom, if you select the 'Console' tab on this last pane you can interactively type in JavaScript as shown in figure 3-1. To access the JavaScript REPL on a Firefox browser, go to the 'More Tools' menu -> 'Web Developer Tools' option or use the Ctrl-Shift-I keyboard combination, at which time a browser pane is open at the bottom, if you select the 'Console' tab on this last pane you can interactively type in JavaScript as shown in figure 3-2.

Google Chrome browser REPL console
Figure 3-1. Google Chrome browser REPL console

Firefox browser REPL console
Figure 3-2. Firefox browser REPL console

Unlike browsers, non-GUI software that can also operate as an offline JavaScript REPL requires an installation process. You can consult the Node JS installation process and Deno installation process in other chapters of the book. Once you install one of these non-GUI softwares, it's as simple as invoking its main executable (e.g. node or deno) to enter a JavaScript REPL environment, like it's shown in the Node JS REPL section.

The HTML <script> element

Moving beyond REPLs, you'll rely heavily on the HTML <script> element which is what browsers use to run JavaScript. For non-GUI software -- like Node JS and Deno -- you'll have no need for the <script> element, since these environments don't have built-in HTML support like browsers. For non-GUI software you can instead place JavaScript in .js files and run it directly using the software's executable (e.g. node myscript.js).

The HTML <script> element at its simplest can run JavaScript on browsers by placing it in between an opening <script> tag and closing </script> tag, following the same conventions as other HTML elements with opening and closing tags to delimit content (e.g. <p>...</p> , <li>...</li>). Listing 3-1 shows some JavaScript declared in a <script> element.

Listing 3-1. JavaScript declared in <script> element
<html>

<head>
  <script>
    var x = 1;
    var y = 2;
    var z = x + y;
    alert(z);
  </script>
</head>

<body>
  <h1> An alert box triggered by a <script> element <h1>
</body>

</html>

See the Pen HTML <script> element with inline JavaScript - Modern JS by D Rubio (@modernjs) on CodePen.


If you run the example in listing 3-1, you'll see an alert box display the number three. The JavaScript logic in listing 3-1 first declares the x and y variables and assigns them a number, followed by declaring the z variable that calculates the sum of x and y, finishing with the alert(z) statement that creates an alert box with the value of z.

Although inserting inline JavaScript in an HTML <script> element is simple, it can become unwieldy over time. Another alternative is to place JavaScript in .js files and reference them using a url path using the same HTML <script> element, but adding the src attribute with a value that points to a url with a .js file.

In addition to supporting the src attribute, the HTML <script> element also supports multiple attributes that influence JavaScript definitions, processing and security behaviors. Table 3-1 illustrates the various HTML <script> element attributes.

Table 3-1. HTML <script> element attributes
Purpose groupAttributeDescriptionValue options
DefinitionnomoduleIndicates if a <script> element should be ignored by browsers that support ES6 (ES2015) modules. Note a <script nomodule> element is often complemented by a <script type="module"> element.
  • Default (when omitted): False.
  • Since this is a boolean attribute, declaring it (e.g. <script nomodule>) is considered true and the contents are ignored by browsers that support ES6 (ES2015) modules; not declaring it (e.g. <script>) is considered false and the contents are processed by browsers that support ES6 (ES2015) modules.
srcDefines the .js file to load
  • An absolute path to the script on the site hosting the HTML document: /assets/scripts/intro.js
  • A relative path to the script from the HTML document on the site: scripts/intro.js
  • A full path with explicit site to the script: https://modernjs.com/assets/scripts/intro.js
typeDefines the language or format of the data in the <script> element as a MIME type
  • Default (when omitted): text/javascript
  • text/javascript
  • module; note a <script type="module"> element is often complemented by a <script nomodule> element.
ProcessingasyncDefines parallel downloading of .js files, allowing other activities (e.g. HTML processing, other <script> element) to proceed without waiting. Although the downloading of .js files is done in parallel, the execution of a .js file starts immediately after it's downloaded and blocks other activities like HTML processing. In addition, if two or more <script> elements use async, there's no guarantee they will run in their declared order since downloads are asynchronous and can vary depending on network latency or other factors.
  • Default (when omitted): False.
  • Since this is a boolean attribute, declaring it (e.g. <script async>) is considered true and the file is downloaded asynchronously; not declaring it (e.g. <script>) is considered false and the file is downloaded and processed synchronously (i.e. blocking until it's downloaded and processed).
deferDefines parallel downloading of .js files, allowing other activities (e.g. HTML processing, other <script> element) to proceed without waiting. The execution of all .js files marked with defer only starts once the HTML document has been completely processed -- technically the DOMContentLoaded event is fired -- guaranteeing access to elements in the HTML document. In addition, the execution of <script> elements with defer is guaranteed to be made in their declared order.
  • Default (when omitted): False.
  • Since this is a boolean attribute, declaring it (e.g. <script defer>) is considered true and the file is downloaded asynchronously and run after the HTML document is processed; not declaring it (e.g. <script>) is considered false and the file is downloaded and processed synchronously (i.e. blocking until it's downloaded and processed).
SecuritycrossoriginAllows a <script> to influence its Cross-Origin Resource Sharing (CORS) request.
  • Default (when omitted): anonymous.
  • anonymous
  • use-credentials
integrityBased on the Subresource Integrity specification[1], it defines a hash to verify the contents of a .js file haven't been tampered with. It's generally used on .js files hosted by third party sites.
  • Default (when omitted): None.
  • A base64-encoded hash prefixed with the hash algorithm -- sha256, sha384 or sha512 -- followed by a dash and the hash itself (e.g. sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=).
nonceA "nonce" (a.k.a. number only used once) to whitelist scripts via the HTTP header Content-Security-Policy in the script-src directive.
  • Default (when omitted): None.
  • Value should not be hard-coded and be auto-generated on the server-side dynamically creating the <script> element with a fresh nonce value on all requests.
referrerpolicyDefines a value to influence the value of the HTTP header Referer that accompanies a request for a .js file.
  • Default (when omitted): strict-origin-when-cross-origin or referrer policy specified in HTML document response or by browser.
  • Various values per spec[2]

To add more context to the HTML <script> element attributes in table 3-1, the following sub-sections describe attributes in greater detail based on their purpose.

Definition attributes for the HTML <script> element: nomodule, src & type

The JavaScript in listing 3-1 -- the four lines inbetween the <script> tag & </script> tag -- can be placed in a .js file and loaded into an HTML file using the src attribute. Assuming you place the JavaScript in a file named intro.js and make it available under a web site's absolute path /assets/scripts/intro.js, you can substitute the <script> element in listing 3-1 with the element <script src="/assets/scripts/intro.js"></script> to obtain the same results.

Whether you opt to use .js files with the src attribute or use inline JavaScript like listing 3-1, the <script> element always implies a type="text/javascript" attribute, indicating a MIME type[3] for the JavaScript language. Throughout the evolution of JavaScript and the HTML <script> element, there were attempts to incorporate other JavaScript MIME types (e.g. application/javascript, application/ecmascript, text/ecmascript), however, these MIME types are no longer relevant and should be considered legacy types. The only <script> element type attribute values you should use are: text/javascript or module.

A <script> element in the form <script type="module" src="..."></script> tells a browser the referenced JavaScript should be treated as a JavaScript ES6 (ES2015) module. As outlined in the modules, namespaces & module types section in the modern JavaScript essentials chapter, modules are one of the more convoluted topics in JavaScript. Later in this chapter, I'll explain the finer details of what constitutes a JavaScript ES6 (ES2015) module, but for the moment, it's sufficient you know the <script> element with the type="module" attribute is how browsers tell the difference between a file being a JavaScript ES6 (ES2015) module and a file containing plain JavaScript.

The <script> element also supports the nomodule attribute to tell a browser to ignore the <script> element in case it supports JavaScript ES6 (ES2015) modules. The purpose of the nomodule attribute is to allow older browsers -- that don't support JavaScript ES6 (ES2015) modules -- the ability to run a fallback <script> element with equivalent JavaScript that isn't an ES6 (ES2015) module. In most cases, an element like <script nomodule src="pseudo-module-pre-es6.js"> is accompanied by an element like <script type="module" src="module-es6.js"></script>, to deliver the following behavior: if a browser supports ES6 (2015) modules, it ignores the pseudo-module-pre-es6.js file on account of the nomodule attribute and loads the module-es6.js file as a module on account of the type="module" attribute; if a browser doesn't support ES6 (2015) modules, it loads the pseudo-module-pre-es6.js file -- which contains the equivalent module functionality of module-es6.js in plain JavaScript -- because it doesn't know what the nomodule attribute does, plus it ignores the module-es6.js file since it also doesn't know how to process type="module" which is also an ES6 (2015) specific attribute.

Processing attributes for the HTML <script> element: async & defer

An HTML document can contain one or dozens of <script> elements. The issue with having too many <script> elements is you need to be aware of their processing behavior. When you declare a <script> element with either inline JavaScript like listing 3-1 or reference a JavaScript .js file with the src attribute, the processing behavior is sychronous (a.k.a. blocking), which means nothing besides the processing of the <script> element happens.

Although this default synchronous behavior creates a predicatable outcome, with the first declared <script> element processed first, followed by the second <script> element processed second and so on, this blocking behavior of not being able to do tasks in parallel can lead to a bad user experience. This potential bad user experience is rooted in the fact that users are primarily interested in seeing the HTML document -- which must also be processed -- and not on waiting for <script> elements to be processed. But this is where you'll face a conundrum, most <script> elements exist to alter or influence an HTML document, so how do you go about presenting an HTML document in the quickest way possible, without <script> elements blocking the process ?

When it comes to presenting an HTML document in the quickest way possible, the position of HTML <script> elements matters. Although you can technically place <script> elements anywhere on an HTML document, there are implications to every position. The following is a list of the <script> element positions and their implications:

An important and subtle behavior of the <script> element which may not be obvious in this last list, is that <script> elements with a src attribute pointing to a .js file must also undergo a download step as part of the processing workflow. Since download times can vary widely, depending on file sizes and network conditions, the <script> element supports the async and defer attributes to influence download & processing behaviors.

Using either the async or defer attribute on a <script> element with a src attribute, allows the download process of a referenced .js file to run in parallel, that is, as soon as it's encountered the download process begins and work can move forward to another <script> element or the processing of the HTML document.

The only difference between using the async and defer attributes is when the actual contents of the JavaScript .js file are processed. For <script> elements with async the contents are processed immediately after download, in the process blocking any other task, whereas for <script> elements with defer the contents are executed until the HTML document has been completely processed, specifically the DOMContentLoaded event is fired.

Tip The DOMContentLoaded event is the same one used by jQuery's $(document).ready() method presented in the jQuery example in listing 1-2. Therefore, JavaScript wrapped in jQuery's $(document).ready() method is equivalent to placing it inside a <script defer> element.

Figure 3-3 illustrates how an HTML document is processed with a plain <script> element, with a <script defer> element, with a <script async> element, as well as how <script> loading behaves with a JavaScript module (i.e. <script type="module">).

script tag behavior with async and defer attributes
Figure 3-3. <script> tag behavior with async & defer attributes; source HTML <script> spec[4]

As you can see in figure 3-3, the <script defer> element preemptively downloads JavaScript while HTML processing can continue, waiting until the end of HTML processing to process the JavaScript downloaded with defer. The <script async> element also preemptively downloads JavaScript while HTML processing can continue, but it interrupts HTML processing as soon as the download is complete to process the JavaScript with async. For JavaScript modules, you can also see in figure 3-3 it isn't necessary to use the defer attribute, since by default a <script type="module"> element is preemptively downloaded and processed until HTML processing is done. Finally, figure 3-3 also shows you can use the async attribute on a JavaScript module to trigger the processing of a module while the HTML is still being processed.

Although preemptively downloading JavaScript using either defer or async on <script> elements sounds like a great idea, both attributes can present undesired side effects.

One issue with <script> elements that use async is their processing order isn't necessarily the same as their declared order. Although all download processes start in the <script> declared order, the download completions won't necessarily finish in the same order due to file size, network issues or other factors, making the processing order inconsistent. Therefore, if you need to guarantee a given exeuction order for <script> elements (e.g. one <script> element depends on another) you shouldn't use the async attribute.

Unlike the async attribute, <script> elements that use defer are guranteed to execute in their declared order. However, the reason they can guarantee the same execution order as their declared order, is because the execution is deferred until the HTML document is completely processed. Therefore, if you need certain JavaScript logic to be executed prior or as part of the HTML processing workflow, you shouldn't use the defer attribute.

So plain <script> elements, <script defer> elements and <script async> elements, all have their use. One technique I recommend you follow to determine when to use each one is to follow above the fold web design[5]. The 'above the fold' concept is derived from newspapers where readers can immediately see what's above the fold, but need to flip the newspaper to see other content. In web design, 'above the fold' refers to what users are able to see on their browsers before needing to scroll to see other content. Given the attention span of most users surfing the web, keeping the 'above the fold' loading times low for HTML documents is critical.

Applied to the placement of <script> elements, 'above the fold' design would mean following these rules of thumb:

Security attributes for the HTML <script> element: crossorigin, integrity, nonce & referrerpolicy

The web has become an increasingly hostile place as more commercial activity takes place on it. With JavaScript driving much of this activity on browsers, it should come as no surprise that the <script> element has more attributes related to security than anything else.

The <script> element's integrity attribute allows browsers to validate if JavaScript files have been tampered with. All files have a unique hash -- or in layman's terms a fingerprint -- that when a file is modified by even a character or space, the file's hash changes. The integrity attribute is widely used in JavaScript files distributed via content delivery networks (CDNs), where files can end up being handled and distributed by multiple parties that can alter their original source. To solve this potential tampering issue, the original creator of a file publishes its hash which is added to a <script> element's integrity value, ensuring that wherever a file is obtained it's a trustworthy copy.

The integrity attribute validation workflow is done per the Subresource Integrity specification[1] supported by major browsers. In case a browser detects a <script> element's integrity attribute value doesn't match the value calculated by the browser, it blocks loading the JavaScript and generates an error in the form: "Failed to find a valid digest in the 'integrity' attribute for resource '<src_url>' with computed SHA-<hash_algorithm> integrity '<hash>'. The resource has been blocked", which is presented in a browser's console (figure 3-1 and figure 3-2).

The integrity attribute supports three hash algorithms: she256, sha384 and sha512. The integrity attribute value must be prefixed with the hash algorithm, followed by a dash and the hash itself (e.g. sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=). If you want to create integrity values to your <script> elements you can use command line tools like shasum and openssl or an online service like https://www.srihash.org/.

The nonce attribute allows <script> elements with inline JavaScript to be whitelisted for loading into browsers. The issue with inline JavaScript is it can be subject to cross-site scripting (XSS), a security vulnerability where malicious users can either alter the original JavaScript or inject new JavaScript to execute rogue logic, hence the need to whitelist this kind of JavaScript via a "nonce" (a.k.a. number only used once).

In order to use the <script> element's nonce attribute it must be used in conjunction with Content Security Policy (CSP)[6], a more extensive XSS initiative, intended to tell browsers what they can safely load from the perspective of an HTML document owner. All nonce values must be different for all requests made to an HTML document, therefore, it's imperative that nonce values be generated by the server-side language dispatching the HTML document. In accordance with the CSP specification, a nonce value must be at least 128 bits long and be generated by a secure random number generator, for which you can use a variety of libraries depending on the server-side programming language.

A nonce value (e.g. MDg3slR6BAJ21zVYI7N4tw==) should be added to all <script> elements with inline JavaScript (e.g. <script nonce="MDg3slR6BAJ21zVYI7N4tw==">). In addition, this same nonce value must be added as part of the HTML document response, either as part of the HTTP header Content-Security-Policy (e.g. Content-Security-Policy: script-src 'nonce-MDg3slR6BAJ21zVYI7N4tw=='), or if you're not able to add the CSP HTTP header, a <meta> element added to an HTML document's <head> section (e.g. <meta http-equiv="Content-Security-Policy" content="script-src 'nonce-MDg3slR6BAJ21zVYI7N4tw=='"> ). In this manner, when a browser detects CSP as part of the HTML document response and notices a script-src with a nonce- value, it checks that <script> elements with inline JavaScript match the nonce value in CSP, if the nonce values don't match the <script> is blocked from loading.

Note CSP is a broad subject to prevent all kinds of XSS attacks, therefore the CSP HTTP header value or CSP <meta> element value can be much more complex than the nonce value just described. For example, a CSP value could also be script-src 'self' ajax.googleapis.com; to tell browsers to only allow loading of <script> elements with src files from the site origin (i.e. where the HTML document is hosted) or the ajax.googleapis.com domain. In the end, CSP is much more than allowing the loading of inline JavaScript or JavaScript files, it's about whitelisting the loading of any resource (e.g. images, media, frames) that can have XSS consequences.

The crossorigin attribute allows <script> elements to influence a browser's default Cross-Origin Resource Sharing (CORS) behavior. By default, all browsers have a same origin policy to execute JavaScript, which means, as long as all JavaScript interactions take place on the same origin -- url scheme (e.g. https://), site & port -- they're allowed with no oversight. So if a <script> element originates on https://modernjs.com/, the JavaScript logic can interact with any other end point on https://modernjs.com/ with no special requirements for the browser or site, however, if you attempt to make requests to another origin like https://api.modernjs.com (different sub-domain) or https://en.wikipedia.org/w/api.php then you'll face a CORS issue.

In practice, CORS works at the HTTP header level. If a request is made from JavaScript it's accompanied by the Origin HTTP header (e.g. Origin: https://modernjs.com). If the destination is the same as the origin, it's the same origin policy and works as expected. However, if the destination is another origin, the destination has a decision to make, does it allow the request from this other origin to go through ? The response from the destination come through in an another HTTP header Access-Control-Allow-Origin (e.g. Access-Control-Allow-Origin: * telling the requester all origins are allowed; or Access-Control-Allow-Origin: https://modernjs.com telling the requester only the https://modernjs.com origin is allowed). There are more subtleties to this whole process, like dry-runs to first verify access (a.k.a.'preflighted requests'), more specialized HTTP request headers (e.g. Access-Control-Request-Method, Access-Control-Request-Headers) and more specialized HTTP response headers (e.g. Access-Control-Allow-Methods, Access-Control-Allow-Headers), but the gist of CORS is an origin must be granted permissions by a destination if it's not the same origin.

Therefore a key factor to CORS workflows is destinations must grant permissions to different origins, something that can be a hassle if destinations need to grant access to dozens or hundreds of origins via the HTTP header Access-Control-Allow-Origin. An easier way to grant these permissions is through credentials, in this manner a destination site can define a set of credentials (e.g. cookie, Basic Auth), that a browser can store for a given destination and send such credentials as part of the CORS request. The only thing that changes from the prior basic CORS workflow, is the requester must add the credentials for the destination and the destination must respond with the HTTP header Access-Control-Allow-Credentials: true.

Armed with all this CORS context we can get back to the purpose of the <script>'s crossorigin attribute. By default, the <script>'s crossorigin attribute is set to anonymous, which means requests made to destinations that are not the origin (i.e. cross-origin) be made anonomyously. However, another value for a <script>'s crossorigin attribute is use-credentials, which tells a browser that requests made to destinations that are not the origin be accompanied with any available credentials, streamlining access to cross-origin resources.

Finally, the referrerpolicy attribute allows <script> elements to influence a browser's HTTP header Referer sent on HTML document requests. When users move from one HTML document to another, browsers leave a trial of breadcrumbs through the HTTP header Referer indicating what url brought them to a given HTML document. Although the HTTP header Referer can be a great source of information for analytics (e.g. user navigation workflows, third party site referrals), it can also be a source of private information leaks given the amount of data urls can contain (e.g. /profile/user/566056, /user?email=john@gmail.com, /profile/reset/password/sdsfA245G6).

In order to control what type of data gets submitted via the HTTP header Referer you can use a referrer policy. Referrer policies have their own standard[2] which supports eight different values with varying degrees of exposure, from the strictest level of not sending any data in the HTTP header Referer with the no-referrer referrer policy, to the laxest level of sending the referring url verbatim in the HTTP header Referer with the unsafe-url referrer policy.

All browsers have a default referrer policy which is used if no referrer policy is specified by an HTML document. For example, the Google Chrome browser defaults to the referrer policy strict-origin-when-cross-origin, which indicates that the HTTP header Referer value should only include the origin, path and query string for same origin requests, but for cross-origin requests the HTTP header Referer value should only include the origin if the protocol security level is the same (i.e. https to https) and avoid sending any value altogether for cross-origin requests that are less secure destinations (i.e. from https to http).

Although a default referrer policy strict-origin-when-cross-origin is a reasonable default, it's also possible to define a referrer policy on a per HTML document basis, by either using the HTTP header Referrer-Policy in an HTML document response (e.g. Referrer-Policy: no-referrer) or adding a <meta> element to an HTML document's <head> section (e.g. <meta name="referrer" content="no-referrer">). Note the <meta> element to add a referrer policy uses the name="referrer" attribute with the content attribute, since the <meta> element to perform this via HTTP headers with http-equiv="Referrer-Policy" is not supported by some browsers

So the purpose of the script element's referrerpolicy attribute is to define an explicit referrer policy for requests applicable to a single <script> element. For example, you can use a definition like <script referrerpolicy="no-referrer" src="..."> so when a browser fetches the .js file from src there's no data sent in the HTTP header Referer. This in turn provides very granular control of the referrer policy applied to <script> requests, otherwise the referrer policy is determined from either the definition made on an HTML document or a user's browser.

The HTML <noscript> element

In spite of all the effort you can put toward creating JavaScript and defining the appropriate <script> elements with their corresponding attributes, there's always a possibility an end user can disable this functionality. This can be for various reasons, including: very security conscious users that don't want JavaScript to automatically execute on their devices; overly private users that disable advertisements for fear of tracking and with it also disable JavaScript; to users running browsers that don't support JavaScript.

The HTML <noscript> element is the standard way to tell users they need to take certain actions on account of them disabling JavaScript. The HTML <noscript> element is designed to wrap HTML elements and content to be displayed when a user disables JavaScript. Depending on the placement of the <noscript> element in an HTML document its HTML elements can vary. If the <noscript> element is placed in the HTML <head> section of document it can only contain <link>, <style> or <meta> elements, whereas if the <noscript> element is placed in the HTML <body> section of document it can contain a wider array of HTML elements for content (e.g. <h1>, <div>).

Where you place the HTML <noscript> element depends on how an HTML document behaves without JavaScript. If an HTML document is heavily dependent on JavaScript that it's unreadable without it, it's best to place the <noscript> element in the <head> section of document and perform a redirect to another HTML document that doesn't rely on JavaScript with instructions on enabling JavaScript, as illustrated in listing 3-2. If an HTML document is not 100% functional due to a lack of JavaScript but still readable, it's best to place the <noscript> element at the top of the <body> section of a document with a banner telling a user to enable JavaScript to get full functionality, as illustrated in listing 3-3.

Listing 3-2. HTML <noscript> element in <head> with redirect
<html>
  <head>
    <noscript>
      <meta http-equiv="refresh" content="3;url=/please-enable-javascript.html">
    </noscript>
  </head>
<body>
...
</body>
Listing 3-3. HTML <noscript> element in <body> with warning banner
<html>
  <head>
   <style>
      .no-script-banner {
	  position: fixed;
	  top: 0;
	  left: 0;
	  width: 100%;
	  text-align: center;
	  background-color:#FF5252;
	  color:#fff;
	  font-size:large;
	  padding:10px;
      }
    </style>
  </head>
<body>
<noscript>
  <div class="no-script-banner">
    <b>Enabling JavaScript offers a better navigation experience</b>
  </div>
</noscript>
...
</body>

Listing 3-2 illustrates how a <noscript> element inside the <head> section of a document declares a <meta> element to redirect users to a different page. The <meta> element uses the http-equiv attribute with a refresh value to indicate the use of the HTTP header Refresh which performs a redirect operation. The pair of values assigned to the content attribute specify the redirect details: 3 is the number of seconds to wait before making the redirect -- it can be set to 0 for an immediate redirect -- whereas url=/please-enable-javascript.html indicates the url to redirect to, which in this case is an absolute path on the site to the page please-enable-javascript.html.

Listing 3-3 illustrates a <noscript> element inside the <body> section of an HTML document, which creates a <div> element to show a red banner at the top of the page telling users that enabling JavaScript offers a better navigation experience. The <div> element uses the CSS class attribute to give the banner its style through the CSS class <no-script-banner> declared in the <style> element.

The var & function keywords, scope, hoisting and undefined 

JavaScript relies on rather obvious keywords to define variables and functions: var and function, respectively. This means JavaScript .js files tend to lead with statements in the form var language = "JavaScript" or var numbers = [1,2], language = "JavaScript", letters = {"vowels":["a","e","i","o","u"]} -- the last of which is used to declare multiple variables in one line, as a CSV(Comma Separate Value) list of variables -- as well as function name(args) { } statements, where name is the name to reference a function, args is the function input (if any) and curly brackets {} are used to wrap the logic of the function.

The var keyword plays an important role because it influences the scope/visibility of a variable, to be either global or local, as illustrated in listing 3-4.

Listing 3-4. var scope behavior
var letter = "a";
var number = 1; 

function testing() { 
  var number = 2;
  // Check variable values in function scope
  console.log("testing() number: %d", number);
  console.log("testing() letter: %s", letter);
  letter = "e";
}

// Run testing function
testing();
// Check variable values in global scope
console.log("global number: %d", number);
console.log("global letter: %s", letter);

Listing 3-4 begins by declaring two global variables, letter with a value "a" and number with a value of 1. Next, you can see the testing() function has its own local var number = 2 and it also re-assigns the global letter variable to a value of "e".

The testing() function is run first, so the initial output is testing() number: 2 and testing() letter: a. Since the testing() function has its own var number statement, this local scope takes precedence and therefore the output for number is 2. The output for the letter variable inside testing() is taken from the global scope so the result is "a". Finally, notice the last statement in the testing() function updates the global letter variable to "e".

Once the testing() function runs, the two final console.log statements output: global number: 1 and global letter: e. The global number remains unchanged -- since the testing() number variable has its own local definition with var -- however, the global letter variable is now "e" on account of it being updated inside the testing() function.

Now that you have an understanding of the var keyword and its influence on the scope of JavaScript variables, it's also important you understand another JavaScript behavior associated with variables called hoisting.

Hoisting is the act of raising or lifting something and in JavaScript all variables are hoisted to the top of their scope. As a consequence, this means variables are processed before any JavaScript logic is executed and in turn variables declared throughout a script or function are equivalent to declaring them at the top of their scope. Listing 3-5 illustrates the process of JavaScript hoisting.

Listing 3-5. var hoisting behavior
// IF YOU TYPE...                                   // JAVASCRIPT DOES THE FOLLOWING (DUE TO HOISTING)
var letter;                                         var letter;
                                                    var vowels;

console.log(letter);                                console.log(letter);

function testing() {                                function testing() {   
  console.log(number);                                var number;
  var number = 2;                                     console.log(number);
  console.log(number);                                number = 2;
                                                      console.log(number);
}                                                   }

testing();                                          testing();

console.log(vowels);                                console.log(vowels);
var vowels = ["a","e","i","o","u"];                 vowels = ["a","e","i","o","u"]
console.log(vowels);                                console.log(vowels);

random = 8;                                         random = 8;
console.log(random);                                console.log(random);

As you can see, although the vowels variable is declared near the bottom, it's hoisted by JavaScript to the top since it's part of the global scope. In addition, the number variable declared in the middle of the testing function is also hoisted to the top of the function which is its local scope. Under most circumstances JavaScript hoisting is an afterthought, mainly because variables are generally declared at the top of both the global and local scopes anyways to maintain logical ordering.

However, hoisting in can have unintended behaviors if you're not careful. Hoisting moves variable definitions toward the top of their scope before any logic is executed, this means you can potentially access or use variables before they're declared. But in addition, hoisting only raises a variable's definition and not its actual value, as you can see in the example.

For example, although the vowels variable in listing 3-5 is declared near the bottom, it's valid -- albeit sloppy -- to have statements like console.log(vowels) or variable re-assignments (e.g. vowels = []) before the actual var vowels = ["a","e","i","o","u"]; statement, something that's possible because the var vowels definition is hoisted toward the top of the scope allowing vowels operations to run as if the var vowels definition were actually declared at the top.

This means the first console.log(vowels); statement in the example -- before the actual var vowels = ["a","e","i","o","u"]; statement -- outputs undefined, since variable values in an of themselves aren't hoisted. The second console.log(vowels); statement in the example though does output ["a","e","i","o","u"], since it exists after the variable value assignment. This same hoisting behavior occurs on the number variable inside the testing() function (i.e. the first console.log(number); statement outputs undefined, where as the second console.log(number); statement outputs a value of 2.)

As you can see, hoisting can leave you wide open to potential errors. In this case, you can see that even if you attempt to perform an operation on a variable that hasn't been declared, JavaScript doesn't throw an error, instead it happily gives a variable a value of undefined because hoisting lifts variable definitions toward the top of their scope.

All of which takes us to the subtle difference between undeclared and undefined. In JavaScript, the term undeclared is distinctively different than the term undefined, due to how JavaScript assigns variables their values. For example, in listing 3-5 -- once hoisting is applied -- the statement var vowels; declares the vowels variable, but its value is left undefined. Similarly, the var letter; statement has no explicit assignment and thus the letter variable is given the value of undefined.

Once a variable is declared in this manner -- due to hoisting or because it isn't assigned an explicit value -- it becomes available for any operation and JavaScript automatically assigns the undefined primitive data type value to all undefined variables. Because JavaScript data types is a deep topic and I don't want to get sidetracked at this point, the next chapter on JavaScript data types contains more details about the undefined primitive data type and JavaScript data types in general.

Now look at the random = 8; statement in the second to last line in listing 3-5. This last statement also represents a variable, but notice it's missing the var keyword, which makes it an undeclared variable. The issue as you can see in the last line of listing 3-5 console.log(random), JavaScript also happily lets you access an undeclared variable, in this case console.log(random) outputs 8.

So similar to the unintended consequences of accessing hoisted variables, operations on undeclared variables in JavaScript can also lead to unintended consequences. To restrict operations on undeclared variables (i.e. without the var keyword) you can rely on JavaScript strict mode. However, undefined variables are still perfectly valid in JavaScript -- since they're an actual JavaScript data type -- which are the result of knowingly not assigning a value to a variable (e.g.var letter) or a side-effect of a hoisted variable (e.g.var vowels).

First class functions: Function declarations, function expressions, hoisting & undefined 

Functions are a pretty basic concept in all programming languages because they encapsulate logic you can invoke in a single call, but in JavaScript functions play a key role because they're used extensively to support first-class functions, which means functions are values that can be passed around to other functions.

A function declaration is what was shown in the previous example in listing 3-5 and what you're most likely accustomed to see as functions in other languages (e.g.function testing() { }). Function expressions on the other hand, while similar to function declarations, are declared as var statements just as if they were variable values. For example, the function declaration function testing() { } is equivalent to the function expression var testing = function() { }. With an understanding that function declarations can have equivalent function expressions, let's take a closer look at the ability to use JavaScript functions as values.

Listing 3-6. Functions as values
function plainEchoer(message) {
  console.log(message);
  return message;
}

plainEchoer("Hello there from plainEchoer()!");
console.log("Now passing a function as value from plainEchoer()...");
plainEchoer(plainEchoer(plainEchoer(plainEchoer("Hello there from plainEchoer()!"))));


var echoer = function(message) { 
  console.log(message);
  return message;
}

echoer("Hello there from echoer()!");
console.log("Now passing a function as value from echoer()...")
echoer(echoer(echoer(echoer("Hello there from echoer()!"))));

As you can see, plainEchoer() is a function declaration (i.e. it begins with the function keyword) that outputs and returns its input value. The interesting aspect of this function occurs when it's called in the form plainEchoer(plainEchoer(plainEchoer(plainEchoer("Hello there from plainEchoer()!"))));, illustrating JavaScript's first-class function status. Notice it isn't necessary to use a placeholder variable to pass the result of a function to another function, in JavaScript you can simply chain function calls and the functions themselves are treated as values.

The second part of the example illustrates the echoer function expression (i.e. it begins with the var keyword and its value is a function() statement). The echoer function expression also outputs and returns its input value, just like the plainEchoer() function expression. Similarly, you can see it's possible to treat function expressions as first-class functions, by chaining multiple calls in the form echoer(echoer(echoer(echoer("Hello there from echoer()!"))));.

Now that you have an understanding of JavaScript's ability to treat functions as values (a.k.a. first-class functions), you're likely to ask yourself, when should you use a function declaration and when should you use a function expression ? It turns out you can achieve the same results with both function declarations and function expressions, as you've just seen in the previous example in listing 3-6. However, function expressions are often the preferred choice over function declarations, since the former provide added functionality -- via Immediately-invoked function expressions (IIFE), which I'll describe shortly -- in addition to being less error prone in terms of scope/visibility issues.

Now let's take a look at these scope/visibility issues when it comes to using function declarations and function expressions.

Listing 3-7. Hoisting behavior with function declarations and expressions
// IF YOU TYPE...                                     // JAVASCRIPT DOES THE FOLLOWING (DUE TO HOISTING)
console.log(helloDeclaration());                      function helloDeclaration() { 
function helloDeclaration() {                                  return "Hello from a function declaration";
   return "Hello from a function declaration";        } 
   }                                                       
console.log(helloDeclaration());                      var helloExpression;

                                                      console.log(helloDeclaration());
					              //helloDeclaration function is hoisted
                                                      console.log(helloDeclaration());

                                                      // Can't call function expression before its defined
                                                      // Raises TypeError: helloExpression is not a function
console.log(helloExpression());                       console.log(helloExpression());
var helloExpression = function() {                    helloExpression = function() {
  return "Hello from a function expression";              return "Hello from a function expression";
}                                                     }
console.log(helloExpression());                       console.log(helloExpression());

The first thing to note about function declarations is they're hoisted to the top of their scope just like variables. If you look closely at the example in listing 3-7, you can see the helloDeclaration() function is called both before and after the actual function declaration definition. Although in some programming languages calling a function before it's defined generates an error, in JavaScript you can call a function declaration at any point -- so long as it's within scope -- and it's perfectly valid due to the effect of hoisting.

Because function expressions are declared as var statements, they're also subject to variable hoisting as described in the previous section. But similarly, like all other var statements, hoisting only raises a function expression's definition and not its actual value, as you can see in the example. This means the helloExpression() function expression variable is set to undefined at the outset and the function expression only becomes available at the point of the actual function expression definition.

As you can see in the example in listing 3-7, because the first attempt to call helloExpression() is done before the function expression definition, JavaScript generates the error TypeError: helloExpression is not a function because helloExpression() at this point it's undefined. The reason undefined function expressions generate an error is because you can't call/invoke an undefined function, unlike an undefined variable which you can easily use and access -- since it's assigned an undefined primitive data type -- as you learned in the previous section.

As you can see from this example listing 3-7, calling function expressions before they're defined causes an error, something which is a good safety net vs. function declarations which are entirely hoisted (i.e. definition and value) to the top of their scope. This is why I mentioned function expressions are less error prone in terms of scope/visibility issues than function declarations.

Working with text: ' and "  ; plus template literals with backticks ` and ${}  

JavaScript variables can declare text in various ways. It's equally valid to use either single quotes ' or double quotes " to enclose text values. One thing to consider when selecting one option over the other, is if you plan to literally use either symbol in a text value. For example, if a text value will contain an apostrophe as a single quote, it's easier to use double quotes to enclose the text value. Similarly, if a text value will contain a sentence in double quotes, it's easier to use single quotes to enclose the text value. Both of these cases are illustrated in listing 3-8.

Although it reduces readability, it's also possible to use a literal single quote in a value delimited by single quotes or a literal double quote in a value delimited by double quotes. In order for this to work, a literal single quote or literal double quote must be escaped with a backslash \. Notice in listing 3-8, the plain text variables and their equivalent escaped quote ones produce identical values.

Another scenario to consider for JavaScript text variables is values that can span multiple lines. The simplest option is to merge all lines into a single line, however, this can reduce readability. There are two other alternatives to declare text in multiple lines, while keeping individual lines visually separate. One option is to use a backslash \ as the line separator in a single quoted or double quoted value. While a second alternative is to use the + symbol, to concatenate standalone lines with their own single quoted or double quoted delimiters. Listing 3-8 illustrates all three variations for multi-line text values, which is worth pointing out produce identical values.

Listing 3-8. Text with ', ", backslashes and multiple lines
var russel = "Science is what you know. Philosophy is what you don't know";
var descartes = 'I think therefore I am ("Cogito, ergo sum")';

console.log(russel);
console.log(descartes);


var russelBackslash = 'Science is what you know. Philosophy is what you don\'t know';
var descartesBackslash = "I think therefore I am (\"Cogito, ergo sum\")";

console.log(russelBackslash);
console.log(descartesBackslash);

if (russel == russelBackslash) { 
  console.log("russel == russelBackslah");
}
if (descartes == descartesBackslash) { 
  console.log("descartes == descartesBackslah");
}

var lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";

var loremBackslash = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\
 Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\
 Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\
 Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\
";

var loremConcatenation = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." +
" Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." +
" Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur." +
" Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";

console.log(lorem);
console.log(loremBackslash);
console.log(loremConcatenation);

if (lorem == loremBackslash) { 
  console.log("lorem == loremBackslah");
}
if (loremBackslash == loremConcatenation) { 
  console.log("loremBackslash == loremConcatenation");
}

The + symbol that concatenates multiple lines in listing 3-8, can also be used to dynamically create text values based on variables. For example, a variable statement like var name; can be concatenated with another variable like var greeting = "Hello " + name + ", how are you today?", in this manner the greeting variable is dynamically updated with the name value.

As useful as the + symbol is to concatenate fixed text with variables, these type of statements can be difficult to read. In addition, the options to join text spanning multiple lines -- shown in listing 3-8 -- using either +, backslashes \ or merging all lines into a single line, can also be difficult to construct and read. In order to solve these issues, JavaScript supports a third delimiter for text values in the form of a backtick ` .

Variables that use the backtick ` symbol to delimit text, receive the special name of template literals or tagged templates, depending on their use. Unlike the ' and " symbols, text delimited by backticks (a.k.a. backquotes) can span multiple lines without any special syntax and can also contain inline JavaScript (e.g. variables references or plain logic) using the ${} syntax making it easier to incorporate dynamic values. Listing 3-8 illustrates the use of backticks as template literals and tagged template literals (or simply 'tagged templates' for short).

Listing 3-9. Template literals and tagged templates with backticks
var vowels = ["a","e","i","o","u"];
// Template literal
var message = `Vowel letters are ${vowels}`;

console.log(message);

var cost = 5;
var tax = 7.25;

// Template literal
console.log(`Total cost with tax: ${cost * (1 + tax/100)}`);


function worldMessage(message) { 
  // Template literal
  console.log(`${message} World!`)
}

worldMessage("Hello");
worldMessage("Brave new");

// Function for tagged template literal
function thankYouEmail(parts,recipient="Customer",sender="Support") {
  return `${parts[0]}${recipient}${parts[1]}${sender}`;
}

// Define parameters to use in template literal
var recipient, sender;
// Call tagged template literal
var boilerplateEmail = thankYouEmail`Dear ${recipient},

Thank you for contacting us...

Best, 
${sender}`;

console.log(boilerplateEmail);

// Redefine parameters to use in template literal
recipient = "Jen";
sender = "Sally";
// Call tagged template literal
var personalEmail = thankYouEmail`Hi ${recipient},

I read...

Thanks, 
${sender}`;

console.log(personalEmail);

The first example in listing 3-9 uses a backtick on the message reference with the ${vowels} syntax to insert the value of the vowels variable into the text. The second backtick example in listing 3-9 also uses the ${} syntax, but notice how the statement contains multiple references and also uses a mathematical operation (e.g. ${cost * (1 + tax/100)}) to become part of the text. The third example in listing 3-9, demonstrates how it's also possible to use template literals inside functions and use function parameters as part of the substitution process.

The last two examples in listing 3-9 illustrate the use of a tagged template named thankYouEmail. In this case, notice the last two backticked examples use the same template literal syntax (i.e. ${}), but are prefixed with thankYouEmail, that is, the name of the tagged template. Tagged templates work like functions, so the first thing a JavaScript engine does when it encounters the thankYouEmail` ` statement is to look for a function named thankYouEmail.

Functions used for tagged templates need to follow two conventions: Use an input signature in the form ([literal_text_parts], reference_1, reference_2, reference_etc), where literal_text_parts represents an array containing the text of the tagged template separated by references and reference_1, reference_2 & reference_etc are the references (i.e. ${}) declared in the text of the tagged template; in addition, the function must also return a result for the tagged template, using any of the input variables or some other construct.

In this case, you can see the thankYouEmail function declares three arguments: parts, recipient="Customer", sender="Support". In addition, the function also opts to return a result with all the input values with another backticked statement, however, it's perfectly valid for a function backing a tagged template to return anything (e.g. a boolean value based on the input values, a trimmed down version of the input values or some other variation).

In the first tagged template call (i.e. boilerplateEmail), the parts argument is assigned the ["Dear ", "Thank you for contacting us..."] value, since "Dear " is the first part of text in the template, followed by the ${recipient} reference which counts as a separator, followed by "Thank you for contacting us..." as the second part of the text in the template. Since both the recipient and sender references are undefined at the time of invoking the tagged template, the recipient and sender parameters are assigned the default function values "Customer" and "Support", respectively. In the second tagged template call (i.e. personalEmail), the message argument is assigned the ["Hi ", "I read..."] value, while the recipient argument is assigned "Jen" -- since this value is assigned prior to invoking the tagged template and overrides the default function value -- and the sender argument is assigned "Sally" since this value is also assigned prior to invoking the tagged template and also overrides the default function value. As mentioned in the last paragraph, the output for both tagged template calls is a concatenation of all input values, replicating the original template literal.

Text and escape sequences: Backslash \, \x and \u  ; plus \u{}  

You can sometimes find JavaScript text interspersed with escape sequences. An escape sequence is a combination of characters intended to represent something other than their literal value. Escape sequences in JavaScript are prefixed with a backslash \ (a.k.a. escape character) -- to identify them as such -- followed by a predetermined character or set of characters.

In JavaScript you can find several escape sequences: those prefixed with a backslash \ and one character that can be either literal character escapes or control character escapes; those prefixed with \x followed by more characters known as hexadecimal escapes; those prefixed with \u followed by more characters known as unicode escapes; and those prefixed with \u{} with characters inside {} known as unicode code point escapes.

Literal character escape sequences are used for cases when a character holds special meaning in the context of a text definition and needs to be used literally as part of the text definition. In listing 3-8 you already explored a couple of literal character escape sequences -- \' and \" -- needed to incorporate a single quote ' and double quote ", since such characters hold special meaning to delimit text definitions. Other literal character escape sequences can include \\ to literally represent a backslash \ and \` to literally represent a backtick `. Although you can find literal character escape sequences in plain text variables, they're particularly heavily used to define regular expressions -- which define text patterns to match values inside text variables -- in which case you'll find an even larger variety of literal character escape sequences, since regular expressions rely on more special meaning characters (e.g. \[, \] or \? are used to literally match [, ] and ?, respectively, since such characters in regular expressions hold special meaning for regular expression definitions).

Control character escape sequences are used for cases when you want to represent a non-printable character in the context of a text definition. Non-printable characters include such things as: a backspace represented with the escape sequence \b; a new line represented with the escape sequence \n; or a tab represented with the escape sequence \t. Table 3-2 illustrates the full set of JavaScript control character escape sequences, including JavaScript's most common literal character escape sequences.

Table 3-2. JavaScript literal & control character escape sequences
Escape sequence typeEscape sequenceRepresents
Literal character escape sequence\'Literal single quote ', used when ' has special meaning (e.g. text or regular expression that uses single quotes)
\"Literal double quote", used when " has special meaning (e.g. text or regular expression that uses double quotes)
\`Literal backtick `, used when ` has special meaning (e.g. text or regular expression that uses backticks)
\\Literal backslash \, used when \ has special meaning (e.g. text or regular expression that uses a backslash)
\[Literal left bracket [, used when [ has special meaning (e.g. regular expression to match a left bracket)
\]Literal right bracket ], used when ] has special meaning (e.g. regular expression to match a right bracket)
\?Literal question mark ?, used when ? has special meaning (e.g. regular expression to match a question mark)
Control character escape sequence\bBackspace
\fForm feed (page break)
\nLine feed (new line)
\rCarriage return
\tHorizontal tab
\vVertical tab
\0The null character*
*The \0 escape sequence or null character is a control character used by character encodings across many programming languages, which is unrelated to JavaScript's null primitive

Understanding hexadecimal escape sequences which start with \x, unicode escape sequences which start with \u and unicode code point escape sequences which start with \u{} require some lower level background into how JavaScript handles text.

JavaScript engines internally use a character encoding format called UTF-16. In very simle terms, UTF-16 uses a concept called code point to represent characters, where each code point is defined by a number. With UTF-16 being a base 16 system, this means code point numbers use a hexedecimal notation with a 0 to 9 or A to F sequence to represent code points in multiples of 16.

This essentially means all characters can also be represented as code point numbers. Listing 3-10 shows how it's possible to define text using literal characters -- like humans do -- or use either a hexadecimal escape sequence, a unicode escape sequence or a unicode code point escape sequence, more importantly, listing 3-10 also illustrates how all approaches produce the same results.

Listing 3-10. Text escape sequences
// single character with escape sequences
let vowel = "a";
let vowelHex = "\x61";
let vowelUnicode = "\u0061";
let vowelUnicodeCP = "\u{0061}";

console.log("vowel value is: %s", vowel);
console.log("vowelHex value is: %s", vowelHex);
console.log("vowelUnicode value is: %s", vowelUnicode);
console.log("vowelUnicodeCP value is: %s", vowelUnicodeCP);

if ((vowel == vowelHex) && (vowelHex == vowelUnicode) && (vowelUnicode == vowelUnicodeCP)) { 
   console.log("((vowel == vowelHex) && (vowelHex == vowelUnicode) && (vowelUnicode == vowelUnicodeCP))");
}

// multiple characters with escape sequences
let x = "JavaScript";
let xHex = "\x4A\x61\x76\x61\x53\x63\x72\x69\x70\x74";
let xUnicode = "\u004A\u0061\u0076\u0061\u0053\u0063\u0072\u0069\u0070\u0074";
let xUnicodeCP = "\u{004A}\u{0061}\u{0076}\u{0061}\u{0053}\u{0063}\u{0072}\u{0069}\u{0070}\u{0074}";

console.log("x value is: %s", x);
console.log("xHex value is: %s", xHex);
console.log("xUnicode value is: %s", xUnicode);
console.log("xUnicodeCP value is: %s", xUnicodeCP);


if ((x == xHex) && (xHex == xUnicode) && (xUnicode == xUnicodeCP)) { 
   console.log("((x == xHex) && (xHex == xUnicode) && (xUnicode == xUnicodeCP))");
}

Listing 3-10 first illustrates how the "a" character is equivalent to the hexadecimal escape sequence "\x61", the unicode escape sequence \u0061 and the unicode code point escape sequence \u{0061}. The second set of definitions in listing 3-10 also shows how it's possible to represent the longer "JavaScript" text using the three different escape sequence syntax types and obtain the same outcome.

Although you're unlikely to see or use JavaScript escape sequences/code points to encode letters available on keyboards -- like those shown in listing 3-10 -- what you're more likely to see or use is escape sequences for more specialized characters you want to incorporate into JavaScript text. This of course leads us to explore why there are three different escape sequences to achieve the same results, an answer that's rooted in the limitations and base 16 nature of each escape sequence:

Hexadecimal escape sequences always consist of two elements added to its \x prefix. This means the lowest hexadecimal escape sequence is \x00, followed by \x01, moving on to \x0F (16th value), continuing with \x10, \x11 and \x1F (32th value), finishing with \xFD, \xFE and \xFF (256th value). If you add something outside these boundaries you'll either get an error or the interpretation of only the two elements that follow the \x prefix . For example, if you add elements that are not between 0 and 9 or A and F, you'll get the JavaScript error Invalid hexadecimal escape sequence. Similarly, if you add a third element like \xFF1, then the \xFF escape sequence is interpreted followed by the trailing element, so \xFF1 = ÿ1 since the \xFF escape sequence represents the ÿ character.

This two element cap on hexadecimal escape sequences means they're limited to representing 256 characters or code points. Where the first 32 code points (0-31) represent ASCII control characters, like those presented in the second half of table 3-2 that can also be represented with JavaScript control character escape sequences. The following 96 code points (32-127) are ASCII printable characters -- like the code points used in listing 3-10 -- and the remaining 128 code points (128-255) are extended ASCII characters. However, as you saw in listing 3-10, the same code points used by hexadecimal escape sequences are equivalent to those used by unicode escape sequences, with the advantage of unicode escape sequences being capable of supporting more than 256 code points.

Unicode escape sequences always consist of four elements added to its \u prefix. This means the lowest unicode escape sequence is \u0000, followed by \x0001, moving on to \u000F (16th value), continuing with \u0010, \u0011 and \u001F (32th value), finishing with \uFFFD, \uFFFE and \uFFFF (65,536th value). If you add something outside these boundaries you'll either get an error or the interpretation of only the four elements that follow the \u prefix . For example, if you add elements that are not between 0 and 9 or A and F, you'll get the JavaScript error Invalid Unicode escape sequence. Similarly, if you add a fifth element like \uFFFC1, then the \uFFFC escape sequence is interpreted followed by the trailing element, so \uFFFC1 = 1 since the \uFFFF escape sequence represents the character.

This four element cap on unicode escape sequences means they're initially limited to representing 65,536 code points. This 65,536 limit comes from UTF-16 using a single 16-bit element or code unit to store a code point that represents a character. However, it's entirely possible for UTF-16 to use a second 16-bit element or code unit to expand the amount of code points it can support from 65,536 to 1,112,064.

The key to unlocking support from 65,536 to 1,112,064 code points in JavaScript unicode escape sequences prefixed with \u, as well as understanding what brought about the need for JavaScript unicode code point escape sequences prefixed with \u{}, is related to how UTF-16 uses planes through a second 16-bit element or code unit.

In UTF-16, relying on a single 16-bit element or code unit means code points can go from a low of U+0000 to a high of U+FFFF (65,536th value). This set of code points in UTF-16 that use a single code unit are known as the "Basic multilingual plane" (BMP) or plane 0, which are code points representing characters and symbols used in most modern languages. In order to support more code points, UTF-16 makes use of its other 16-bit element or code unit to support sixteen planes (a.k.a. supplementary planes) with each plane capable of supporting a maximum 65,536 code points. In this manner, code points going from a low of U+10000 to a high of U+1FFFF belong to plane 1 or "Supplementary multilingual plane", code points going from a low of U+20000 to a high of U+2FFFF belong to plane 2 or "Supplementary ideographic plane", moving on to code points going from a low of U+F0000 to a high of U+FFFFF that belong to plane 15 and finishing with code points going from a low of U+100000 to a high of U+10FFFF that belong to plane 16.

Although the concept of UTF-16 planes unlocks the ability to move beyond 65,536 code points, this requires support for code points with a fifth or sixth element with ranges that can span from U+10000 to U+10FFFF. This of course creates a problem for JavaScript unicode escape sequences that are capped to representing four element code points. Because JavaScript isn't the only language to potentially be limited from using five or six elements to represent code points, UTF-16 planes are also supported through what are known as surrogate pairs.

Instead of using a single five or six element escape sequence to represent a code point made up of two code units, it's possible to use a pair of four element escape sequences each one representing a code unit to also represent a code point. This pair of four element escape sequences, known as a surrogate pair, is composed of a high-surrogate code unit and a low-surrogate code unit, in this manner, if two four element escape sequences are found back to back and they match an expected high-surrogate/low-surrogate pattern, both escape sequences are interpreted as a single code point. High-surrogate code units are those composed in the escape sequence range U+D8000+DBFF, whereas low-surrogate code unit values are those composed in the escape sequence range U+DC00 to U+DFFF. Therefore, if you see a pair of JavaScript unicode code point escape sequences in the form \uD***\uD*** it's likely you're seeing a surrogate pair representing a single code point.

Due to the reduced readability and complexity that can arise in structuring UTF-16 surrogate pairs, JavaScript ES6 (ES2015) incorporated the unicode code point escape sequence with the \u{} syntax, where the input to {} is capable of directly accepting a five or six element escape sequence with plane information to represent a code point. This makes unicode code point escape sequences the preferred choice for many situations vs. dealing with surrogate pairs in plain unicode escape sequences or dealing with the limitations of hexadecimal escape sequences.

Listing 3-11 illustrates how a unicode code point escape sequence simplifies representing a character vs. using a unicode surrogate pair.

Listing 3-11. Text escape sequences with unicode surrogate pairs and equivalent unicode code point escape sequences
// literal, unicode surrogate pair and unicode code point
let clef = "𝄞";
let clefUnicode = "\uD834\uDD1E";
let clefUnicodeCP = "\u{1D11E}";

console.log("clef value is: %s", clef);
console.log("clefUnicode value is: %s", clefUnicode);
console.log("clefUnicodeCP value is: %s", clefUnicodeCP);

if ((clef == clefUnicode) && (clefUnicode == clefUnicodeCP)) { 
   console.log("((clef == clefUnicode) && (clefUnicode == clefUnicodeCP))");
}

// literal, unicode surrogate pair and unicode code point
let emoji = "😅";
let emojiUnicode = "\uD83D\uDE05";
let emojiUnicodeCP = "\u{1F605}";

console.log("emoji value is: %s", emoji);
console.log("emojiUnicode value is: %s", emojiUnicode);
console.log("emojiUnicodeCP value is: %s", emojiUnicodeCP);

if ((emoji == emojiUnicode) && (emojiUnicode == emojiUnicodeCP)) { 
   console.log("((emoji == emojiUnicode) && (emojiUnicode == emojiUnicodeCP))");
}

Listing 3-11 illustrates a pair of characters that require two code units for their code point representation. The musical clef symbol "𝄞" is first declared literally, followed by its unicode escape sequence with the surrogate pair "\uD834\uDD1E" and the unicode code point escape sequence "\u{1D11E}". Next, the "😅" emoji is declared literally, followed by its unicode escape sequence with the surroage pair "\uD83D\uDE05" and the unicode point sequence "\u{1F605}". Notice the conditionals confirm the three character representations for literal, unicode escape sequence and unicode code point sequence are identical.

Equality symbols ==, !=, === and !==  

By most accounts, JavaScript follows the same equality syntax conventions as many other programming languages. For example, the = symbol is used to assign values, such as var number = 1; which gives the number variable a value of 1. In addition, JavaScript also uses the computer science symbol convention of == and != to perform equality comparisons. For example, if (number == 1) tests if the number variable has a value of 1 and if (number != 1) tests if the number variable does not have a value of 1.

Where JavaScript strays from the conventional path is with its use of the === and !== symbols, also known as strict equality operators. In JavaScript, the standard computer science equality symbols == and != symbols, are known as loose equality operators.

The reason JavaScript supports both double equal and triple equal symbols for comparison operations, is rooted in JavaScript data types. As it turns out, JavaScript has two major groups of data types: primitive and object types. The standard computer science equality symbols == and != symbols are used to perform comparisons by value, irrespective of the underlying data type, in other words, the == and != symbols implicitly perform data type conversions to compare values. Where as the triple equal === and !== symbols are used to perform comparisons without any type conversion, in other words, comparisons made with the === and !== symbols contemplate the underlying JavaScript data type. Listing 3-12 illustrates this behavior.

Listing 3-12. Equality and inequality symbols
if (6 == "6") { // True
 console.log('6 == "6"');
} 

if (["a","e"] == "a,e") { // True 
 console.log('["a","e"] == "a,e"');
}

if (6 === "6") { // False, won't enter condition
 console.log('6 === "6"');
} 

if (6 === 6) { // True
 console.log('6 === 6');
} 

Notice how the first JavaScript comparison in listing 3-12 statement 6 == "6" is true, even though the comparison is being made between a 6 integer value and a quoted "6" that represents a string value. The second JavaScript comparison statement ["a","e"] == "a,e" is also true, even though the first value is an array with two letters and the second value is a string with the same two letters. In both cases, because the statements use a double equal symbol (i.e. == or !=) they ignore the data types for the variables in question and a comparison is made based on value, therefore both 6 == "6" and ["a","e"] == "a,e" evaluate to true.

The third JavaScript statement 6 === "6" uses a strict equality operator and therefore when a comparison is made between a 6 integer value and a quoted "6" the condition evaluates to false. The fourth JavaScript statement 6 === 6 evaluates to true, since both variables represent the same value and data type (integer). This behavior associated with a triple equal symbol (i.e. === or !==) is based on JavaScript performing a comparison based on not just variable values but also the underlying data types.

For the moment, this level of detail on JavaScript strict equality and loose equality operators should be sufficient. The upcoming topic dedicated to JavaScript data types re-addresses the use of JavaScript strict equality and loose equality operators in greater detail, concepts that should be much easier to grasp at that point, once you know about JavaScript's various data types.

Debugging, documenting & logging JavaScript

While the intent of writing any programming language is to solve problems, it's generally inevitable to end up doing some ancillary tasks to support this problem solving process. These tasks can take the form of debugging complex workflows, which in itself can require extensive use of logging statements, as well as the need to document JavaScript logic for either oneself or future maintainers.

The console object

JavaScript or more specifically ECMAScript doesn't define a standard logging syntax, therefore JavaScript uses a logging technique that's historically rooted in browsers. If you look back at figure 3-1 and figure 3-2 you'll see the console facilities included in browsers that work as REPLs. These same console facilities are also intended to view log messages from JavaScript running on browsers.

In order to interact with a browser's console you can use the console object. The console object is globally accessible as a convenience in JavaScript, therefore, it's as simple as typing console in a REPL or introducing a console statement in a .js file to get a list of methods offered by the console object.

Given the prevalent use of the console object in JavaScript -- similar to the HTML <script> element -- there's sufficient agreement between vendors for a console specification[7] to ensure its functionalities work equally across environments. Inclusively, even non-browser JavaScript environments such as Node JS and Deno, which lack a console facility per se like browsers, have gone to the extent of supporting the console object in a similar fashion, albeit such environments log console statements to standard output (e.g. the command line where the environment is run).

In previous examples, you can see the console.log() method is widely used to output generic log messages. But in addition, the console object also supports other logging methods that map closely to the standard logging practice in other programming languages, where different levels are used to output deeply detailed log messages (e.g. debug) or to limit output to severe log messages (e.g. error).

There are two benefits to using these other console logging methods. First, it's possible to configure a browser console to output certain logging levels, this way the logging output can be controlled to only show certain log messages. The second benefit of mixing up the use of console logging methods is the log messages are color formatted depending on their severity (e.g. console.error() in red; console.warn() in yellow), adding a nice touch to visually identify the importance of log messages.

All the console logging methods from the previous list also support a basic C-like sprintf formatting technique -- initially shown in listing 3-4 -- whereby a log message can use format specifiers, which are substituted with arguments passed to a logging method. The following list shows the supported format specifiers with examples:

Tip An alternative to outputting the contents of an object with the %o or %O specifiers, is to wrap a reference with the JSON.stringify() method and use the %s specifier. For example, the output for the statement console.log("Object is %s", JSON.stringify(myobject)) is a string version of the statement console.log("Object is %o", myobject).

In addition to supporting the logging methods with formatting specifiers presented in the previous two lists, the console object also supports another series of methods. Unlike the previous console methods intended to output custom log messages, these other methods are designed for a variety of purposes including: formatting the console itself, shortcuts for logging-type information and methods to measure time.

Tip It's good practice to remove all console statements before JavaScript is released to production. The reason is that even though you can control the console (e.g. logging level) on your own browser, there's no way to control the console on other browsers. So with the exception of severe logging messages -- which can be leveraged between an end user and support team -- there should be a minimum of console object statements in a production release. This removal task of console statements is often delegated to a JavaScript bundler, that through a simple configuration parameter strips such statements in preparation for a production release.
Tip Although the console object's functionalities follow a specification[7], there are non-standard features available in certain environments that can be very useful (e.g. console.profile() & console.profileEnd() are used to record a performance profile, both methods are available in the Google Chrome browser, but not in Node JS).

The debugger keyword  

While the console object offers a great deal of functionality to analyze JavaScript workflows, sometimes a deluge of log messages or time metrics can be of little value if you're not able to do inspections for function/variable values & scope visibility or call stack analysis in certain problem sections. JavaScript supports the debugger keyword that acts as a breakpoint to invoke a debugger, where you're able to move forward or backward on a statement by statement basis to analyze JavaScript statements in greater detail.

It's as simple as adding the standalone debugger; statement to a JavaScript workflow to invoke a debugger, if one is available, otherwise debugger; statements are ignored. Most browsers -- like Google Chrome and Firefox -- include a debugger by default, so when a debugger; statement is reached, the debugger opens in the bottom right where can you control the workflow and get details associated with the breakpoint. If you're using a non-browser JavaScript environment like Node JS, you must explicitly run it in inspect mode for debugger; statements to be evaluated and funneled to the built-in debugging client, as described in the Node JS node inspect command.

Comments with // and /* */  

In case you didn't notice from previous examples, JavaScript supports two kinds of comments: // and /* */

A double slash // defines an in-line comment, with anything after it ignored by a JavaScript engine. This means you can have full line comments if they start with // or you can also have comments after a statement, for example: var x = 1; // This is a comment after var.

Although it's perfectly valid to declare one comment line // after another // to obtain multiple comment lines, wrapping comments that span multiple line inside /* and */ is a best practice. Therefore, anything after the /* symbols is ignored by a JavaScript engine, until the corresponding closing */ symbols are encountered.

Documenting with JSDoc

Although plain comments are a reasonable approach to documenting JavaScript, using a structured format favors documentation that's more readable, as well as the possibility to auto-generate documentation to read it outside a codebase (e.g. as HTML documents). Although unrelated to ECMAScript standards, documenting JavaScript has evolved around JSDoc[8].

JSDoc relies on markup embedded in JavaScript multiline comments. However, there's a minor change needed for multiline comments to be inspected for JSDoc markup, you must add an extra * to the beginning of a comment, that is, /** */. This in turn allows all other multiline comments to be ignored, speeding up the JSDoc generation process by only inspecting blocks that start with /**.

Any text written inside a /** block is considered a description of the construct below it (e.g. a /** */ block above a var statement is considered a variable's description). In addition to the description, a /** block can also include more than 60+ block tags which are prefixed with an @ and intended to describe a construct. For example, there's a @class block tag to mark a construct as a JavaScript class, @param and @returns block tags to specify a function's parameters and return values, as well as block tags like @author and @since to add author and versioning information.

Depending on the block tag, it can be declared in a standalone manner (e.g. @global to declare a global construct), with accompanying text (e.g. @param vowels - An array of vowels ) or with references and accompanying text (e.g. @param {string[]} vowels - An array of vowels). Curly brackets {} in JSDoc are used to link to JavaScript references, (e.g. {string} to indicate a JavaScript string type or {Vowels} to indicate a JavaScript class named Vowels). In addition, certain block tags like @param can also include brackets [] to define optional parameters with default values (e.g. @param {string} [address] - An optional address or @param {string} [address="N/A"] - An optional address with default value).

Once you define JSDoc markup in .js files you can use a variety of tools to generate documentation from JavaScript sources. JSDoc itself comes equipped with a command line tool to generate HTML documentation from a basic template. And for modern environments like Deno, it even comes equipped with its own built-in document generator (e.g. deno doc) that processes JSDoc markup.

Validating and error handling in JavaScript

JavaScript can be a pretty unforgiving language syntax wise, where a single misplaced quote or comma can break an entire application with thousands of statements. Therefore, it's essential to know how to preemptively validate and apply error handling techniques to limit the potential blast radius of JavaScript errors.

The "use strict" statement  

The "use strict" statement is added to the beginning of .js files or functions to enforce strict JavaScript mode[9]. The need for strict JavaScript mode emerged because of the leeway allowed in ECMAScript 5 vs. the requirements of subsequent ECMAScript versions and what were considered innocuous errors. In other words, soon after ECMAScript 5 was formalized, there was a pressing need to fix issues without waiting for a newer ECMAScript version.

Adding "use strict" to the top of a .js file or function, enforces the following:

Let's revisit the example in listing 3-5 which shows undeclared variables to illustrate part of what "use strict" solves. Mistyping variable names can be a common source of errors, where you can have a statement like var color = "blue"; and one thousand lines later you attempt to reassign its value with colour = "red";. Without strict JavaScript mode, you wouldn't be able to easily detect a statement like colour = "red" is an undeclared variable, while you were really trying to update the declared variable var color.

If you declare a "use strict" statement at the top of a .js file or function, it ensures only operations on properly declared variables are allowed. Listing 3-13 shows an updated version of listing 3-5 with "use strict".

Listing 3-13. "use strict" to detect undeclared variables
"use strict";
var letter;

console.log(letter);

function testing() {                                
  console.log(number);
  var number = 2;
  console.log(number);
}      

testing();

console.log(vowels); 
var vowels = ["a","e","i","o","u"];  
console.log(vowels);


random = 8;
console.log(random);

Notice listing 3-13 generates an error in the form '<variable> is not defined'. In this case, the random = 8 statement is the problem because it's an undeclared variable. It's also worth pointing out that hoisting still takes place with "use strict", since it's an inherent behavior of var statements, therefore it's still possible to reference variables ahead of their definitions without error, as it's shown with the various console.log statements in listing 3-13.

Because "use strict" is a pre-ES6 (ES2015) statement, JavaScript constructs that were added in ECMAScript ES6 (ES2015) or later versions are implicitly strict, such is the case for things like JavaScript classes and modules. Therefore, don't expect to encounter the "use strict" statement everywhere, since it's redundant in certain circumstances. Still to this day, the "use strict" statement can be of help to catch undeclared variables like it's shown in listing 3-13, as well as other obscure errors that are otherwise silent (e.g. raise errors when function parameters aren't unique, raise errors when assigning values to references that can't be updated, etc). Where pertinent in the upcoming discussions, I'll reference the "use strict" statement when it influences the behavior of certain constructs.

Tip You can use a command line tool like Node JS syntax checker to validate JavaScript and not have to load things directly in a browser to detect errors in the console.

The try, catch and finally keywords  

In circumstances where you know there's a possibility JavaScript logic can generate errors, it's a best practice to wrap this logic in what's known as a try/catch block. The try and catch keywords are used in conjunction with curly brackets { } to encapsulate logic that should run as an initial block, that in case an error surfaces in any part of the initial block a separate catch block is run, otherwise, if no error is raised in the initial block the separate catch block is ignored. In addition to try/catch blocks, JavaScript also supports try/catch/finally blocks. The purpose of the finally keyword is to define a third block to complement try and catch blocks, so that irrespective of the outcome of said blocks, the instructions in the finally block are always run.

The typical scenario for try/catch and try/catch/finally blocks is in JavaScript logic that involves networking related operation, since network bound logic tends to have a higher degree of uncertainty. Nevertheless, try/catch or try/catch/finally blocks can be used anywhere where it's deemed beneficial, as illustrated in listing 3-14.

Listing 3-14. try/catch/finally blocks for error handling
var vowels = ["a","e","i","o","u"];

try {
  console.log("Vowels is %s", vowels);
   // Access undefined reference
  console.log("Number is %d", number);
} catch (error) {
   console.error("Error is %s : %s", error.name, error.message);
   console.trace(error);
} finally { 
   console.log("Running finally block");
}

Notice listing 3-14 attempts to access the undefined number reference, but since the logic is wrapped in a try/catch block, the workflow isn't completly interrupted and is instead handled inside the catch block. In this case, the catch block uses the optional argument (error) to gain access to the error and its details through the error reference. Once inside the catch block the error details in error are output with a console.error() and console.trace() methods. Next, you can see the finally block represents logic that always runs whether an error is raised or not.

One important aspect of try/catch and try/catch/finally blocks -- shown in listing 3-14 -- is that in case an error ocurrs, the error is placed in a JavaScript error object. Notice in the catch block, the error reference outputs the name and message properties which belong to the JavaScript error object. The next chapter on JavaScript data types contains more details about JavaScript error objects.

The throw keyword  

The throw keyword is used to control the type of error that's generated inside a try/catch block. In listing 3-14 you can see the generated error is a ReferenceError type, which is a built-in error object. But what happens if you want to generate a custom error for a given condition ? Such as, "input must be a number" or "number can't be greater than 100" ? This is where the throw keyword can help, as illustrated in listing 3-15.

Listing 3-15. throw for custom error handling
function testing(number) {      
  try {
    console.log("Number is %d", number);
    if (isNaN(number)) {
      throw "Input must be a number, can't be NaN";
    }
    if (number > 100) {
      throw new Error("Number can't be greater than 100");
    }
    console.log("Number %d passed validation", number);
  } catch (error) { 
    console.error("Error is %s : %s", error.name, error.message);
    console.trace(error);
  }
}

testing();
testing(200);
testing(50);

Listing 3-15 defines a try/catch block inside the body of the testing function to validate its number input. The first call to testing() doesn't provide a value, therefore the number value is undefined. When this happens the function matches the first conditional if (isNaN(number)) that uses the built-in isNan() function to validate if a value is not a number. You can see that when the number value is not a number, the throw "Input must be a number, can't be NaN"; statement is run, which raises an error and turns control to the catch block.

Although the throw "Input must be a number, can't be NaN"; is perfectly valid, notice the output in the catch block shows undefined values for both the error.name and error.message properties. The reason for this behavior in this case, is the error generated with throw is a plain string (i.e. "Input...be NaN") with no name or message properties, therefore the output defaults to undefined values for unknown properties.

The second call to testing() in listing 3-15 provides an input value of 200 which causes that function to match the second conditional if (number > 100) when a value is greater than 100. You can see that when the number value is greater than 100, the new Error("Number can't be greater than 100") statement is run, which raises an error and turns control to the catch block. In this case, because the error generated with throw is an Error object, the output in the catch block for the error.name and error.message properties is appropriately output: Error as the name of the error object and "Number can't be greater than 100" as the message of the error object. Once again to avoid getting sidetracked, I'll refer you to the next chapter on JavaScript data types which contains more details about these type of JavaScript error objects.

Finally, the third call to testing() in listing 3-15 provides an input value of 50 which causes the function to run all the way through to its last statement console.log("Number %d passed validation", number)

Namespaces and block scoping, the problem  

Let's first take a look at the concept of JavaScript namespaces and the idea of having a function with a fairly common name like render(). The problem with using a fairly common JavaScript function name -- or variable name for that matter -- is it has the potential to clash with other equally named ones, due to JavaScript's hoisting behavior shown in listing 3-5 and listing 3-7.

Suppose you found two great JavaScript libraries on the web -- named fantastic.js & wonderful.js -- and you want to leverage them along with your own amazing.js library. After you put together your amazing.js library, you then proceed to make use of all three libraries in the following manner:

Listing 3-16. Naming conflict due to lack of namespace
<script src="fantastic.js"></script>
<script src="wonderful.js"></script>
<script src="amazing.js"></script>
<script>
// Let's call the render() function
// What happens if there's a render() function in fantastic.js, wonderful.js and amazing.js ?

render();

</script>

As you can see in listing 3-16, if all libraries define a render() function, there's no way to call the render() functions in each library, unless they use namespaces. This behavior is due to hoisting, which causes all library functions and variables to be hoisted and become part of the same scope (e.g. three global render() functions, where the last one becomes the definitive one).

This same namespace conflict can happen in .js files that grow extremely large. In a .js file with dozens of lines created by a single person it's easy to keep track of function and variable names, but if a file grows to hundreds of lines or is modified by multiple people, it's easy to inadvertently introduce the same name for a function or variable, which leads to name clashes when hoisting is applied. To avoid these naming conflict behaviors you can use namespaces, which is where immediately-invoked function expressions (IIFE) becomes an auxiliary JavaScript namespace artifact.

Now let's take a look at another JavaScript concept that's influenced by hoisting behavior just like namespaces: block scoping. When I first mentioned JavaScript scoping and hoisting, I described how variables can belong to either a global or local scope (i.e. everything in the top-level gets hoisted to the global scope and everything else gets hoisted to their relative local scope). However, having only a global scope and local/function scope can limit the ability to introduce functionality that requires a block scope.

A block scope is a more granular kind of scope than a local/function scope, which is often required in the context of execution blocks, namely that of if conditionals or for loops. The following example illustrates the lack of block scoping in JavaScript.

Listing 3-17. Lack of block scoping
var primeNumbers = [2,3,5,7,11];

for (var i = 0; i < primeNumbers.length; i++) {
  console.log(primeNumbers[i]);
}

console.log("The value of i is " + i); // i is 5! Leaked from the loop

// Let's try curly brackets
{
  for (var j = 0; j < primeNumbers.length; j++) {
    console.log(primeNumbers[j]);
  } 
}

console.log("The value of j is " + j); // j is still 5! , simple curly brackets still leak from block scope

The first for loop in listing 3-17 uses the i variable as the loop counter. In most programming languages, the norm for anything that happens inside a for loop is to operate within a block scope, that is, statements occurring inside a for loop are confined to the scope of the loop. However, you can see that even though the i variable is declared as part of the loop, it's accessible even after the loop has ended! This happens because the var i declaration is hoisted, making the variable accessible long after the loop has ended. In other words, JavaScript var statements don't support block scope because they're hoisted to their nearest level scope (i.e. global or local/function scope).

The second for loop in listing 3-17 uses the j variable as the loop counter and attempts to use an additional set of curly brackets to define a block scope. However, you can see the additional set of curly brackets around the for loop work to no avail, since the j variable is still leaked beyond the scope of the for block.

This lack of support for block scope in var statements is also where immediately-invoked function expressions (IIFE) become an auxiliary JavaScript block scoping artifact.

Immediately-invoked function expressions (IIFE): Namespaces and block scoping solved, the early years  

The past section introduced two problems with JavaScript hoisting: It lifts functions to the same scope, causing potential namespace conflicts if functions with the same name are accessed from multiple .js files and it also lifts variables to their nearest level scope lacking any type of block scoping. Both problems can be solved with Immediately-invoked function expressions (IIFE), a specialized type of function expression, the last of which was presented in the first class functions: Function declarations, function expressions, hoisting & undefined section.

IIFE can be one of the most awkward pieces of syntax in JavaScript because they can visually appear to be doing nothing, as a function expression wrapped around parenthesis (e.g.(function testing(){ }());. The purpose of this wrapper is to immediately trigger the enclosing function logic (i.e. it isn't called explicitly) and the reason for doing this is almost always to simulate namespaces & block scoping.

Listing 3-18 illustrates an updated version of listing 3-16 making use of IIFE to support namespaces.

Listing 3-18. Namespaces with IIFE prevent naming conflicts
// Contents of fantastic.js
var fantasticNS = {};

(function(namespace) { 
    namespace.render = function() { console.log("Hello from fantasticNS.render()!") };
})(fantasticNS);

// Contents of wonderful.js 
var wonderfulNS = {};

(function() {
    this.render =  function() {  console.log("Hello from wonderfulNS.render()!") };
}).apply(wonderfulNS);


// Contents of amazing.js
var amazingNS = {};
(function() {
  var privateRender = function() {  console.log("Hello from amazingNS.render()!") };
  this.render = function() { privateRender() };
}).call(amazingNS);


// Let's call the render() function for each of the different libraries
fantasticNS.render();
wonderfulNS.render();
amazingNS.privateRenderer; // This does nothing because privateRenderer is local to IIFE block
amazingNS.render();

The contents of fantastic.js in listing 3-18 first show the fantasticNS variable is assigned to an empty object {}, that will serve as the namespace reference -- for the moment, bear with me on the talk about JavaScript objects, the next chapters on JavaScript data types and JavaScript object-orientated and prototype-based programming contain more details about this topic. Next, notice the IIFE syntax (function() {} ); which wraps the bulk of the logic, including a render function. In this case, the IIFE is given the fantasticNS reference, to permit access to the functions inside the IIFE.

The contents of wonderful.js in listing 3-18 use a slightly different syntax variation to incorporate a namespace. Similarly, the wonderfulJS variable is assigned to an empty object {} that will serve as the namespace reference. However, notice the IIFE syntax now uses (function() { }).apply() to wrap the bulk of the logic, including a render function. In this case, the apply()[10] function allows IIFE to modify the function's this reference to another value -- in this case to wonderfulNS -- and forgo using arguments in its definition (e.g.(function(namespace) { }) like the IIFE for fantastic.js).

The contents of amazing.js in listing 3-18 use the same namespace technique as wonderful.js, but instead use the call()[11] function to produce the same outcome. In addition, notice the body of the amazing IIFE contains the statement var privateRender = function() {}. By using var, the privateRender function expression becomes confined to the IIFE and inaccessible outside of its scope -- which is called a block scope -- even though the IIFE does make use of namespace to access other functions like render.

Finally in listing 3-18, you can see the calls to the various render() functions relying on the different namespaces. In addition, you can also see that attempting to access amazingNS.privateRenderer doesn't produce any output, because privateRenderer is a var confined to the scope of the IIFE.

Now that you've learned how IIFE support namespaces, let's take a look at how IIFE can support block scoping. Listing 3-19 illustrates an updated version of listing 3-17 making use of IIFE to support block scoping.

Listing 3-19. Block scoping with IIFE
var primeNumbers = [2,3,5,7,11];

for (var i = 0; i < primeNumbers.length; i++) {
  console.log(primeNumbers[i]);
}

console.log("The value of i is " + i); // i is 5! Leaked from the loop

// Let's try curly brackets
{
  for (var j = 0; j < primeNumbers.length; j++) {
    console.log(primeNumbers[j]);
  } 
}

console.log("The value of j is " + j); // j is still 5! , simple curly brackets still leak from block scope

// Let's try an IIFE
(function() { 
  for (var k = 0; k < primeNumbers.length; k++) {
    console.log(primeNumbers[k]);
  } 
})();

console.log("The value of k is " + k); // k is not defined ReferenceError, k is out of scope by using IIFE

The first two examples in listing 3-19 are the same ones presented in listing 3-17 that leak a loop's variables i and j beyond the scope of the for block.

The third for loop in listing 3-19 illustrates how to obtain block scoping by means of an IIFE. You can see that by wrapping the for loop in plain IIFE syntax (i.e. (function() { })(), with no namespace), the k variable used by the for loop remains contained to the block scope, with any attempt to access it outside the loop generating a reference error. It's worth pointing out, this IIFE block scope behavior is the same one illustrated in the previous IIFE example in listing 3-18 when a call is made to amazingNS.privateRenderer, the privateRenderer is inaccessible because it's contained to the block scope of the IIFE.

The key takeaways you should get from these IIFE examples and JavaScript functions in general is:

Tip IIFE can also be declared with the void operator. See the void keyword and listing 3-31 for additional details.

Object assignments and object notation: Namespaces solved another way, the early years  

Even though IIFE are a common practice to create JavaScript namespaces, there are other ways to create them. For the sake of completeness, other variations to support JavaScript namespaces, include: objects assignment and object notation in conjunction with plain function expressions and IIFE.

Listing 3-20. Namespaces with object assignment and object notation
var fantastic = { }

fantastic.render = function() {
       console.log("Hello from fantastic.render()!")
}

var wonderful = {
       render: function() { 
           console.log("Hello from wonderful.render()!");
          }
}

var amazing = (function () {
   return {
          render: function() { 
           console.log("Hello from amazing.render()!");
          }
       }
})();

fantastic.render();
wonderful.render();
amazing.render();

The first example in listing 3-20 creates the empty fantastic object reference and then directly assigns the render function to it. This direct assignment namespace technique is valid due to the flexibility of JavaScript objects, although it can become sloppy because namespace definition can be spread out in multiple places.

The second example consists of creating the wonderful object literal [12] -- which is simply a comma-separated list of name-value pairs wrapped in curly braces. Where as the third example consists of creating the amazing IIFE which returns an object literal like the second example.

Toward the end of the example, you can see how all three different namespace syntax variations are capable of invoking a function named render() without interfering with one another.

Why are techniques to support JavaScript namespaces so convoluted and fragmented ?

At the beginning of Modern JavaScript essentials, I mentioned how JavaScript modules, namespaces and modules types have always been one of the language's achilles heel. Well now you've seen this first hand, exploring how you need to make use of IIFE, object data types and function() statements to obtain namespace behavior.

The reason JavaScript namespaces are so convoluted and fragmented is because in the early years of the language there wasn't any formal support for JavaScript namespaces. ECMASCript 5 simply relied on ad-hoc mechanisms to simulate namespaces like you just learned in the past sections. ECMAScript 6 (ES2015) solved this problem by formally introducing syntax for namespaces that you'll learn shortly.

Lexical scope and the this execution context  

JavaScript is a lexically scoped language, which means its statements are resolved based on where they're declared. And now that you've learned how JavaScript works with a global scope, local scope and can also support block scope, understanding lexical scope -- or static scope as it's also known -- is relatively easy.

JavaScript always attempts to resolve its statements using the scope where they're declared and moves upward in scope until it's able to resolve a statement -- with block scope being the inner most possible scope, followed by local scope and finishing with the global scope. This behavior is illustrated in listing 3-4, where the letter statement can't be resolved within its local testing() function scope and is instead resolved with the global scope statement var letter = "a";.

By the same token, being lexically scoped also means a JavaScript statement declared in an inner scope isn't visible in an outer scope -- unless it's also declared in the outer scope. This behavior is illustrated in listing 3-19, where the k reference can't be resolved in the global scope because it's enclosed and resolved in the local function (block) IIFE scope.

JavaScript scope is closely tied to another concept called the execution context -- or simply context -- that's managed through the this keyword. In listing 3-18 you learned how a function()'s default context can be altered and assigned properties through the this keyword. Although I'll once again talk about JavaScript objects prematurely, it's essential to do so in order to grasp the concept of JavaScript context and closures. If you want to explore JavaScript objects in-depth now, read the next chapters on JavaScript data types and JavaScript object-orientated and prototype-based programming.

In very simple terms, you can assume all JavaScript scopes have their own context or this reference. In reality though, it's not the scope per se that provides access to a context or this reference, it's the underlying object that does. In OOP (Object Orientated Programming) the this keyword is a standard to self-reference an object instance and in JavaScript a function() is itself an object. Therefore, because a function() always creates its own scope and is also an object, every scope has its own context or this reference.

Now let's take a closer look at the JavaScript context or this reference. Not only does every JavaScript scope or technically object have its own default this context that can be modified -- as you learned in listing 3-18 -- the JavaScript context or this reference can also vary depending on the lexical scope. Since all you've seen up to this point are function() objects, listing 3-21 illustrates how the this reference or context can vary depending on the lexical scope of a function() object.

Listing 3-21. Execution context or this varies depending on lexical scope
var message = "Good day!";

var morning = { message: "Good morning!" }

var afternoon = { message: "Good afternoon!" }

var evening = { message: "Good evening!" }

var greeter = function() {
  // this always varies depending on calling context
  var message = "Good night!";
  return this.message;
}

// Call to greeter() 
// uses this.message=(global)message since global is the calling context
console.log(greeter()); // Good day!

// Call to greeter()) modifies calling context with bind()
// modifies this.message=morning.message since morning is the calling context
console.log(greeter.bind(morning)()); // Good morning!
// modifies this.message=afternoon.message since afternoon is the calling context
console.log(greeter.bind(afternoon)()); // Good afternoon!
// modifies this.message=evening.message since evening is the calling context
console.log(greeter.bind(evening)()); // Good evening!

var strictGreeter = function() { 
  "use strict";
  var message = "Good night!";
  // "use strict" forces 'this.message' to be in local scope/context 
  return this.message;  
}

// Call to strictGreeter() which uses "use strict"
// error because this and this.message are undefined in local context
console.log(strictGreeter()); // Cannot read property 'message' of undefined

Let's start with the greeter() function in listing 3-21 which declares a local message variable and returns this.message. Notice how the first call made to the greeter() function outputs "Good day!" due to lexical scoping behavior. Because the greeter() function scope can't resolve the this.message statement -- only a local var message variable which is unrelated to the this context -- lexical scoping makes JavaScript look outward to the next scope to resolve the this.message reference. It's in the next scope -- the global scope -- where JavaScript resolves this.message to "Good day!" due to the var message = "Good day!"; statement on the first line.

Now you may be asking yourself, why is the global var message = "Good day!"; assigned to this.message in the global scope, but the similar var message = "Good night!"; statement ignored for assignment in the this.message function scope ? As it turns out, every JavaScript statement is eventually assigned to its context, so the global var message = "Good day!"; statement ends up being treated as is if it were declared as this.message = "Good day!"; -- a perfectly valid syntax which you can use explicitly by the way. So does this mean there's an implicit global this context reference ? Exactly, all globally scoped statements end up assigned to the top level this context which is called the JavaScript global object.

The JavaScript global object and the this, global and window keywords

In addition to all the JavaScript scoping and context behaviors you've learned up to this point, there's a closely related concept to scoping and context dubbed the JavaScript global object. In listing 3-21, the standard this keyword is used to reference the top level context, however, the JavaScript global object or top level context is also often referenced with the global or window keywords.

Because the JavaScript global object inevitably requires some prior knowledge on JavaScript data types, the use of the this, global and window keywords for top level context is explained in the global object section of the JavaScript data types chapter.

Continuing with the example in listing 3-21, there are three more calls made to the greeter() function, but notice they're all prefixed with the bind()[13] function. The bind() function solves the potential unintended side-effects of JavaScript lexical scoping on the context/this reference, creating a new function with a specific context reference.

Therefore due to the bind() function, the greeter.bind(morning)() call outputs "Good morning!" because the morning reference is composed of var morning = { message: "Good morning!" }, a JavaScript object where morning gets treated as this and this.message is assigned the "Good morning!" value. A similar binding ocurrs for the greeter.bind(afternoon)()) and greeter.bind(evening)() calls which output "Good afternoon!" and "Good evening!", respectively, due to the contents of the afternoon and evening objects.

What is the difference between the apply(), call() and bind() functions that alter the this context ?

The apply() and call() functions used in listing 3-18 are designed to immediately call a function with a modified this context. Both the apply() and call() functions produce identical results, the only difference is their syntax, the call() function accepts an optional list of arguments (e.g..call(this,arg1,arg2)) and the apply() function accepts an optional array of arguments (e.g..apply(this,[arg1,arg2])).

The bind() function used in listing 3-21 is designed to create a new function with a modified this context, in order to have a permanently modified function with another this context vs. constantly modifying the this context on every call like it's done with apply() and call().

Eventhough the bind() function is helpful to unequivocally assign a specific this context to a JavaScript object -- a function() object in listing 3-21 -- it still leaves the default lexically scoped behavior of this. It's one thing for an explicitly declared global JavaScript variable like var letter = "a"; in listing 3-4 to be accessed inside the scope of a function, it's quite another for an implicit global this reference to be accesible inside a function, when it could well be the function's own this reference you're attempting to access.

In the final part of listing 3-21 you can see the strictGreeter() function is almost identical to the greeter() function, except it uses the "use strict"; statement. If you recall from the "use strict" section earlier, by adding the "use strict"; statement to the beginning of a .js file or function, the JavaScript engine enforces a set of stricter syntax rules, one which in this case is generating an error when an attempt is made to access the this reference in a scope where it isn't defined. Toward the end of listing 3-21, you can see that attemtping to call the strictGreeter() function generates an error because this.message isn't defined as part of the function's context and lexical scoping from the this context of the global scope isn't applied because there's no explicit this declaration in the JavaScript global object.

Having this potential of multiple this context references clashing with one another -- because one this is generated for every scope/object -- creates an edge case. What happens if you want to access the this context of an outer scope in an inner scope ? How can you tell them apart if they both use the this reference ? This is solved in one of two ways, you can create a placeholder reference to access one this context with another name (e.g. var that = this) or you can use the apply() or call() functions to use the same this context across different scopes/objects. Listing 3-22 illustrates both approaches.

Listing 3-22. Access the same this context in different scopes/objects
var tableTennis = {}
tableTennis.counter = 0;

tableTennis.play = function() { 
  // 'this' is the tableTennis object in this scope
  // Use placeholder 'that' to access 'this' in inner functions
  var that = this;
  var swing = function() { 
    // 'this' is the local function object in this scope
    // must use 'that' to access outer scope
    that.counter++;
  }
  var ping = function() { 
    // 'this' is the local function object in this scope
    // must use 'that' to access outer scope
    console.log("Ping " + that.counter);
  }
  var pong = function() { 
    // 'this' is the local function object in this scope
    // must use 'that' to access outer scope
    console.log("Pong " + that.counter);
  }
  // Call inner functions in sequence 
  swing();
  ping();
  pong();
}

// Call tableTennis.play() three times 
tableTennis.play();
tableTennis.play();
tableTennis.play();


tableTennis.playApply = function() { 
  // 'this' is the tableTennis object in this scope
  var swing = function() { 
    // Use this local function object, must use apply() on call to change 'this' 
    this.counter++;
  }
  var ping = function() { 
    // Use this local function object, must use apply() on call to change 'this'
    console.log("Ping " + this.counter);
  }
  var pong = function() { 
    // Use this local function object, must use apply() on call to change 'this'
    console.log("Pong " + this.counter);
  }
  // Call inner functions in sequence 
  // with apply() so 'this'(tableTennis object) is visible inside inner functions 
  swing.apply(this);
  ping.apply(this);
  pong.apply(this);

}

// Reset counter 
tableTennis.counter = 0;
// Call tableTennis.playApply() three times
tableTennis.playApply();
tableTennis.playApply();
tableTennis.playApply();


Listing 3-22 begins with the empty tableTennis object and adds the counter and play properties to it. Notice how the play property is a function() which in itself has other function() statatements that are invoked when play is called. Because each function() has its own this context, the play function relies on the var that = this statement to use the that reference in the inner swing(), ping() and pong() functions to get a hold of the play function context.

Careful reassigning this to potentially conflicting keywords

Listing 3-22 uses the var that = this statement to allow access to one this context with the that reference. While it's technically possible to use any reference (e.g. foo, bar) for this purpose, you should be careful in selecting this reference.

For example, you may encounter many online examples that use the var self = this statement to reassign this, while this can appear to be harmless, the window reference (i.e. the JavaScript global object in certain environments) has the self property. And because the window reference is also accessible through this, a statement like var self = this can cause unintended behaviors for window.self.

The other alternative to access an outer scope this context presented in listing 3-22 is through the apply() function -- although technically call() produces the same outcome as apply() and could have been used instead.

In listing 3-22 you can see the playApply property is added to the tableTennis object and that its contents are a similar function to the play() function. However, notice the inner swing(), ping() and pong() functions of the playApply() function use the this reference directly and get the expected value, how is this possible ? By calling each inner function with a modified this context, which corresponds to the outer this context. Notice the calls to each inner function are swing.apply(this);, ping.apply(this); and pong.apply(this); and because this at this point corresponds to the outer this context, it gets used as the this of each inner function.

Closures: Functions that remember their lexical scope  

Now let's explore closures which are better known for their special lexical scope behavior. Under most circumstances, calls made to a function() produce immediate outcomes. That is to say, when a function executes, it runs its logic -- using input arguments or not -- and produces a result: it returns "yes" or "no", it generates an error, it returns data or does whatever else the function is designed to do. However, there can be circumstances when a call made to function has to wait until another action is fulfilled before the function is able to complete its duties.

With JavaScript conceived for browsers and UI (User Interface) type programming, this scenario of calling a function and it being dependant on the result of another action to finish is quite common. For example, functions associated with web page events are always dependent on user actions to finish their execution (e.g. a mouse click to trigger a pop-up or a mouse hover to change an image). Similarly AJAX functions are always dependent on remote services to provide additional input or data to finish their execution.

So what is the big deal about a function having to wait for another action to complete ? The biggest issue is related to how a JavaScript function() operates with lexical scope and how it manages the different scopes when multiple functions are interacting with one another. Listing 3-23 illustrates this behavior with two closure examples.

Listing 3-23. Closures enclose their lexical scope
var countClosure = function() { 
  // local variable 
  var counter = 1;
  // return function to be called after termination 'encloses' lexical scope
  return function () {
    console.log(counter); // var counter is accesible
    counter += 1; // counter can be incremented and persists in outer scope
  }
};

// Call to countClosure function
var mycounter = countClosure();

// countClosure function is done, but still invoked to trigger its return function 
mycounter(); 
mycounter(); 
mycounter(); 



var buttonMaker = function(value) { 
  // local variable assigned input argument
  var name = value;
  // return functions to be called  after termination 'encloses' lexical scope
  return { 
    name: function() { 
      console.log("Button name is " + name); // var name is accesible
    },

    click : function() { 
      console.log("Clicked on " + name); // var name is accesible
    },

    hover : function() { 
      console.log("Hovered over " + name); // var name is accesible
    }
  }
}

// Call to buttonMaker function with different input values
var redBtn = buttonMaker("Red");
var yellowBtn = buttonMaker("Yellow");
var blueBtn = buttonMaker("Blue");
// buttonMaker function is done, but can still return different results


// note the following function calls on buttonMaker have access to the 
// var 'name' in buttonMaker, even though the buttonMaker function is done
// This is because all lexically scoped variables are 'enclosed' with the
// return result, hence the name 'closure'
redBtn.name(); 
redBtn.click();
yellowBtn.click();
blueBtn.click();
redBtn.hover();
yellowBtn.hover();

Listing 3-23 begins with the countClosure() function which returns another function() as its result. This behavior is a more elaborate example of the one in listing 3-6 that illustrated how JavaScript can treat functions as values, except in this case, it returns a yet to be evaluated function!

Notice the countClosure() function declares var counter = 1; and immediately after returns a function that outputs a log statement and increases the counter variable. What's interesting about this syntax is the ability of the return function to access a variable in the outer scope. Notice the var mycounter = countClosure(); statement in listing 3-23 makes a call to countClosure() and assigns the result to mycounter, at this juncture, the countClosure() function is done but its return function has yet to be evaluated.

Next, the mycounter reference is evaluated as a function -- adding () -- which triggers the actual logic inside the return function. This action is performed three times, but more importantly, notice how the logic of the return function is capable of accessing and updating the counter variable in the outer scope, even though the outer scope function (i.e. countClosure()) has apparently finished. The reason JavaScript is able to access outer scope variables when it returns a full-fledged function() is because it 'encloses' the lexical scope and it can therefore gain access to variables in the outside scope, hence the name closure.

The second example in listing 3-23 is the buttonMaker() function which uses more complex closure logic. The buttonMaker() function accepts a single value argument which is then assigned to var name = value;. However, unlike the countClosure() function which returns a single function(), the buttonMaker() function returns an object with multiple functions. Here it's important not to loose sight of the fact the various return functions all make use of the outer scope name variable.

Next, three calls are made to the buttonMaker() function using diffrent arguments, with each result stored in one of three references: redBtn, yellowBtn and blueBtn. At this juncture, the buttonMaker() function is done but its return object functions have yet to be evaluated. Next, calls are made to the various return object functions in each of the three buttonMaker() function references. Here again, the critical aspect of this functionality is that the various return object functions are capable of accessing the name variable in the outer scope because the lexical scope is 'enclosed'.

The let and const keywords: Block scoping solved, the modern years; plus saner access, defaults & temporal dead zones (TDZ) for variables  

In order to support block scoping, you learned in listing 3-19 how to use IIFE syntax (e.g. (function() { })()) to avoid leaking values to outer scopes. With the introduction of ES6 (ES2015), a more natural syntax was introduced to support block scoping.

Listing 3-24 illustrates the use of the let keyword and how it supports block scoping in a more natural manner.

Listing 3-24.let block scoping
let vowels= ["a","e","i","o","u"];

for (let i = 0;i < vowels.length; i++) { 
    console.log(vowels[i]);
}

// Let's see if we can access the i loop variable...
console.log(i); // ReferenceError: i is not defined

Notice the for loop in listing 3-24 uses the i variable as the loop counter, but more importantly, notice how the i variable is preceded by the let keyword vs. the var keyword typically used to declare variables. As you can see in this example, attempting to access the i variable after the loop scope results in an error. This means the let keyword naturally supports block scoping, foregoing the need to use ad-hoc block scope syntax with IIFE, something that also reduces the complexity of creating block-scoped logic (e.g. nested loops).

In addition to supporting block scoping, the let keyword also introduces additional safeguards to avoid problems that can surface with the var keyword, such as duplicate variable definitions and function parameter name clashes, which are illustrated in listing 3-25.

Listing 3-25. let vs. var duplicate and function parameter name clashes
// THIS IS VALID WITH var...                          // THIS IS INVALID WITH let

var number = 1;                                       let number = 1;

// 500 lines later                                    // 500 lines later

// var with same name redeclared is valid             // let with same name redeclared is an error
var number = 2;                                       let number = 2; // Duplicate declaration "number"


var echoer = function(message) {                      let echoer = function(message) {
  // var with argument name is valid                    // Reusing function argument name as let is an error
  // gets overwritten                                   // Duplicate declaration "message"
 var message = "Local message";                         let message = "Local message"; 
  console.log(message);                                     console.log(message);
  return message;                                           return message;
}                                                      }

echoer("Hello there!");                                echoer("Hello there!");

Notice the number reference is declared twice in listing 3-25, if you use a var statement JavaScript doesn't mind the duplicity, however, if you use a let statement JavaScript generates an error. Similarly, notice the echoer() function uses the message reference as both an input argument and a local variable, with a var statement JavaScript ignores this potential pitfall, but with a let statement JavaScript generates an error to warn you of the potential conflict.

The const keyword has similar behaviors to let block-scoped variables, except const values can't be updated. Once a const statement is defined its value is constant and attempting to modify it generates an error, as illustrated in listing 3-26.

Listing 3-26. const vs. let behaviors
// THIS IS VALID WITH let...                  // THIS IS INVALID WITH const

let number = 1;                               const number = 1;


// let can be reassigned                      // const can't be reassigned
number = 2;                                   number = 2; // Assignment to constant variable

// let can be left undefined                  // const can't be left undefined    
let letter;                                   const letter; // Unexpected token

As you can see in listing 3-26, const statements provide an additional safeguard to prevent variables from having their values reassigned or being left as undefined.

Finally, another important aspect of let and const statements has to do with hoisting, undeclared and undefined behaviors. Back in listing 3-5, you learned how var statements get hoisted to the top of their scope, allowing you to inadvertently access them, on top of which JavaScript assigned such statements an undefined primitive data type until the actual statement declarations were reached.

These undesirable behaviors, of accessing variables before they're declared and automatically assigning them an undefined primitive data type before their declarations are reached, aren't permitted in let and const statements. In other words, if you attempt to access let or const statement references before their declaration, you'll get a // Reference error: <let_or_const_name> is not defined. This let and const statement behavior is much safer and more in-line with most programming languages, than JavaScript's var behavior.

For the sake of accuracy, this behavior of let and const statements might lead you to believe that hoisting doesn't apply to these type of statements, but in fact hoisting is applied to let and const declarations. The only thing let and const statements do differently, is they don't allow access to such statements until their declaration is reached and it also doesn't give such statement an automatic undefined value before their declaration is reached.

The ES6 (ES2015) specification confirms this behavior: "The variables are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable's LexicalBinding is evaluated"[14]. In friendlier terms, "the variables are created when their containing Lexical Environment is instantiated" means variables are created as soon as they enter their scope -- which indicates hoisting behavior -- "but may not be accessed in any way until the variable's LexicalBinding is evaluated" means they cannot be accessed until their declaration is reached.

The act or error of attempting to access a let or const variable before its declaration is reached, is said to happen because the variable is in a temporal dead zone or TDZ -- a fancy name indicating the variable has been created (due to hoisting), but is still 'temporarily dead' because its declaration hasn't been reached.

Use let and const over var

Because let and const statements provide block-scoping, in addition to other safeguards just mentioned in the previous section, they should always be preferred over JavaScript var statements.

Although var statements continue to be valid in JavaScript, the only reason you should even consider using var statements is if you want to access a reference globally (i.e. across all scopes), since it's the only option available. Using let and const statements force you to create cleaner and more thought-out JavaScript logic, since statements are restricted to their scope.

The export & import keywords and modules: Namespaces solved, the modern years  

In order to support namespaces, you learned in listing 3-18 and listing 3-20 how to use either IIFE, object assignments or object notation, to allow functions with the same name to interact with another. With the introduction of ES6 (ES2015), a more natural syntax was introduced to support namespaces.

Because namespaces are closely tied to the concept of classifying entities (e.g. variables, functions) into different groups, JavaScript took a similar approach to other programming languages and introduced modules -- as they're also called in Python or packages as they're known in Java. Modules in JavaScript are supported through the export and import keywords, which allow the visibility of entities to be controlled between files and avoid name collisions.

Let's rework the examples from listing 3-18 and listing 3-20 that use namespaces with IIFE, object assignments and object notation, to use JavaScript modules and the export/import keywords.

Listing 3-27. Modules with export and import to support namespaces
// Contents of fantastic.js
export let render = function() {
       console.log("Hello from fantastic.render()!")
}

// Contents of wonderful.js
export let render = function() {
           console.log("Hello from wonderful.render()!");
}

// Contents of amazing.js
export let render = function () {
           console.log("Hello from amazing.render()!");
}


// Contents of script.js
import * as fantastic from './fantastic.js';
import * as wonderful from './wonderful.js';
import * as amazing from './amazing.js';

fantastic.render();
wonderful.render();
amazing.render();

// index.html
<!DOCTYPE html>
<html>
  <body>
    <h1>ES6 modules with <script type="module"> -
                            See console for results</h1>
    <script src="amazing.js" type="module"></script>
    <script src="fantastic.js" type="module"></script>
    <script src="wonderful.js" type="module"></script>
    <script src="script.js" type="module"></script>    
  </body>
</html>

Listing 3-27 illustrates three function expressions named render() placed in three different files, similar to those in listing 3-18. However, notice that in addition to using let statements (vs. var statements), the function expressions in listing 3-27 are preceded by the export keyword. In this case, the export keyword gives each render() function expression the ability to be accessed from other files or modules.

Next, in listing 3-27 you can see the contents of a fourth file named script.js with multiple import statements. In each case, the import keyword is followed by the * symbol or wildcard (to indicate everything), followed by the as keyword and a namespace identifier, followed by the from keyword and the name of the JavaScript file with export statements.

Therefore, the import * as fantastic from './fantastic.js'; statement, indicates to import every exportable entity in the fantastic.js file and make it available under the fantastic namespace, a technique that's also used to import the contents of the wonderful.js and amazing.js files. Finally, toward the end of listing 3-27, the namespaces for each import statement are used to invoke the render() function from each of the three files without threat of name collisions.

Also notice the HTML document in listing 3-27 uses the the HTML <script> element with the type="module" attribute to tell a browser's JavaScript engine to process the contents of the .js file as a module, that is, to expect export/import statements and process them accordingly.

As you can see, the JavaScript export/import module syntax is much simpler and straightforward to use than the namespace techniques in listing 3-18 and listing 3-20. In addition, notice the import statement also lets the user of a module define the namespace vs. the previous techniques in which namespaces are hard-coded by the creator of a file.

The only caveat to using export/import module syntax is it's designed for ES6 (ES2015) JavaScript engines. Although now a days most browsers support JavaScript modules, it can always be the case that some users are stuck on old browsers without this ES6 (2015) feature, in which case this syntax wouldn't work. To solve this issue for users on older browsers, you'd need to create equivalent non-ES6 module logic as a fallback mechanism and add another HTML <script> element with the nomodule attribute, a process that's described in greater detail in table 3-1. HTML <script> element attributes.

With the limitations of the export/import module syntax to run on browsers out of the way, I'll describe an additional syntax variation for the export and import keywords.

Although you can use the export keyword to precede as many let, const, function -- or inclusively var -- statements as needed, just as it's shown in listing-3-27, this can lead to excessive typing. To alleviate this problem, the export keyword can also be used at the end of a file to export multiple constructs in a single line, as illustrated in listing 3-28.

Listing 3-28. Modules with refined and default, export and import
// Contents of amazing.js
const vowels = ["a","e","i","o","u"]; 

let render = function () {
           console.log("Hello from amazing.render()!");
}

function testing() {                                
  console.log("testing() in amazing.js");  
  
}

export {vowels,render,testing};

// Contents of fantastic.js
export let render = function() {
       console.log("Hello from fantastic.render()!")
}

let main = function() { 
    console.log("main() in fantastic"); 
}

export default main;

// Contents of wonderful.js
export let render = function() {
           console.log("Hello from wonderful.render()!");
}

console.log("Log statement in global scope of wonderful.js");



// Contents of script.js
import coolstuff, * as fantastic from './fantastic.js';
import './wonderful.js';
import {vowels as letters, render,testing} from './amazing.js';

fantastic.render();
console.log(letters);
render();
testing();
coolstuff();

// index.html
<!DOCTYPE html>
<html>
  <body>
    <h1>ES6 modules with <script type="module"> -
                            See console for results</h1>
    <script src="script.js" type="module"></script>
  </body>
</html>


Notice how the contents of the amazing.js file in listing 3-28 are a const, let and function statements and toward the end is the export {vowels,render,testing}; declaration that makes all three statements available to anyone using the file or module.

The export keyword can also be used in conjunction with the default keyword to make a function expression a file's default export, as it's shown in the contents of the fantastic.js file. This technique is common to expose a function that executes a key file routine (e.g. main, core), so that whomever uses the file can simply use the import without the need to guess which function to run -- it's worth pointing out that the export keyword with the default keyword can't be used with let, const or var statements, only with function expressions. In listing 3-28 you can see the statement export default main; makes the main expression the default function for the fantastic.js file.

In addition to the import * as <namespace_reference> from '<file_name>'; syntax presented in listing listing-3-27 -- which imports every construct in a file making it accessible under a namespace reference -- the import keyword also has additional syntax variations, illustrated in the script.js file in listing 3-28.

To selectively import certain constructs into a file you can use a list of names wrapped in { }, instead of the * wildcard symbol, listing 3-28 illustrates the use of these import syntax variations. For example, to only import the render function from the fantastic.js file, you can use the syntax import {render} from 'fantastic.js'. To use a different reference name than the name used in the export file (i.e. an alias), you can use the as <reference_name> (e.g. import {render as fantastic_render} from 'fantastic.js' to use fantastic_render as the reference). And to import multiple constructs selectively you can use a CSV-type list (e.g. import {render as fantastic_render, number as fantastic_number} from 'fantastic.js').

Another variation of the import keyword is to only declare it with the name of a file to execute the contents of its global scope, as illustrated in the import './wonderful.js'; illustrated in listing 3-28. For example, the import './wonderful.js' statement executes the global contents of the wonderful.js file and the rest of its contents remain isolated, in this case, it means the console.log() statement in the file -- belonging to the global scope -- is executed. Finally, to complement the export default syntax, the import keyword can be used in conjunction with a reference followed by the from '<file_name>'; syntax. For example, the import coolstuff from './fantastic.js' statement would import whatever function is defined with export default in fantastic.js and make it accessible through the coolstuff reference. It's worth pointing out the import keyword can be combined to import a default function, as well as all other constructs or selective constructs. For example, import coolstuff, * as fantastic from './fantastic.js' -- used in listing 3-28 -- or import coolstuff, {render as fantastic_render} from './fantastic.js'.

Pre-ES6 (ES2015) modules detour: CommonJS, AMD and UMD  

Now that you know how JavaScript works with standard ECMAScript modules, it's important to take a brief detour to talk about non-ECMAScript module alternatives. Like any standard, adding new features to ECMAScript is a time consuming process that requires committees and a more formal approval process. However, in the real-world, things move much faster. As I mentioned in the modern JavaScript essentials modules, namespaces & module types sub-section, the 4 to 6 year time frame between ES5 and ES6's module syntax (i.e. import/export keywords), saw three more variations come to light to support JavaScript modules: CommonJS, AMD and UMD -- some of which are still in use to this day.

In most cases, module loaders and bundlers will save you the need to know these non-ECMAScript module syntax variations. Still, it's important you realize there's more to JavaScript namespaces and modules beyond the official ECMAScript standard.

One of the first use cases for modules in the pre-ES6 age came with the use of JavaScript outside of a browser through Node JS. Unlike browser JavaScript engines that are inherently tied to running something visual, JavaScript engines that operate outside of a browser can demand a much wider range of functionalities (e.g. network operations, database operations, file manipulation operations), similar to other programming language run-times (e.g. Python, Java, PHP). In this sense, the idea of loading dozens or hundreds of different .js files to support these functionalities into the same context or global object became intractable -- due to the potential amount of name clashes and other limitations already described in the past section -- which is why JavaScript got its first module system called CommonJS[15] built to run on top of ES5.

The CommonJS standard uses the exports and require keywords to reference modules. For example, if you encounter the syntax var fantastic = require("fantastic.js"), this is CommonJS syntax telling the JavaScript engine to load the contents of the fantastic.js file and make them available under the fantastic namespace reference. Similarly, if you encounter the exports keyword followed by a value assignment, it's CommonJS syntax telling the JavaScript engine to expose a file's constructs for access in other files. Although strikingly similar to standard JavaScript ES6 module syntax, the exports and require keywords are plain JavaScript references which get their special functionality through the CommonJS library designed to run on an ES5 environment.

Because CommonJS was the first approach to grant JavaScript the ability to run the closest thing to a module system, it's common to still find many JavaScript projects with exports and require statements, specifically those reliant on Node JS which in itself is a CommonJS based system.

Although CommonJS was a step in the right direction for module support, loading dozens or hundreds of .js files represented another problem. As mentioned in the the HTML <script> element section, by default JavaScript loads .js files in a sequential manner, meaning that all files are loaded one after the other before any actual work gets done. This in turn creates a bottleneck -- particularly for visually bound applications -- requiring the loading of modules to take up an application's entire lead up time. This paved the way for a second non-ECMASCript module system named AMD (Asynchronous Module Definition)[16].

What AMD solved was the ability to load JavaScript modules asynchronously (i.e. without blocking), allowing module files to be loaded discretely as they were needed, without hoarding the entire lead up time (e.g. initially loading only essential modules, followed by application work, followed by more module loading as needed, followed by more application work, and so on). To achieve this, AMD relies on the special syntax define(id?, dependencies?, factory);, where id represents a module identifier, dependencies a list of module identifiers that need to be loaded first (in order for the module to work) and factory represents a function to execute for the instantiation of the module.

Similar to CommonJS, what gives the AMD define() syntax its special functionality is the AMD library designed to run on an ES5 environment. For the most part, due to AMD's more limited scope you're less likely to encounter its syntax in JavaScript projects, but if you do, it's generally as part of a larger JavaScript project -- put together by its creators -- to ensure the efficient loading of modules (e.g. the JavaScript DOJO toolkit uses AMD modules [17]).

Because AMD's define syntax complements CommonJS's exports and require syntax, a third module system combining the features of both AMD and CommonJS emerged: UMD (Universal Module Definition)[18]. Fortunately, by the time UMD became a reality, the stadard ECMASCript module system (i.e. import/export keywords) was well underway offering not only what CommonJS, AMD and UMD achieved, but also solving the issue of module circular dependencies. For this reason, UMD is the least likely non-ECMAScript module syntax you're bound to encounter, so I'll leave the conversation on UMD at that.

But as you can see, there are actually three non-ECMAScript JavaScript module variations that filled the void for modules up until standard JavaScript modules came along in ES6 (ES2015). All of which begs the question, how can you easily work with this many JavaScript module and syntax variations in a sane manner ? The process as it turns out is fairly easy with a little bit of transpiling and module loaders and bundlers.

Arrow functions with =>  

The purpose of arrow functions is twofold: to offer a simpler syntax to define functions, as well as to offer a more natural behavior for JavaScript's lexical scope and the this execution context in functions.

An arrow function allows you to substitute the standard function(arguments) { function_body } syntax into the shorter (arguments) => { function_body } syntax. To get accustomed to arrow function syntax, it's easiest to think of the => symbol as the function keyword shifted to the right of a function's arguments. For example, function(x) { return x * x } is equivalent to the arrow function x => { return x * x }, which is also equivalent to the arrow expression x => x * x.

Listing 3-29 illustrates various examples of the fat arrow symbol used as a function expression, immediately-invoked function expression (IIFE), as well as a closure.

Listing 3-29. Arrow functions
// Arrow based expression
let arrowEchoer = message => {console.log(message);return message};

arrowEchoer(arrowEchoer(arrowEchoer(arrowEchoer("Hello there from arrowEchoer()!"))));

// Let's try an arrow based IIFE
let primeNumbers = [2,3,5,7,11];

(() => {
  for (let k = 0; k < primeNumbers.length; k++) {
    console.log(primeNumbers[k]);
  } 
})();


// Arrow closure
let countClosureArrow = (() => { let counter = 1; return () => {console.log(counter); counter += 1;} })();

countClosureArrow();
countClosureArrow();
countClosureArrow();


As you can see in listing 3-29, much of the motiviation behind arrow functions is due to scenarios where the verbose nature of function statements complicates their interpretation. The arrowEchoer() function in listing 3-29 is a simplified version of the echoer() function in listing 3-6; the IIFE with a loop over the primeNumber array in listing 3-29 is a simplified version of the IIFE with a loop in listing 3-19; and the countClosureArrow() closure in listing 3-29 is a simplified version of the closure in listing 3-23

In this sense, arrow functions bring a welcomed relief to additional typing and visual overload.

But in addition, arrow functions also bring some normality to functions in JavaScript in terms of their execution context or this reference. If you recall from the prior section on lexical scope and the this execution context, all JavaScript objects and by extension function declarations which are objects, have access to their own context through the this keyword.

In certain circumstances, having a this reference for every function() can lead to extra workarounds you learned about in listing-3 20 (e.g. using that = this or the call() function to alter a function's default this reference). Arrow functions on the other hand, do not have their own this context, as illustrated in listing 3-30.

Listing 3-30. Arrow function's this reference is from their outer scope
var tableTennis = {}
tableTennis.counter = 0;

tableTennis.playArrow = function() { 
  // 'this' is the tableTennis object in this scope
  let swing = () => { 
    // 'this' in arrow functions is the outer function object in this scope
    // can use 'this' to access outer scope
    this.counter++;
  }
  let ping = () => {
    // 'this' in arrow functions is the outer function object in this scope
    // can use 'this' to access outer scope  
    console.log("Ping " + this.counter);
  }
  var pong = () => { 
    // 'this' in arrow functions is the outer function object in this scope
    // can use 'this' to access outer scope
    console.log("Ping " + this.counter);
  }
  // Call inner functions in sequence 
  swing();
  ping();
  pong();
}

// Call tableTennis.playArrow() three times 
tableTennis.playArrow();
tableTennis.playArrow();
tableTennis.playArrow();

The example in listing 3-30 is an updated version of listing 3-22 that uses arrow functions. In this case, notice the inner swing(), ping() and pong() functions of the playArrow function use the this reference directly and get access to the outer scope this. This is possible because arrow functions don't have their own this reference and instead gain automatic access to the outer scope this reference due to lexical scoping, a behavior which make arrow functions quite popular in scenario like JavaScript callbacks which would otherwise require the workarounds presented in listing 3-22.

The void keyword  

Depending on their purpose, functions in JavaScript can return an explicit value or simply ignore this requirement. Both scenarios have been illustrated in previous examples, like the functions in listing 3-4 and listing 3-5 that don't return any value and the functions in listing 3-6 and listing 3-7 that use the return keyword to return a value.

For functions that don't use a return statement to return a value, JavaScript automatically returns an undefined value, which is a JavaScript primitive data type.

This takes us to the similarly behaved void keyword, which is an operator to evaluate expressions that always returns undefined. In this sense, the void operator behaves like a function that doesn't return a value, because it also evaluates an expression and returns undefined by default. So what's the difference between using one or the other ? None at all, except you can still find some occurrences of the void operator, although most are not widely used or no longer relevant.

One scenario where you can find the void operator is in legacy HTML link elements that don't take users anywhere, such as <a href="javascript:void(0)">Button</a>. Such links are created to keep the mouse hovering effects of an HTML link and simulate a button that does something else besides taking users to another url. So the href attribute of an HTML link can be given a JavaScript handler -- javascript: -- to execute any JavaScript function and the void operator serves as practical mechanism to evaluate the expression inside it -- 0 does nothing, but it could also be a console statement or something else -- and return an undefined value.

However, the HTML link attribute href="javascript:void(0)" is now equivalent to the HTML link attribute href="javascript:undefined". Early JavaScript engines -- pre ES5 -- didn't recognize the undefined value in the latter manner, therefore the void operator had to be used to obtain an undefined value. But with JavaScript engines and their global object now supporting undefined, definitions with a statement like href="javascript:void(0)" can be substituted with the simpler statement href="javascript:undefined".

A second scenario where you can find the void operator is prefixed to functions that operate as Immediately-invoked function expressions (IIFE). Back in listing 3-18 and listing 3-19 you learned how an IIFE is used to deliver namespace support and block scoping using a syntax wrapped around parenthesis (e.g.(function testing(){ }());).

Since the void operator evaluates an expression and returns undefined, it can be used in place of the outer parenthesis ( ) to evaluate the function and not return anything, delivering the same results. Listing 3-31 shows refactored versions of listing 3-18 and listing 3-19 that use IIFE with the void operator.

Listing 3-31. IIFE with void operator
// Contents of fantastic.js
var fantasticNS = {};

void function(namespace) { 
    namespace.render = function() { console.log("Hello from fantasticNS.render()!") };
}(fantasticNS);

// Contents of wonderful.js 
var wonderfulNS = {};

void function() {
    this.render =  function() {  console.log("Hello from wonderfulNS.render()!") };
}.apply(wonderfulNS);


// Contents of amazing.js
var amazingNS = {};
void function() {
  var privateRender = function() {  console.log("Hello from amazingNS.render()!") };
  this.render = function() { privateRender() };
}.call(amazingNS);


// Let's call the render() method for each of the different libraries
fantasticNS.render();
wonderfulNS.render();
amazingNS.privateRenderer; // This does nothing because privateRenderer is local to IIFE block
amazingNS.render();


// IIFE to avoid leaking loop var
var primeNumbers = [2,3,5,7,11];

void function() { 
  for (var k = 0; k < primeNumbers.length; k++) {
    console.log(primeNumbers[k]);
  } 
}();

console.log("The value of k is " + k); // k is not defined ReferenceError, k is out of scope by using IIFE

As you can see in listing 3-31, all the IIFE examples use the void operator instead of parenthesis () and produce the same outcome. Since the appearance of JavaScript modules lessened the need for IIFE altogether, finding syntax like the one in listing 3-31 might not be all that common. If and when you need to declare an IIFE, I'll leave the judgment of which syntax to use to you.

Finally, a third scenario where you can find the void operator is as a wrapper mechanism to ensure arrow functions always return an undefined value. Let's assume you have a function called unreliableReturnValue(), that's subject to returning various data types. If you want to ensure unreliableReturnValue() runs its internal logic for its side-effects and not care about its return value so it always returns undefined, you can use an arrow function in the following form: let safeUnreliable = () => void unreliableReturnValue(). This way, if you call safeUnrealiable() it triggers the execution of unreliableReturnValue() and the void operator ensures the result is always undefined.

Function parameter default values  

Briefly presented in listing 3-9 as part of the template literals topic, JavaScript functions also have the ability to define default values for their parameters. Function parameters in JavaScript (e.g. message in function plainEchoer(message) ) prior to ES6 (ES2015), needed to be provided by the caller of a function or make use of conditional logic in the function itself (e.g. if (message !== undefined)...) to operate with a default value. Now it's possible to use an equality symbol on function parameters to define them with a default value, as illustrated in listing 3-32.

Listing 3-32. Function parameter default values
// Function with default parameter value
function plainEchoer(message="Hello World!") {
  console.log(message);
  return message;
}

plainEchoer();
plainEchoer("Hello there!");


// Arrow based expression with default parameter value 
let arrowEchoer = (message="Good day") => {console.log(message);return message};

arrowEchoer();
arrowEchoer("Good night");

In listing 3-32, you can see default function parameter values are used in case functions are called without parameter values, and for cases where functions are called with parameter values, these take precedence over function parameter default values.

Generators: Functions with * and the yield keyword  

Generators[19] are a special construct used in programming languages to efficiently loop over a set of values. You know you're in the presence of a JavaScript generator when you see a function statement followed by the * symbol accompanied by return values making use of the yield keyword.

Because generators are closely tied to the use of JavaScript for loops, they're explained in the generators and yield expressions section of said chapter. For the moment, just be aware that whenever you see a function* statement and the use of the yield keyword as part of a return statement, it means you're dealing with a generator.

The spread/rest ... operator  

Many programming languages support alternative approaches to achieve the same functionality while reducing typing and increasing readability, a mechanism often dubbed syntactic sugar on account of it being syntax that's sweeter for human consumption. JavaScript introduced the ... operator to simplify cases where data elements required expansion. There are two scenarios where the ... syntax is used, one is called spread operator syntax and the other rest parameter syntax.

The spread operator syntax is used for cases in which an Array reference needs to be expanded, avoiding the need to manually traverse its contents. The rest parameter syntax is used to define an Array parameter to handle an undetermined amount of parameters, avoiding the need to explicitly define parameters. Listing 3-33 illustrates the use of the spread/rest ... operator

Listing 3-33. Spread/rest ... operator
let vowels = ["a","e","i","o","u"];
let numbers = [1,2,3];

let randomLetters = ["c","z",...vowels,"b"];
let randomNumbers = [...numbers,7,9];

console.log(randomLetters);
console.log(randomNumbers);


function alphabet(...letters) {
  console.log(letters);    
  
}      

alphabet("a");
alphabet("a","b","c");
alphabet("m","n","o","p","q","r","s","t","u","v","w","x","y","z");

The first two examples in listing 3-33 illustrate how the spread operator syntax expands the contents of the vowels and numbers variables in the randomLetters and randomNumbers variables. The alphabet function example in listing 3-33 makes use of the rest parameter syntax on the letters parameter, demonstrating how it's possible to call the alphabet function with different amounts of parameters and gain access to each one -- as array elements -- inside the body of the function.

The exponentiation operator **  

Prior to ES7 (ES2016), in order to raise a number to the nth power you had to use the Math data type (e.g. Math.pow(x, y)). With the exponentiation operator, the syntax x**y executes the same operation -- x raised to the power y -- using a simpler syntax.

Asynchronous functions with async and await  

You'll now when you encounter an asynchronous function when it's preceded by the async keyword and the function uses the await keyword inside its body. However, in order to understand the why behind asynchronous functions, it requires you comprehend how JavaScript dealt with asynchronous problems since the language's inception. For this reason, JavaScript asynchronous functions are described in the chapter dedicated to JavaScript asynchronous behavior.

Numeric separators with the underscore _ character  

Numbers in JavaScript can be difficult to discern if they have too many digits to either the left or right of the decimal point. It's possible to use the underscore _ character to separate digits to allow the human eye to easily detect where units, hundreds or thousands start and end in a large number.

The underscore _ character has no influence over how an actual numeric value is stored and just works as a visual crutch. The underscore _ character can be used with both number primitive data types and bigint primitive data types.

Listing 3-34 illustrates a series of numeric value examples that use an underscore character _ as a numeric separator.

Listing 3-34. Numeric separator with the underscore _ character
// number primitive, with literal decimal number
let a = 30_000_000_000;
let b = 30.505_656_951;
// number primitive, with literal exponential number
let c = 30_000e1;
let d = 300_000e-1;
// number primitive, with literal binary number
let e = 0B0000_0000_0000_0000_0000_0000_0001_1110;
let f = 0b0000_0000_0000_0000_0000_0000_0001_1110;
// number primitive, with literal octal number
let g = 0O0_3_6;
let h = 0o0_3_6;
// number primitive, with literal hexadecimal number
let i = 0X1E_AF_12;
let j = 0x1E_BD_37;

console.log("a is %s with value: %s", typeof a, a);
console.log("b is %s with value: %s", typeof b, b);
console.log("c is %s with value: %s", typeof c, c);
console.log("d is %s with value: %s", typeof d, d);
console.log("e is %s with value: %s", typeof e, e);
console.log("f is %s with value: %s", typeof f, f);
console.log("g is %s with value: %s", typeof g, g);
console.log("h is %s with value: %s", typeof h, h);
console.log("i is %s with value: %s", typeof i, i);
console.log("j is %s with value: %s", typeof j, j);

// bigint primitive, with literal number
let k = 100_000_000n;
let l = 9_007_199_254_740_993n; // Number.MAX_SAFE_INTEGER + 2

console.log("k is %s with value: %s", typeof k, k);
console.log("l is %s with value: %s", typeof l, l);

Listing 3-34 first declares ten variables a through j with different number primitives that use the underscore character _ as a numeric separator. Notice the _ separator is valid on all five number primitive syntax variations -- decimal, exponential, binary, octal & hexadecimal -- and can also be used to separate units, hundreds or thousands. The console statements that follow, confirm the underlying value for the number primitives is unaffected by the _ separator.

The last part of listing 3-34 declares two bigint primitives in the k and l variables, that also use the underscore character _ as a numeric separator. Notice how the console statements that follow, also confirm the underlying value for the bigint primitives is unaffected by the _ separator.

  1. https://www.w3.org/TR/SRI/    

  2. https://w3c.github.io/webappsec-referrer-policy/    

  3. https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types    

  4. https://html.spec.whatwg.org/multipage/scripting.html#the-script-element    

  5. https://en.wikipedia.org/wiki/Above_the_fold#In_web_design    

  6. https://www.w3.org/TR/CSP3/    

  7. https://console.spec.whatwg.org/    

  8. https://jsdoc.app/    

  9. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode    

  10. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply    

  11. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call    

  12. https://en.wikipedia.org/wiki/Literal_(computer_programming)#Literals_of_objects    

  13. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind    

  14. https://www.ecma-international.org/ecma-262/6.0/#sec-let-and-const-declarations    

  15. http://www.commonjs.org/    

  16. https://github.com/amdjs/amdjs-api/blob/master/AMD.md    

  17. http://dojotoolkit.org/documentation/tutorials/1.10/modules/    

  18. https://github.com/umdjs/umd    

  19. https://en.wikipedia.org/wiki/Generator_(computer_programming)