JavaScript key concepts & syntax: ES5, ES6, ES2016 (ES7), ES2017 (ES8) and TypeScript

JavaScript like all programming languages has certain concepts you need to understand in order to become proficient in its overall use. Up next, you'll learn about JavaScript's key concepts and their associated syntax, as well as how JavaScript key concepts and syntax have evolved throughout the years, from ECMAScript 5 to ECMAScript 8 (a.k.a. ECMAScript 2017), including TypeScript which is a JavaScript superset.

By key concepts I'm referring to those that apply equally across any JavaScript application, whether it's designed for user interfaces on browsers or server side business logic. Upcoming chapters detail more specific concepts and syntax associated with things like JavaScript data types, JavaScript object-orientated and prototype-based programming, JavaScript for loops and JavaScript asynchronous behavior, but before we get to those deeper topics, it's important you have a firm understanding of JavaScript key concepts and syntax.

ES5: Understanding scope and functions in JavaScript

ES5 is the oldest major JavaScript version for which you're still likely to find entire applications or important libraries written in it, even though the specification dates back to 2009[1] -- or technically 2011[2], see the sidebar 'ECMASCript 5/ECMAScript 5.1' for additional details. However, like many average software technology spec-to-adoption ratios, mainstream ES5 adoption came approximately five years later -- circa 2014-2016 -- so you're bound to find ES5 syntax even in relatively new apps, never mind there's still certain ES5 syntax that's equally used and valid in newer JavaScript versions.

The fundamental syntax and concepts you really need to understand about ES5 almost all revolve around the topics of scope and functions. In addition, there are other broader topics that I'll introduce here, such as equality symbols that are influenced by JavaScript data types, as well as an ad-hoc JavaScript standard used in conjunction with ES5 to streamline the use of future JavaScript versions, in addition to namespaces and closures which are closely tied to function concepts.

ECMASCript 5/ECMAScript 5.1 are functionally equivalent

If you work with ES5, you'll soon realize there are actually two specs: an ECMAScript 5 version[1] and an ECMASCript 5.1 version[2]. Both are functionally equivalent, which means there are no new language or library features between the two, even though the minor 0.1 number increase might lead you to believe otherwise.

Although both ES5 specifications are two years apart, the ECMAScript 5.1 specification simply incorporates a number of editorial and technical corrections over the ECMAScript 5 specification to align it with an ISO edition of the spec[3]. So for practical purposes, when you see the term ES5 it can be interchangeably referring to either ECMAScript 5 or ECMAScript 5.1, similarly if you see the term ES5.1 -- which is rarely used, except in official references -- it can also be interchangeably referring to either ECMAScript 5 and ECMAScript 5.1.

ES5: 'use strict'

At the beginning of our discussion on Modern JavaScript, specifically in languages that produce JavaScript, I mentioned how flawed JavaScript has been since its inception due to how quickly it was put together. With the introduction of ES5, certain flaws became so obvious that JavaScript introduced the 'use strict' statement.

The 'use strict' statement is a literal string added to the beginning of a script (i.e. .js file) or function, to enforce strict JavaScript mode. Adding 'use strict' to a script or function, allows JavaScript to enforce the following:

This means there are actually two types of JavaScript ES5: the standard JavaScript ES5 version according to the specification and the strict JavaScript ES5 variant[4].

For practical purposes, since strict JavaScript ES5 is intended to avoid bad JavaScript practices without being a full-fledged standard, the emergence of newer JavaScript standards like ES6 and ES2016 (ES7) make ES5 strict mode implicit in these newer standards. Therefore, the 'use strict' statement is a remnant of the past, limited to either legacy ES5-based applications or newer JavaScript applications transpiled into JavaScript ES5.

Where pertinent in the upcoming discussions, I'll reference the 'use strict' statement when it influences the behavior of certain constructs, such as the var keyword described next.

ES5: var, scope, hoisting and undefined

By default, JavaScript ES5 relies on the var keyword to define variables. This means JavaScript scripts and functions tend to have statements at the beginning written 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.

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 1-1:

Listing 1-1. ES5: var scope behavior
var letter = 'a';
var number = 1; 

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

testing();
console.log(number);
console.log(letter);

The testing() function in listing 1-1 first declares var number = 2 and outputs the number and letter variables with the console.log statement. When the testing() function is invoked, the number variable is output as 2, the letter variable is output as a and the letter variable is re-assigned to a value of e.

Since the testing() function has its own var number statement, this local scope takes precedence and therefore the output of the console.log() is 2, even though there's also a global number variable with a different value of 1. In addition, notice the testing() function has access to the global letter variable declared toward the top, on top of which the testing() function also modifies the global letter variable with the letter = 'e' statement.

Once the testing() function is invoked in listing 1-1, notice the number and letter variables are output once again with a console.log statement. In this last case, the number variable is output as 1 and the letter variable is output as e. The number variable is output as 1 because it's the global number variable that takes effect, which means the var number = 2 statement/value inside the testing() function is effectively forgotten after the testing() function finishes. However, the letter outputs e because the testing() function re-assigns the original global letter variable which is initially assigned the value of a.

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 1-2 illustrates the process of JavaScript hoisting.

Listing 1-2. ES5: 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 ES5 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 1-2 is declared near the bottom, in ES5 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 1-2 -- 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 1-2. 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 1-2 console.log(random), ES5 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 ES5 strict mode or newer ECMAScript versions. 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).

ES5 strict mode prevents access to undeclared variables

If you enforce ES5 strict mode by adding the 'use strict' statement to an ES5 script or function, it ensures only operations on properly declared variables are allowed. It's worth pointing out that hoisting per-se is still performed in ES5 strict mode and newer ECMAScript versions (e.g. ES6 and ES2016 [ES7]) -- since it's an inherent behavior of var statements -- however, operations on undeclared variables generate errors.

Note 'use strict' generates the error '<variable> is not defined', even though it should technically be undeclared.

ES5: Equality symbols ==, !=, === and !==

By most accounts, JavaScript follows the same 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 1-3 illustrates this behavior.

Listing 1-3. ES5: 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 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.

ES5: Function declarations, function expressions, and immediately-invoked function expressions (IIFE)

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 produce two important behaviors. The first behavior is associated with JavaScript supporting first-class functions[5] -- which means functions are values that can be passed around to other functions. The second behavior has to do with simulating block scoping and namespaces, which represent a major omission in ES5.

While exploring the finer details of these last two function behaviors in JavaScript, I'll introduce you to three JavaScript function syntax variations: function declarations, function expressions and immediately-invoked function expressions (IIFE). Function declarations are what you've seen in the previous examples and what you're most likely accustomed to see as functions in other languages (e.g.function testing() { }), a statement headed by the function keyword, followed by the name of the function, next to a pair of parenthesis -- to declare function parameters, if any -- surrounded by curly brackets containing the logic of the function.

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() { }. IIFE are a special type of function expression wrapped around parenthesis (e.g.(function testing(){ }()); which immediately trigger the enclosing function logic (i.e. they aren't called explicitly) and are used to achieve what in most circumstances is namespace behavior, a process which I'll describe shortly once you understand function expressions.

Now that you have an overview of the different JavaScript function syntax variations and understand function declarations can have equivalent function expressions, let's take a closer look at the ability to use JavaScript functions as values.

Listing 1-4. ES5: 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 1-4. However, function expressions are often the preferred choice over function declarations, since the former provide added functionality -- via 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 1-5. ES5: 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 1-5, 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 1-5, 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 is undefined. The reason undefined function expressions generate an error -- irrespective of using ES5, ES5 'strict mode' or other -- 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 1-5, calling function expressions before they're defined causes an error, something which is a good safeguard 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.

Now let's explore a special kind of function expressions: immediately-invoked function expressions (IIFE). IIFE can be one of the most awkward pieces of syntax for JavaScript newcomers, but IIFE are a particularly common artifact in ES5. The first step to understanding JavaScript IIFE is to understand the purpose of immediatly executing their logic is almost always to simulate namespaces & block scoping.

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.

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 1-6. ES5: 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 1-6, if all libraries define a render() function, there's no way to call the render() functions on 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 use namespaces, which is where function syntax becomes an auxiliary JavaScript namespace artifact. Take a look at the following modified example in listing 1-7, which makes use of namespaces.

Listing 1-7. ES5: 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 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 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()[6] 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 use the same namespace technique as wonderful.js, but instead use the call()[7] 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, 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.

Multiple ways to create JavaScript namespaces

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 in ES5, include: objects assignment and object notation in conjunction with (regular) function expressions and IIFE.

Listing 1-8. ES5: 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 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 [8] -- 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 JavaScript namespaces so convoluted in ES5 ?

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 it's you need to make use of object data types and function() statements to obtain namespace behavior.

So the reason JavaScript namespaces are so convoluted in ES5, is because there isn't any formal support for JavaScript namespaces in ES5. ES5 simply uses ad-hoc mechanisms (i.e. object data types, function statements) to simulate namespaces. ES6 solved this problem by formally introducing syntax for namespaces that you'll learn in an upcoming section.

Now that you've learned how IIFE support namespaces, let's take a look at how IIFE support 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 use -- and lack -- of block scoping in JavaScript ES5.

Listing 1-9. ES5: Use and 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 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 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 for loop in listing 1-9 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 1-9 uses the j variable as the loop counter and attempts to use an additional set of brackets to define a block scope. However, you can see the additional set of brackets around the for loop work to no avail, since the j variable is still leaked beyond the scope of the for block.

The third for loop in the example illustrates the correct way to define a block scope in ES5 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 namespace example in listing 1-7 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 exercises and JavaScript functions in general is:

ES5: Lexical scope, the this execution context and closures

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 1-1, 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 1-9, where the k statement 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 1-7 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 1-7 -- 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 1-10 illustrates how the this reference or context can vary depending on the lexical scope of a function() object.

Listing 1-10. ES5: 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 s 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.

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 1-10 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 1-10, 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 1-10, there are three more calls made to the greeter() function, but notice they're all prefixed with the bind()[9] 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 1-7 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 1-10 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 1-10 -- 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 1-1 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 1-10 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 script (i.e. .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 1-10, 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 1-11 illustrates both approaches.

Listing 1-11. ES5: 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 1-11 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 1-11 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 1-11 is through the apply() function -- although technically call() produces the same outcome as apply() and could have been used instead.

In listing 1-11 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.

Finally, 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 1-12 illustrates this behavior with two closure examples.

Listing 1-12. ES5: 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 1-12 begins with the countClosure() function which returns another function() as its result. This behavior is a more elaborate example of the one in listing 1-4 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 1-12 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 1-12 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'.

And with this we conclude our overview of key concepts and their syntax in ES5. As you can see, the bulk of the concepts in this section revolve around the understanding of scope and functions. The good news is with the introduction of ES6, many of these JavaScript ES5 ad-hoc approaches to working with things like namesapces, block scope and conflicts between this context references, now have new language syntax to deal with them in a more natural manner.

ES6: Scoping and namespaces from ES5 resolved, modules, functions get new syntax and behaviors, plus additional "syntactic sugar"

With ES6, the JavaScript language designers addressed some of the major obstacles required to work with certain aspects of ES5 which you learned in the previous section. The first aspect was related to var definitions, including their scoping implications (e.g. hoisting) and limitations (e.g. no block scope), for which ES6 introduced the let and const keywords. Another aspect that was addressed in ES6 was support for namespaces and modules with the addition of the export and import keywords.

ES6 also added new syntax and features to functions. Things like default function values and a new function syntax with => -- called arrow functions -- to bring relief to this context reference clashes were added in ES6. In addition, support for generators[10] -- a special type of function in computer science -- was also added to JavaScript in ES6, supported by the * symbol in function statements and the yield keyword used as part of a function's logic.

Finally, ES6 also introduced a series of JavaScript syntax constructs to make things easier to read and express, a concept often referred to as "syntactic sugar" in programming languages. Some of these constructs include the ellipsis symbol ... which in JavaScript script is dubbed spread/rest syntax.

ES6: Block scope, let and const

Block scope as you saw in listing 1-9 is not supported naturally in ES5, requiring the somewhat awkward IIFE syntax (e.g. (function() { })()). In case you jumped straight into this section and don't know what an IIFE is and why block scope is problematic in ES5, please review the entire ES5: Function declarations, function expressions and immediately-invoked function expressions (IIFE) sub-section for additional details, this way the following example will make more sense.

Listing 1-13 illustrates the use of the let keyword and how it naturally supports block scoping.

Listing 1-13. ES6: 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 1-13 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 in ES5. 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 ES5's ad-hoc block scope syntax with IIFE, something which 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 1-14.

Listing 1-14. ES6: 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 1-14, 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.

Another addition to ES6 is the inclusion of the const keyword. 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 1-15.

Listing 1-15. ES6: 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 1-15, 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 1-2, 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 JavaScript ES6 does differently with let and const statements, is it doesn'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 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"[11]. 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 the other safeguards I just described in this past section, they should always be preferred over JavaScript var statements.

Although var statements continue to be valid in newer JavaScript versions -- ES6 and ES2016 (ES7) -- the only reason you should even consider using var statements is if you're stuck working in the context of ES5, since it's the only option available. Using let and const statements force you to create cleaner and more thought-out JavaScript logic.

ES6: Modules, namespaces, export and import

ES6 revamped the support of namespaces in JavaScript, which as you might recall in the previous ES5 section -- in listing 1-6, listing 1-7 and listing 1-8 -- required the use of either IIFE, object assignments or object notation.

Because namespaces are closely tied to the concept of classifying entities (e.g. variables, functions) into different groups, JavaScript ES6 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 1-7 and listing 1-8 which illustrated namesapces in ES5, to use ES6 modules and the export/import keywords.

Listing 1-16. ES6: Modules with export and import
// 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>
 <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.20.19/system.js"></script>
  <script>
    SystemJS.config({
    map: {
    'plugin-babel'         : 'https://cdn.rawgit.com/systemjs/plugin-babel/master/plugin-babel.js',
    'systemjs-babel-build' : 'https://cdn.rawgit.com/systemjs/plugin-babel/master/systemjs-babel-browser.js'
    },
    transpiler: 'plugin-babel'
    });
  </script>
  <script>
    SystemJS.import('script.js')
  </script>
  <body>
    <h1>ES6 modules with SystemJS - See console for results</h1>
  </body>

</html>

Listing 1-16 illustrates three function expressions named render() placed in three different files, similar to those in listing 1-7. However, notice that in addition to using let statements (vs. var statements), the function expressions in listing 1-16 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 1-16 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 1-16, the namespaces for each import statement are used to invoke the render() function from each of the three files without threat of name collisions.

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

But as powerful and simple as the ES6 export/import module syntax is, it has one caveat: it can only run on ES6 JavaScript engines. This is particularly critical in the face of fragmented ES6 support in web browsers, as well as non-ECMAScript standards that emerged to support modules in-between ES5 and ES6, all of which require the special syntax and library (e.g. SystemJS) used in the index.html page in listing 1-16. The next section on ES6: Files, modules, scripts, <script> tag, <script type="module"> tag and <module> tag contains more details on the practical aspects of using the ES6 export/import module syntax, but before we get to that, I'll describe additional syntax variations of 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-1-16, 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 1-17.

Listing 1-17. ES6: 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>
  <script type="module" src="script.js"></script>
  <body>
    <h1>ES6 modules with <script type="module"> -
                            See console for results</h1>
  </body>
</html>

Notice how the contents of the amazing.js file in listing 1-17 are a const, let and function statement and toward the end is the export {vowels,render,testing}; declaration which 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 1-17 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-1-16 -- 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 1-17.

To selectively import certain constructs into a file you can use a list of names wrapped in { }, instead of the * wildcard symbol, listing 1-17 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 1-17. 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 1-17 -- or import coolstuff, {render as fantastic_render} from './fantastic.js'.

ES6: Files, modules, scripts, <script> tag, <script type="module"> tag and <module> tag

The JavaScript logic contained in the .js files in listing 1-16 and listing 1-17 is standard ES6 syntax. However, depending on where you execute these files, they can be treated as either modules or scripts. The difference is subtle but an important one, because it's rooted in how ES5 JavaScript engines and ES6 JavaScript engines execute .js files.

In computer science, a script is just a file that contains a set of instructions that are executed one after the other. If you look back at the example in listing-1-6 -- based on ES5 -- you can see each .js file is declared using the HTML <script> tag which is how browsers typically load JavaScript files. When a browser's JavaScript engine encounters these <script> statements, it executes the contents of each script file one after the other in sequential order.

While this ES5 script loading behavior is simple, it's also fraught with problems. First, you must be careful there are no naming conflicts between the constructs in each script (e.g. two functions or three variables with the same name), otherwise due to the effects of hoisting, only a single construct will be visible. In other words, everything gets loaded into the same this context (a.k.a. the global object), unless the script uses an ad-hoc ES5 namespace technique like the ones described earlier. Second, in the event one script needs logic or data from another script, you as a human must ensure the script loading order, because a JavaScript engine will blindly execute the contents of each script file one after the other, without helping you detect dependencies. And to give you a third scenario, how would you solve inter-dependencies between scripts (a.k.a. circular dependencies) ? That is, in what order would you declare script A if it needs something from script B, but script B also needs from script A ? These are just a few of the many issues of loading JavaScript files as scripts.

Because of this legacy design in ES5 of treating JavaScript files as scripts, it's not possible to use the same basic HTML <script> tag to execute JavaScript ES6 modules in browsers. In order for browsers to interpret ES6 modules you must add the type="module" attribute to the HTML <script> tag, as illustrated in the index.html page in listing 1-17 (e.g. <script type="module" src="script.js"></script>). In this case, you can see the contents of the script.js file module contain standard ES6 module syntax with import statements, which themselves references other file modules with export statements.

But as wonderful as the <script type="module"></script> syntax is, there's one major catch: it's not supported by all browsers. For example, only browsers such as Safari 10.1, Chrome 61 and Microsoft Edge 16 -- or higher versions -- support this <script type="module"></script> syntax. And considering many of these browsers versions are circa 2017, it's simply not feasible to rely on the <script type="module"></script> syntax without using a pollyfill to deliver these kind of instructions to users with browsers that won't execute ES6 modules.

Finally, for the sake of completeness, you're likely to find online references to an HTML tag named <module>, specifically designed to load ES6 modules in browsers vs. the <script> tag used in conjunction with the type="module" attribute. However, the HTML tag <module> as far as major browsers are concerned does not exist, appearing to have only been a suggestion floated around by JavaScript spec and community members.

Pre-ES6 modules detour: CommonJS, AMD and UMD; plus a first look at transpiling and bundlers

Now that you know how both the ES5 and ES6 standards deal with JavaScript modules, it's important to take a brief detour to talk about non-ECMAScript module alternatives. Because ES5 and ES6 are standards, like any standard, adding new features required committees and a more formal approval process. However, in the real-world, things moved 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 ES5 and ES6 standards.

One of the first use cases for modules in the age of ES5 came with the use of JavaScript on the server through Node.js. Unlike browser JavaScript engines which are inherently tied to running something visual, JavaScript engines bound to a server or workstation 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 scripts 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[12] 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 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 in ES5, it isn't uncommon to still find references of the exports and require keywords in certain JavaScript projects. The only thing you must ensure in such cases, is that the CommonJS library be loaded alongside such files in order for these special keywords to behave as expected.

Although CommonJS was a step in the right direction for module support in ES5, loading dozens or hundreds of scripts represented another problem. As I mentioned in the previous section, JavaScript inherently loads scripts in a sequential manner, meaning that all files are loaded one after the other before any actual work gets done. This in turn created 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 ES5 module system named AMD (Asynchronous Module Definition)[13].

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 [14])

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)[15]. Fortunately, by the time UMD became a reality, the ES6 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 ES6 came along. 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.

If you look closely again at the example in listing 1-16 and the index.html page, you'll notice the first thing that's loaded on the page is a JavaScript library named system.js. SystemJS[16] is a dynamic ES module loader, which means it's a utility that allows any type of JavaScript module to be dynamically loaded into the same environment! So with SystemJS, you solve the problem of dealing with multiple module syntax variations.

Still, the way SystemJS solves this thorny problem requires a transpiler to convert the various JavaScript module syntax variations into a homogeneous structure. This is where the second HTML <script> tag in the index.html page from listing 1-16 comes into play. The block section SystemJS.config represents the SystemJS configuration, in it you can see the transpiler option with a value set to plugin-babel, which in the map option points to a JavaScript library named plugin-babel.js. In this case, Babel[17] represents one of the most popular JavaScript transpilers.

Once SystemJS is loaded and configured with a transpiler, you can see the third HTML <script> tag uses the SystemJS syntax SystemJS.import('script.js') to load the script.js module. In this particular case, script.js represents an ES6 module, that in itself uses ES6 module module syntax to reference other ES6 modules in listing 1-16. However, with SystemJS.import statements it would have been equally valid to import CommonJS modules in combination with ES6 modules or some other variation.

You should avoid transpilation and any other heavyweight operation -- like loading SystemJS -- on browsers

Although the example in listing 1-16 showcases the use of SystemJS, this particular type of loading procedure should be avoided on browsers. The issue with the index.html page in listing 1-16, is that it forces browsers to download relatively large libraries -- SystemJS and Babel -- and then requires browsers to perform a transpilation procedure. This is not only time consuming, but also wasteful.

Transpilation and other heavyweight operations should always be done as part of a build process for JavaScript applications, to reduce the burden on browsers and end users. So ideally, the creator of a JavaScript application should always be charged with transpilation and other heavyweight operations -- so it's performed only once --- instead of being delegated dozens or hundreds of times to n users.

ES6: Arrow functions with => and default function values

Enhancing ES5's function declarations, function expressions and immediately-invoked function expressions (IIFE) as well as lexical scope, the this execution context and closures, ES6 incorporated the short-handed syntax called arrow functions (a.k.a. fat arrow 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 1-18 illustrates various examples of the fat arrow symbol used as a function expression, immediately-invoked function expression (IIFE), as well as a closure.

Listing 1-18. ES6: 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 1-18, much of the motiviation behind arrow functions in ES6 is due to scenarios where the verbose nature of function statements complicates their interpretation. The arrowEchoer() function in listing 1-18 is a simplified version of the echoer() function in listing 1-4; the IIFE with a loop over the primeNumber array in listing 1-18 is a simplified version of the IIFE with a loop in listing 1-9; and the countClosureArrow() closure in listing 1-18 is a simplified version of the closure in listing 1-12

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, the this execution context and closures, 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-1-11 (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 1-19.

Listing 1-19. ES6: 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 1-19 is an updated version of listing-1-11 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-1-11.

In addition to the fat arrow symbol, functions in ES6 also gained the ability to define default parameters. Function parameter values in JavaScript (e.g. message in function plainEchoer(message) ) up until ES6, needed to be provided by the caller of the function or make use of conditional logic in the function itself (e.g. if (message !== undefined)...) to operate with a default value. In is ES6, it became possible to use an equality symbol on function parameters to define them with a default value, as illustrated in listing 1-20.

Listing 1-20. ES6: Default function parameters
// 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 1-20, 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 default function parameter values.

ES6: Generators - Functions with * and yield

Generators[10] are a special construct used in programming languages to efficiently loop over a set of values. In ES6, generators became an official addition to JavaScript with function statements 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 the * and yield syntax, it means you're dealing with a generator.

ES6: Spread/rest ... operator, template literals and tagged templates with backquotes ` `

Many programming languages support alternative approaches to achieving the same functionality to reduce typing and increase readability, a mechanism often dubbed 'syntactic sugar' on account of it being syntax that's 'sweeter' for human consumption. ES6 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 1-21 illustrates the use of the spread/rest ... operator

Listing 1-21. ES6: 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 1-21 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 1-21 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.

Another JavaScript aspect that received syntactic sugar in ES6 were strings. String manipulation and substitution in JavaScript ES5 required string concatenation operations, which were not only innfecient but also cumbersome and difficult to read. ES6 introduced the concept of template literals and tagged templates through the use of ` ` backquotes.

Unlike standard JavaScript strings delimited by ' or ", strings delimited by backquotes (a.k.a. backticks) can contain inline references or inclusively variable operations using the special ${} syntax. Listing 1-22 illustrates the use of backquoted strings as template literals and tagged template literals (or simply 'tagged templates' for short).

Listing 1-22. ES6: Spread/rest ... operator
let vowels = ['a','e','i','o','u'];
// Template literal
let message = `Vowel letters are ${vowels}`;

console.log(message);

let cost = 5;
const 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(message,recipient="Customer",sender="Support") {
  return message[0] + recipient + message[1] + sender;
}

// Define parameters to use in template literal
let recipient, sender;
// Call tagged template literal
let 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
let personalEmail = thankYouEmail`Hi ${recipient},

I read...

Thanks, 
${sender}`;

console.log(personalEmail);

The first example in listing 1-22 assigns a backquote string to the message reference and uses the ${vowels} syntax to insert the value of the vowels reference into the string. The second backquote string example in listing 1-22 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 string. The third example in listing 1-22, demonstrates how it's also possible to use template literals inside functions and use function parameters as part of the string substitution process.

The last two examples in listing 1-22 illustrate the use of a tagged template named thankYouEmail. In this case, notice the last two backquoted strings use the same template literal syntax (i.e. ${}), but are preceded with the thankYouEmail reference. 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. Like any other function, you can see the thankYouEmail function that backs the tagged template is not restricted to return any specific output, in this case, it returns a string composed of the function arguments as the result of the evaluated tagged template.

However, functions that back tagged templates are required to follow a signature in the form: ([literal_string_parts],substitution_string_ref1, substitution_string_ref2). In this case, you can see the thankYouEmail function declares three arguments: message, recipient="Customer", sender="Support". In the first tagged template call (i.e. boilerplateEmail, the message argument is assigned the ['Dear ', 'Thank you for contacting us...'] value, where as the recipient and sender parameters are assigned the default function values, since both the recipient and sender references are undefined at the time of invoking the tagged template. In the second tagged template call (i.e. personalEmail), the message argument is assigned the ['Hi ', 'I read...'] value, the recipient argument is assigned 'Jen' -- since this value is assigned prior to invoking the tagged template -- and the sender argument is assigned 'Sally' since this value is also assigned prior to invoking the tagged template.

ES2016 (ES7): A little "syntactic sugar", nothing more

Following the major ES6 syntax additions presented in the previous section, ES2016[18] or ES7 -- where 2016 refers to the release year and 7 references the amount of ECMAScript editions -- proved to be anticlimactic.

The only new syntax introduced in ES2016 was the exponentiation operator **. Prior to ES2016, 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.

ES2017 (ES8): Asynchronous functions with async and await

The major feature and syntax novelty introduced in ES2017[19] or ES8 were asynchronous functions. 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.

Understanding the addition of asynchronous functions requires that you comprehend how JavaScript dealt with asynchronous problems since the language's inception and the ECMAScript standards leading up to ES2017. For this reason, JavaScript asynchronous functions are described in the chapter dedicated to JavaScript asynchronous behavior.

TypeScript: The JavaScript that should have been

TypeScript is a JavaScript superset, which is an important fact for two reasons: it means all JavaScript is TypeScript and it also means all TypeScript can be converted -- technically transpiled is the appropriate technical term -- into JavaScript. Having such a close relationship, TypeScript is actually very similar to JavaScript, but it does have some syntax differences I'll address shortly.

So why was TypeScript invented if it's so similar to JavaScript ? The simple answer has to do with software development stability and scalability when you're one of the largest creators of software in the world. Microsoft introduced TypeScript in October 2012, which was when ECMAScript 5.1 was barely one year old, ECMAScript 6 was still years away from being formalized and JavaScript use on server-side applications was well underway.

For a company the size of Microsoft, TypeScript was the pragmatic answer to dealing with JavaScript development. On the one hand, JavaScript's dominance on the browser and inroads into server-side applications, made ignoring JavaScript a losing proposition. On the other hand, JavaScript's shortcomings -- as you've already learned in the previous sections (e.g. scoping, namespaces, modules) -- also made working with JavaScript on a large scale an equally losing proposition.

TypeScript's purpose from the outset has been to provide a solid programming language foundation -- similar to other enterprise grade languages like C# and Java -- while at the same time making it work seamlessly with JavaScript engines. In this sense, TypeScript is a means to an end, applications are developed in TypeScript but are eventually transpiled into JavaScript -- ECMAScript 5, ECMAScript 6 -- in order to run on regular JavaScript engines, just as if the applications were developed in JavaScript from the outset.

Can browsers run TypeScript ? Yes, but with a lot of work

For the most part, browsers are only equipped with JavaScript engines, which means that in order to execute any other type of programming language they require some type of plug-in. At the time of this writing, there are no known TypeScript engines known to run on browsers, although there are transpilers capable of converting TypeScript into JavaScript directly on browsers.

Unfortunately, the act of transpiling and adding a TypeScript transpiler to an application delivered to a browser is a heavyweight operation that should be avoided. This is a similar scenario to the one presented in listing 1-16 which relied on a transpiler to run ES6 modules directly on browsers. While you could equally rely on a transpiler to run TypeScript on all browsers, as it was mentioned then, you should avoid transpilation and any other heavyweight operation -- like loading SystemJS -- on browsers.

Can anything run TypeScript natively ?

Technically and as it was mentioned earlier, TypeScript is a means to an end that must always be transpiled into JavaScript.

However, this doesn't mean there hasn't been work done to run TypeScript in a more natural way. Deno is a JavaScript/TypeScript runtime built on V8 -- the same JavaScript engine used by the Google Chrome browser -- designed to include a TypeScript transpiler. In this sense, Deno is the closest thing to a native TypeScript environment and inclusively represents an evolutionary step for server-side JavaScript, as it's a project pioneered by the same creator of Node.js -- the dominant JavaScript server-side environment.

TypeScript: Versions and what makes TypeScript a JavaScript superset

As of 2019 and since its unveiling in 2012, TypeScript has been released in over 90 versions! So if you thought it was difficult to learn the syntax differences between three or four ECMAScript versions, wait until... just kidding, learning TypeScript isn't that difficult and you don't need to learn all 90+ versions, primarily because of the way TypeScript works and is released.

As new JavaScript features and ECMAScript versions are released, TypeScript incorporates these new functionalities into newer TypeScript versions. And because of the fragmented nature of JavaScript features, TypeScript continually adds support for new ECMAScript features as they're formalized -- similar to how browsers support fragmented ECMAScript features -- leading to dozens of TypeScript versions with minor changes. This also means newer TypeScript versions are capable of interpreting newer JavaScript features (e.g. ES2016 [ES7], ES2017 [ES8]), where as older TypeScript versions can only work with older JavaScript features (e.g. ES5, ES6).

Another reason for the copious amounts of TypeScript versions has to do with TypeScript having its own features and development cycle. If the TypeScript steering committee decides to incorporate new features, it can do so without requiring any type of approval from anyone in the JavaScript community, but more importantly, it can do so without fear of breaking JavaScript functionalities -- remember TypeScript always ends up being converted to a compatible ECMASCript version. In this sense, newer TypeScript versions give you access to more powerful features to make development easier, where as older TypeScript versions only support better known TypeScript features (e.g. static typing).

Although there are breaking changes between TypeScript versions[20] (e.g. TypeScript 1 syntax won't necessarily be valid TypeScript 2 syntax; there are reserved keywords in TypeScript 3 that didn't exist in TypeScript 2 / TypeScript 1), for the most part TypeScript's core syntax remains equally valid across all of its versions. In addition, one thing you can be sure about is that all JavaScript is valid TypeScript, so long as the TypeScript version supports a given ECMAScript feature or version.

Finally, before exploring TypeScript syntax, I would add you shouldn't feel intimidated by the amount of TypeScript versions or its syntax. A TypeScript project can easily be started by importing plain JavaScript ES5 or ES6 into a TypeScript environment (e.g. editor, IDE) -- I did mention all JavaScript is valid TypeScript, right ? -- and once you have plain JavaScript ES5 or ES6 in a TypeScript environment, you'll immediately start getting notifications/warnings/errors about the changes required to make it TypeScript compatible -- all of which are intended to make JavaScript development more stable and scalable -- afterwards you can transpile your TypeScript project back into plain JavaScript ES5 or ES6 to run on any JavaScript engine.

TypeScript: Static type syntax

One of the unequivocal signs you're dealing with TypeScript is its use -- albeit optional -- of static data type syntax. TypeScript was conceived as a statically typed langauge, unlike JavaScript which was conceived as a dynamically typed language. This means TypeScript can define data types on all its references, preventing difficult to detect bugs on account of values changing from one data type to another. JavaScript references on the other hand, can change dynamically as illustrated in listing 2-1 and can't define an explicit data type (e.g. string, number, array) as you'll learn in greater detail in the next chapter on JavaScript data types.

The TypeScript syntax to specify static data types is declared after references with the syntax : <data_type>, as illustrated in listing 1-23.

Listing 1-23. TypeScript: Static type syntax
var letter: string = 'a';
let digit: number = 1;
const vowels: string[] = ['a', 'e', 'i', 'o', 'u'];
let letters: Array = ['c', 'z', ...vowels, 'b'];
let mixed: any[] = ['a', 1, 'b', 2, 'c', 3];
let plainArray = ['a', 1, 'b', 2, 'c', 3];

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

let arrowEchoer = (message: number) => { console.log(message); return message };

worldMessage("Hello");
arrowEchoer(1);

// TypeScript detects incorrect assignments/calls
digit = "a"; // "a" not assignable to type 'number'
worldMessage(1);// 1 not assignable to type 'string'
arrowEchoer("Hello");// "Hello" not assignable to type 'number'

Listing 1-23 shows a series of examples from prior listings in this chapter, however, notice the bolded out statements in the form : <data_type>, where <data_type> is string, number, Array<string> or another data type. This syntax enforces that references are only given values of a given data type. As you can see toward the end of the listing 1-23, attempting to reassign a static type reference or calling a function with a static type parameter with a different value than its static type, causes TypeScript to generate an error.

You can also see in listing 1-23 that TypeScript data types can be standalone string or number types, as well as arrays of these data types, supported through either the Array<data_type> or <data_type>[] syntax. In addition, TypeScript also support the any data type -- to allow any value -- as well as other data types, however, this topic is described in greater detail in the TypeScript data types section.

Finally, in listing 1-23 you can see the plain JavaScript statement let plainArray = ['a', 1, 'b', 2, 'c', 3]; that lacks a static type. This last statement is perfectly valid TypeScript, illustrating that static type definitions -- like other TypeScript features -- are optional. The thing you loose by not using TypeScript static type definitions is the safeguard that guarantees data type and value compatibility (e.g. that integers be assigned to integers references, that strings be used to call functions with string parameters). At the end of the day, TypeScript static types -- and all other TypeScript features -- are stripped out when you transpile an application into ES5 or ES6 and run on a JavaScript engine, the TypeScript syntax is there just to make JavaScript development more stable and scalable.

TypeScript: Same old, same old JavaScript syntax

If you look back at the TypeScript statements in listing 1-23, you can see all of them are based on ES5 and ES6 syntax! There's an ES5 var statement, ES6 let and const statements, a standard ES5 function, an ES6 arrow => function, as well as an ES6 spread/rest operator ....

So besides TypeScript's static type syntax, there's not much more you need to learn in order to create large TypeScript applications. If you're worried about developing TypeScript applications with outdated JavaScript syntax, TypeScript environments offer a solution through special options/flags, where by you can allow more flexible features in TypeScript applications (e.g. ES5 syntax) or enforce stricter rules in TypeScript applications (e.g. ES2016 [ES7] syntax or TypeScript specific rules).

TypeScript does have its own functional differences compared to JavaScript, but these are described in their own chapters: TypeScript data types, TypeScript object-orientated and prototype-based programming, TypeScript for loops and TypeScript asynchronous behavior.

Finally, to finish our discussion on TypeScript and JavaScript syntax in general, I'll talk about TypeScript module syntax.

TypeScript: Files, modules, scripts, module, namespace, export = , import = require() and declare

TypeScript being a JavaScript superset is not exempt from having to deal with the fragmented approach to working with modules. In fact, because TypeScript was introduced prior to the ES6 module standard and some pre-ES6 module standards, TypeScript has its own module syntax! And although the TypeScript module syntax has been mostly replaced in favor of ECMAScript standard syntax, you're still likely to find it under certain circumstances.

TypeScript logic is contained in files with the .ts extension. And following a similar convention to JavaScript .js files, TypeScript files can be either modules or scripts depending on their contents. If a TypeScript file contains statements that attempt to access external constructs or has statements permitting other files to access its own constructs, the file is deemed a module. On the other hand, if a TypeScript file doesn't import or export constructs from other files, it's considered a script.

TypeScript was conceived with the semantically confusing concepts of external modules and internal modules. TypeScript external modules relied on the module keyword, used at the beginning of a TypeScript file to make all if its constructs available to other TypeScript modules or scripts. Where as TypeScript internal modules relied on the namespace keyword, to enclose a section of TypeScript logic and make its constructs available under a given name -- to avoid name clashes -- inside the same TypeScript module or script.

The design decision of incorporating external modules/module and internal modules/namespace is rooted in the timing of TypeScript's first release -- one year after the formalization of ES5. As you learned in the previous ES5: Understanding scope and functions in JavaScript section, ES5 only support for namespaces and modules was through ad-hoc syntax using IIFE (listing 1-7) or object notation (listing-1-8). Thus the module keyword allowed the constructs of a TypeScript file to become available to other TypeScript files using a much simpler syntax, where as the namespace keyword allowed the definition of a block scope or namespace, which was severely missing due to the non-block scoping nature of var statements (listing 1-9).

With the release of TypeScript 1.5 in July 2015, TypeScript introduced support for ES6 modules and simplified its original concept of modules. TypeScript dropped the concept of external and internal modules for simply modules, with the ES6 module syntax described earlier (e.g. export, import, default ) taking precedence for TypeScript module definition. With TypeScript 1.5, TypeScript internal modules simply became known by their obvious namespace keyword as namespaces and the use of the module keyword for TypeScript external modules was simply phased out -- although you may still find it in legacy TypeScript logic.

However, even with TypeScript streamlined to use ES6 module syntax, it still left TypeScript with one loose end in the area of JavaScript module support: Pre-ES6 modules. In order to streamline support for CommonJS and AMD module syntax, TypeScript introduced the syntax variation export = and the corresponding import = require() to replace the use of the exports keyword to define a single module object to export/import.

Finally, to allow a TypeScript module to leverage references from other locations (e.g. another TypeScript module, a JavaScript script), TypeScript introduced the declare keyword. The declare keyword is used to support ambient declarations, which is a fancy name for placeholder references. If a reference originates in another location other than the TypeScript where it's used, it must still be declared to avoid undefined error references, in such cases, the declare keyword is used to indicate the original reference hails from another location and is simply a placeholder reference to avoid undefined error references.

  1. https://www.ecma-international.org/publications/files/ECMA-ST-ARCH/ECMA-262%205th%20edition%20December%202009.pdf    

  2. https://www.ecma-international.org/ecma-262/5.1/    

  3. http://www.wirfs-brock.com/allen/posts/39    

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

  5. https://en.wikipedia.org/wiki/First-class_function    

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

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

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

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

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

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

  12. http://www.commonjs.org/    

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

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

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

  16. https://github.com/systemjs/systemjs    

  17. https://babeljs.io/    

  18. https://www.ecma-international.org/ecma-262/7.0/    

  19. https://www.ecma-international.org/ecma-262/8.0/    

  20. https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes