''' JavaScript data types: ES5, ES6, ES2016 (ES7) and TypeScript | Modern JS

JavaScript data types: ES5, ES6, ES2016 (ES7) and TypeScript

JavaScript data types are an essential piece to understanding modern JavaScript. Although JavaScript data types can be a rather dry subject, the topic holds an important key to making better JavaScript design choices, which includes discovering techniques to achieve results with faster execution times and less typing.

Similar to JavaScript key concepts explored in the previous chapter, JavaScript data types have evolved considerably throughout the various ECMAScript versions. In this chapter you'll learn about JavaScript data types and their dynamically typed behavior, how there are only a few core JavaScript data types, as well as how more sophisticated JavaScript data types have been incorporated into later ECMAScript versions to support more advanced constructs. In addition, you'll also learn about TypeScript -- a JavaScript superset -- and how its statically typed behavior influences JavaScript data types for the better, as well as how it offers a completely fresh outlook on JavaScript data types.

ES5: Dynamically typed behavior and data types

JavaScript was conceived as a dynamically typed language. This means data types get determined automatically at run-time and it isn't necessary to declare variable data types. Although this creates a much quicker development process, it also introduces subtle data type bugs, that would otherwise be easily detectable in statically typed languages (e.g. Java, C#) by means of a compiler. Listing 2-1 illustrates JavaScript's dynamically typed behavior.

Listing 2-1. ES5: Dynamically typed behavior
var x = 1;
console.log(typeof(x));
x = ['one'];
console.log(typeof(x));
x = "1";
console.log(typeof(x));
var y = 2;
console.log(x + y);

As you can see in listing 2-1, the x variable is first interpreted as a number, then as an object and finally as a string. Although this cuts down on the amount of typing, notice the last operation x + y that involves the y = 2 variable. Because the last x assignment is a string -- x = "1" -- the x + y operation does not result in a mathematical operation, but rather a string concatenation, therefore x + y results in 12, not in 3. This can seem like a trivial issue to detect in this case, but in projects spanning hundreds or thousands of lines it can result in odd bugs that are difficult to diagnose.

This dynamically typed behavior is one of the main value propostions of TypeScript, a JavaScript superset that supports static typing and is eventually transpiled into JavaScript. The previous chapter on JavaScript syntax described TypeScript - Static type syntax and the upcoming section on TypeScript data types expands on TypeScript's features.

ES5: Primitive data types

The first series of data types available in JavaScript ES5 are called primitives. JavaScript ES5 supports five primitives: string, number, boolean, null and undefined. Primitive data types cannot be de-constructed into simpler types, so they're pretty easy to identify, not to mention their behavior is also straightforward (e.g. var x = 'JavaScript'; is a primitive string, var x = 1; is a primitive number, var x = true; is a primitive boolean,etc).

Primitive data types by themselves don't support much functionality besides representing their values, but due to their potential for requiring additional functionalities, three of the five primitive data types -- string, number, boolean -- support constructor objects.

ES5: Constructor objects for string, number and boolean primitives

JavaScript constructor objects are a 'behind the scenes' mechanism by which JavaScript primitive data types emulate the behavior of an equivalent full-fledged JavaScript object data type. By emulating a full-fledged JavaScript object data type, these three primitive data types gain access to additional functionalities, as illustrated in listing 2-2.

Listing 2-2. ES5: Primitives and constructor objects
var x = 'JavaScript';
console.log(x.length);
console.log(x.indexOf('S'));
console.log(x.substring(4));

// Equivalent String object, which behind the scenes calls String.toString() 
var y = String('JavaScript');

if (x == y) { 
  console.log('x == y is True');
} else {
  console.log('x == y is False');
}

if (x === y) { 
  console.log('x === y are True');
} else { 
  console.log('x === y are False');
}

// Explicitly creates an object of a 'primitive' data type with 'new'
var z = new String('JavaScript');

if (x == z) { 
  console.log('x == z is True');
} else {
  console.log('x == z is False');
}

if (x === z) { 
  console.log('x === z is True');
} else {
  console.log('x === z is False');
}

The var x = 'JavaScript'; statement in listing 2-2 is a primitive string data type -- sometimes also referred to as a literal string. However, notice that immediately after the declaration, calls are made to multiple properties (e.g. length) and methods (e.g.indexOf(), substring()) on the same x reference. Behind the scenes, the JavaScript engine provides all primitive string data types access to the same functionalities as a full-fledged String object data type. So in reality, the properties and methods used by the primitive string data type in this example, are actually part of the String object data type. This same automatic constructor object behavior for primitive string data types, is also used for primitive number and boolean data types (i.e. a primitive number data type gains access to a Number object data type and a primitive boolean data type gains access to a Boolean object data type).

The second statement var y = String('JavaScript'); in listing 2-2 is a constructor based string because it uses the String object data type constructor, which also results in a primitive string. Behind the scenes, String('JavaScript'); calls String.toString('JavaScript'), so you end up with an identical value to using the primitive string syntax, reason why this constructor based syntax is rarely used and considered redundant. You can confirm both String('JavaScript') and 'JavaScript' are equivalent in the subsequent condition evaluations in listing 2-2, where both x -- which doesn't use an explicit object data type -- and y -- which does use an explicit object data type -- are identical, using both the equality == operator and the strict equality === operator (a.k.a. identity operator).

The third statement var z = new String('JavaScript'); in the example makes use of the new operator to create a String object instance. While the new operator is perfectly valid JavaScript OOP (Object Orientated Programming) syntax, its use is discouraged in object data types that map directly to primitive data types (i.e. String, Number and Boolean).

The reason the new operator should be avoided in primitive scenarios is illustrated in the final snippets of listing 2-2. Notice that although the z reference -- that uses the new operator -- apparently creates an identical string to the x and y references, the values differ. On the subsequent condition evaluations, notice the z reference is only equal to the x and y references with an equality == comparison and not with a strict equality comparison ===. In this case, the strict equality comparison fails because the z reference is an object value and the other two are primitive values, which is why the new operator should be avoided on simple object data types that map directly to primitive data types to avoid this type of equality operator disparity. The next chapter contains the section To new or not to new, literals, singletons and static properties/methods with additional details about the influence of using the new keyword with data types.

Forget about loose equality operators == & != syntax and instead use strict equality operators === & !==

A corollary to avoiding the new operator in primitive data type scenarios, is avoiding the use of loose equality operators == & != altogether, because they test against primitive values producing the unexpected results illustrated in this last example in listing 2-2.

Strict equality operators === & !== ensure comparisons are made against object values, producing intuitive results. Which is why strict equality syntax is often the recommended choice, particularly for JavaScript beginners.

If case you're curious about why loose equality operators == & != produce the results they do -- even when applied to object values -- they use the Abstract Equality Comparison Algorithm[1].

ES5: The global object, null and undefined, plus the JavaScript global scope, this, window and global keywords.

Although null and undefined are primitive data types, they don't have constructor objects like the string, number and boolean primitives data types. This means the null and undefined primitive data types don't have equivalent full-fledged JavaScript object data types and as such have no extra functionalities (e.g. indexOf(), substring()) and are truly limited to just representing a value.

Here it's important to understand the null and undefined primitive data types -- as well as other properties and functions -- originate in what's called the JavaScript global object. Every time you run a JavaScript engine, a global object is made available to access functionalities without requiring explicit instructions or objects. Table 2-1 illustrates the various JavaScript global object properties and functions.

Table 2-1. ES5: Global object properties and functions
Properties/methodsDescription
InfinityA numeric value that represents infinity
NaNA value that doesn't represent a number, NaN is an acronym for Not A Number
undefinedA value that represents an undefined reference
null*A value that represents a missing object value. *NOTE: Technically null isn't a global object property, but rather an empty object. However, null is always accesible like other global object properties and functions.
eval()Evaluates the input string value as JavaScript code
isFinite()Evaluates whether the input value is a finite number.
isNaN()Evaluates whether the input value is a NaN (Not a number).
parseFloat()Parses the input value into a floating point number.
parseInt()Parses the input value into an integer.
decodeURI()Decodes a URI, the opposite of encodeURI()
decodeURIComponent()Decodes a URI component, the opposite of encodeURIComponent()
encodeURI()Encodes a URI, the opposite of decodeURI()
encodeURIComponent()Encodes a URI component, the opposite of decodeURIComponent()

The most important takeaway of the properties and functions in table 2-1 is they're all available out of the box in any JavaScript environment. For example, you can place an eval() or parseInt() statement anywhere in a JavaScript application and it will happily work. However, although the properties and functions in table 2-1 are generally called without any additional syntax, sometimes the JavaScript global object itself is explicitly referenced with the this keyword (e.g.this.eval() is equivalent to eval(), this.parseInt() is equivalent to parseInt()).

And in order to understand when it's necessary to explicitly reference the JavaScript global object with the this keyword, it's inevitable to revisit the topic of JavaScript scope and hoisting explored in the previous chapter.

By default, the JavaScript global object starts off with the properties and functions presented in table 2-1, however, as soon as you start adding statements to the global scope of a JavaScript application, these get automatically added to the global object. This means that if you add the statement var x = 'JavaScript'; to the top level -- like listing 2-2 -- it gets added to the JavaScript global object. And while in most cases it's enough to simply use the x syntax instead of this.x -- similar to the properties and functions in table 2-1 -- there are cases when explicitly referencing the JavaScript global object is necessary, one such case is presented in listing 2-3.

Listing 2-3. ES5: Global object and this syntax
var primeNumbers = [2,3,5,7,11];

console.log(primeNumbers);
console.log(this.primeNumbers);

var that = this;

function testing() { 
     console.log("Inside testing");
     var primeNumbers = [13, 17, 19, 23, 23, 29];
     console.log(that.primeNumbers);
}

testing();

You can see the top level statement primeNumbers in listing 2-3 is initially accessed using either primeNumbers or this.primerNumbers, even though the latter syntax is redundant. However, notice the logic inside the testing() method contains another variable named primeNumbers, which presents a problem, how do you access variables with the same name in different scopes ? In order to access the top level primeNumbers variable inside the function, you can see the method in listing 2-3 uses the auxilary that reference which holds the this global object, so the that.primeNumbers syntax gives the method access to the JavaScript global object -- if you're curious as to why the var that = this placeholder was required, review the section lexical scope, the this execution context and closures in the previous chapter.

Now, if you've done any kind of object-orienated programming (OOP) or as you'll learn in the upcoming chapter on JavaScript object-orientated and prototype-based programming, the this keyword holds special meaning to reference object instances. Although the JavaScript global object is technically an object instance and therefore using the this keyword to reference it is accurate, this doesn't mean it isn't confusing.

The problem with using the this keyword to reference JavaScript's global object, is that if you have an OOP heavy design that uses the this keyword extensively, it can be hard to tell if this is referring to the JavaScript global object. Although most JavaScript engines continue to let you use the this keyword to reference JavaScript's global object, to alleviate this potential for confusion -- as well as add custom functionalities to the global object -- JavaScript engines have come to rely on either the window or global keywords to reference JavaScript's global object.

The window keyword has become the norm to reference the JavaScript global object in browser-based JavaScript engines (e.g. Chrome, Firefox, IE), where as the global keyword has been adopted as the JavaScript global object reference for non-visual JavaScript engines (e.g. Node JS).

In addition to the window and global keywords being the JavaScript global object reference and providing access to the properties and methods in table 2-1, these references also come equipped with their own custom functionalities. For example, the window reference has access to properties like console to access a browser's debugging console and methods like alert() to display an alert dialog , all of which become automatically available in browsers -- with or without using the window reference -- in order to facilitate JavaScript development in browser bound applications.

In this sense, both the window and global JavaScript global object references are in fact full-fledged JavaScript object data types -- similar to the String object, Number object and Boolean object data types described in the first section of this chapter -- with the window reference being a Window object data type[2] and the global reference being a global namespace object[3].

ES5: 'First class' data types - object, string, number, boolean, undefined and function

Now that you understand JavaScript primitive data types and their close relationship to object data types, let's take a closer look at the 'first class' data types available in JavaScript ES5.

JavaScript ES5 has a small amount of data types, which can be particularly surprising for someone with experience in other programming languages with dozens of data types. Listing 2-4 makes use of the typeof method to illustrate JavaScript ES5 data types.

Listing 2-4. ES5: 'First class' data types

console.log(typeof("Hello")); // string
console.log(typeof(1)); // number
console.log(typeof(true)); // boolean

var a;
var b = function() { return "ok" }
var c = null;

console.log(typeof(a)); // undefined
console.log(typeof(b)); // function
console.log(typeof(c)); // object


console.log(typeof({})); // object
console.log(typeof({'language':'JavaScript'})); // object
console.log(typeof([1,2])); // object
console.log(typeof(new Date())); // object
console.log(typeof(null)); // object

The first three typeof statements in listing 2-4 correspond to the same three primitive/object data types you learned about in the first part of this chapter -- string, number and boolean -- which form part of the JavaScript ES5 primitive data types that support constructor objects.

Next, are three more typeof statements. The first statement illustrates the case of the a reference which outputs an undefined data type because a has no value assigned to it. The b reference is assigned a function() value and outputs the rather obvious function data type. But more importantly, notice the third and final typeof statement of this second set, the c reference is assigned a null value and outputs an object data type! So why does a null value produce an object data type instead of a null data type ?

Although the c reference is assigned a null primitive value, the typeof statement outputs an object data type, because as I already mentioned in the previous section, the null primitive value does not map directly to a JavaScript object data type and actually represents a missing object value as described in table 2-2, so it's assigned a generic JavaScript object data type.

All of which takes us back to the 'first class' wording in the title of this section. ES5 only has six 'first class' data types -- string, number, boolean, undefined, function and object. So why do I call them 'first class' data types ? Because as you just confirmed in listing 2-4, only these data types are unequivocally identified by a JavaScript engine, all other data values that even at first glance might look like data types themselves are assigned to the catch-all object data type.

You can confirm this catch-all behavior of the object data type in the last set of typeof statement in listing 2-4. The first two statements {} and {'language':'JavaScript'} in listing 2-4 in other programming languages are called hash tables/hash maps, dictionaries or associative arrays, but in both cases the JavaScript typeof statement identifies them as object data types.

Next, notice the third statement in the last set [1,2] which in other programming languages might be identified as an array or list, in JavaScript is also an object data type! Similarly, the fourth statement new Date() which is a date value also outputs an object data type, just like the fourth and final null value produces an object data type.

Now that you know about the prevalence of the object data type and its close relationship to various kinds of JavaScript values, let's take a closer look at it.

ES5: The all prevalent Object data type

Comprehending the JavaScript Object data type is one of the most important concepts you need to understand about JavaScript data types in general, primarily because all JavaScript data types are Object data types due to inheritance. This includes not only the JavaScript global object and the Window object data type you learned about in an earlier section, but also the String object data type, the Number object data type and the Boolean object data type, in addition to all the other JavaScript object data types you'll learn about in this chapter.

So with all JavaScript object data types being descendants of the Object data type, it means all data types gain access to a basic set of functionalities. The next chapter on JavaScript object-orientated and prototype-based programming takes a closer look at these functionalities and dives deeper into the topic of JavaScript prototypal inheritance, but for the moment let's continue to analyze the Object data type.

Since the Object data type is at the top of the hierarchy of all JavaScript data types, the Object data type has a very simple design to store properties and values of any kind, as illustrated in listing 2-5.

Listing 2-5. ES5: Everything is an Object
// Empty object literal
var stuff = {}; 

// Add property and value
stuff.language = 'JavaScript';

// Create populated object literal
var languages = {'JavaScript':
                    {
                      'versions':[
                            {'ES5': 'ECMAScript 5'},
                            {'ES6': 'ECMAScript 6'},
                            {'ES7': 'ECMAScript 7'},
                            {'TS' : 'TypeScript'}
                      ]
                    }
                }

// Create populated object literal with function 
var wonderful = {
       flag: true,
       constant: "Object literal",
       render: function() { 
           console.log("Hello from wonderful.render()!");
          }
}

console.log(stuff);
console.log(Object.prototype.toString.call(stuff));  // [object Object]
console.log(languages);
console.log(Object.prototype.toString.call(languages)); // [object Object]
console.log(wonderful);
console.log(Object.prototype.toString.call(wonderful)); // [object Object]

The first step in listing listing 2-5 creates an empty object with {} and assigns it to the stuff reference. Next, using a dot on the stuff reference, the language property with a 'JavaScript' string value is added to the stuff object. It's worth mentioning, this technique of adding properties and values to a JavaScript object, is how the JavaScript global object operates behind the scenes (e.g. global variables and functions are appended as properties to the global object).

The other two object statements in listing 2-5 illustrate how it's possible to declare inline values as part of an object declaration, as well as how it's possible to use different object property values, including literals and functions, the last of which are technically called methods in the context of objects. Finally, the console.log statements in listing 2-5 output the contents of each object and its data types, which you can confirm for all three cases is [object Object].

But let's pause for a second, why do you think the data type (i.e. the output of Object.prototype.toString.call) for all three objects in listing 2-5 is [object Object] ? In this case, the JavaScript engine is saying the data types are Object object data types, which is the most general purpose data type JavaScript has to offer.

As it happens and I already mentioned a few paragraphs ago, the fallback data type for JavaScript values is always an object data type. However, JavaScript objects in themselves always have an object data type, which is the topic of the next section.

ES5: JavaScript object data types

Now that you have a firm understanding of JavaScript's 'first class' object data type, it's time to take a closer look at the various kind of object data types available in JavaScript ES5 that are descendant of this data type.

Even though JavaScript is technically limited in its number of data types -- with many being object data types -- there are many JavaScript object types to facilitate operations with JavaScript object values. Listing 2-6 makes use of the Object.prototype.toString.call() method to illustrates various object types.

Listing 2-6. ES5: JavaScript Object data types
var numbers = [1,2];
var today = new Date();

console.log(Object.prototype.toString.call(numbers)); // [object Array]
console.log(Object.prototype.toString.call(today)); // [object Date]


var languages = {'JavaScript':
                    {
                      'versions':[
                            {'ES5': 'ECMAScript 5'},
                            {'ES6': 'ECMAScript 6'},
                            {'ES7': 'ECMAScript 7'},
                            {'TS' : 'TypeScript'}
                      ]
                    }
                }

console.log(Object.prototype.toString.call(languages)); // [object Object]

Notice how the first set of Object.prototype.toString.call() statements in listing 2-6 outputs [object Array] and [object Date], indicating a specific type of object. The importance of a specific object data type can't be understated, because this is what provides easy access to custom functionality to work with different kinds of data, a mechanism you learned first hand in listing 2-2 with the String data type having various methods designed to work with string values.

The last Object.prototype.toString.call() statements in listing 2-6 -- executed on the languagesreferences -- outputs [object Object]. The interesting aspect of this output is that it doesn't produce a specific object data type like the previous statements, but rather a generic object data type.

Although object data types represent a powerful option due to their generic nature and flexibility, this last behavior is one of JavaScript's greatest deficiencies in terms of ES5 data types. The problem with a generic data type like object is it has limited functionality. For example, while Date object values gain access to methods like getFullYear() and getHours(), and Array object values gain access to methods like pop() and push() -- all of which facilitate operations on these types of values -- Object object values have no additional functionality. This lack of functionality is most evident in JavaScript for loops where ad-hoc techniques are required to execute simple tasks on generic object data types. To solve these issues, ES6 introduced additional object data types to deal with hash table/hash map like values, which are described in the upcoming section.

But before we get to the topic of ES6 object data types, I want you to get a better feel for some of JavaScript's ES5 most common object data types. Not only because ES5 object data types continue to be a staple of JavaScript development, but also because these objects data types have evolved in newer ECMAScript specifications. Table 2-2 illustrates the most common JavaScript ES5 object data types.

Table 2-2. ES5: Most common object data types
Object data typeDescriptionSample properties and methods
Object[4]All JavaScript object data types are descendants of this class and have access to its properties and methods. Values like associative arrays or dictionaries that can't fit into a more specific sub data type (e.g. String, Date) are simply Object's. Object.prototype.- Allows the addition of properties to an object. See the next chapter on JavaScript object-orientated and prototype-based programming for additional details on its use.
Object.keys().- Returns an array with an object's properties.
String[5]A JavaScript object to store text or character values.String.charAt(x).- Returns the character at the x index.
String.endsWith(x).- A boolean evaluation to determine if string ends with the x string/characters.
Number[6]A JavaScript object to store numerical valuesNumber.isInteger(x).- A boolean evaluation to determine if x is an integer
Number.toExponential(x).- Returns a string representing the number x in exponential notation.
Boolean[7]A JavaScript object to store boolean valuesContains no particular functionalities of its own, except those behavior inherited from Object
Function[8]A JavaScript object to represent functionsFuction.apply().- Calls a function and sets its this to the provided value; see listing 1-7 for example
Function.call().- Executes a function and sets its this to the provided value; see listing 1-7 for example
Array[9]A JavaScript object to store arrays or lists as they're called in other programming languages.Array.pop().-Removes the last element from an array and returns that element.
Array.reverse().-Reverses the order of the elements of an array in place -- the first becomes the last, and the last becomes the first
Date[10]A JavaScript object to represents a single moment in time.Date.now().- Represents the numeric value corresponding to the current time -- the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC (a.k.a. 'Unix epoch time') -- with leap seconds ignored.
Date.getMonth().- Returns the month (0-11) ood the date instance according to local time
Math[11]A JavaScript object with mathematical constants and functionsMath.PI.- Represents the ratio of the circumference of a circle to its diameter, approximately 3.14159.
Math.round(x).- Returns the value of x rounded to the nearest integer.
JSON[12]A JavaScript object to parse and convert JavaScript Object Notation (JSON) data structures.JSON.parse().- Parses a string as JSON
JSON.stringify().- Return a JSON string corresponding to the specified value.
Error[13]A JavaScript object to handle errors or custom errors.Error.message.- Displays error message
Error.name.- Displays error name.
RegExp[14]A JavaScript object for regular experessions.Regexp.exec(x).- Executes a search for a match on the x string .
RegExp.ignoreCase.- Ignores case while attempting to match a string.

ES6: symbol primitive, Proxy and Reflect, the Map and Set data types, plus additions to ES5 data types

ES6 introduced major changes and additions to JavaScript data types. First, ES6 introduced a new primitive data type dubbed symbol to support unique values. Another important addition to ES6 was the inclusion of the Proxy and Reflect object data types, to give JavaScript the ability to proxy or modify application structures and behaviors at runtime.

To facilitate working with data structures that in ES5 were pegged to the the generic and all prevalent Object data type, ES6 introduced the Map, Set, WeakMap and WeakSet object data types designed to work with key-value pairs and unique values. In addition, ES6 also introduced the Promise object data type which plays a key role in JavaScript asynchronous behavior, as well as the Generator and GeneratorFunction object data types which are equipped to deal with JavaScript generator expressions which are closely tied to JavaScript for loops.

Finally, many of the ES5 object data types presented in table 2-2 were also updated with new properties and methods to support new requirements. For example, the Object object data type was updated with the is() to compare if two values are the same value; the String object data type was updated with the startsWith() method to determine if a string begins with the characters of a give string; and the Array object data type was updated with the find() method to return the value of the first element in an array that satisfies a given testing function.

ES6: symbol primitive

Complementing the five primitive data types from ES5 -- string, number, boolean, null and undefined -- ES6 introduced the symbol primitive, whose purpose is to generate unique JavaScript values, just like symbols in other programming languages[15].

In order to create a symbol primitive value, ES6 introduced the JavaScript global object function Symbol()[16] -- which complements the ES5 Global object properties and functions presented in table 2-1. Listing 2-7 illustrates the basic usage of a symbol primitive.

Listing 2-7. ES6: JavaScript symbol primitive data type
let vowel = Symbol();
let letter = Symbol("Letter");

console.log(typeof(vowel));
console.log(typeof(letter));
console.log(vowel.toString());
console.log(letter.toString());

// Create another symbol with the same description
let character = Symbol("Letter");

// Symbols are never the same, even if they use the same description 
if (letter == character) { 
   console.log('letter == character is True');
} else {
  console.log('letter == character is False');
}

if (letter === character) { 
   console.log('letter === character is True');
} else {
  console.log('letter === character is False');
}

// Use symbols as Object keys 
let config = {}
config[vowel] = "A";
config[letter] = "B";
config[character] = "C";

console.log(config);

The first two statements in listing 2-7 generate two primitive symbols with the global Symbol() function, in which the second variation uses an optional argument -- in this case "Letter" -- to give the symbol a description. Next, you can see the type for both references is a symbol data type and the output generated by the toString() method for the symbol corresponds to the description assigned to the symbol (if any).

More importantly though, notice the third symbol definition in listing 2-7 -- let character = Symbol("Letter"); -- uses the same symbol generation syntax as let letter = Symbol("Letter"); before it. Under most computer science scenarios you'd likely expect character to be equal to letter since they're built with the same syntax, but they're not, as you can see in the subsequent conditionals.

JavaScript registers symbols in a symbol registry to ensure uniqueness everytime a call is made to Symbol(). And what is a symbol's actual value ? It doesn't matter -- you can assign a descriptive name as you've already seen -- but behind the scenes all you need to know is the value is unique, a technique that comes in handy to avoid assignment clashes (e.g. loading multiple libraries that use the same references or assigning object keys).

The last snippet in listing 2-7 creates an empty object literal and then uses the prior symbols as keys. This is in contrast to the more typical approach of using strings as object keys which have the probability of clashing with one another. For example, if an object has the potential to have dozens of keys or be modified in multiple places, using an assignment like config['name'] (i.e. a string key) has the probability of being overwritten/reassigned vs. an assignment like config[name] where name is a symbol (i.e. a symbol key) and is never overwritten/reassigned since it's unique.

ES6: Proxy and Reflect object data types

Similar to the object data types in table 2-2 designed to simplify certain JavaScript tasks (e.g. date operations with Date, math operations with Math), JavaScript ES6 introduced the Proxy and Reflect data types to simplify working with proxies and reflection.

Are you familiar with JavaScript classes and object-orientated programming ?

To understand the JavaScript Proxy and Reflect object data types, you inevitably need to be comfortable with JavaScript classes and object-orientated programming concepts. If you've never worked with either of these concepts, I recommend you look over the following chapter on JavaScript object-orientated and prototype-based programming: ES5, ES6, ES2016 (ES7) and TypeScript to gain a better understanding of the examples that follow.

Although software proxies and reflection are not widely used techniques compared to something like date and math operations, proxies and reflection can be essential for testing and certain object orientated designs. If you've never worked with software proxies, I recommend you research the proxy pattern[17] to get a better feel for the use of proxies, in addition to researching the concept of computer science reflection[18] in case you've never worked with the concept of reflection in software.

Like all software proxies, the purpose of a JavaScript Proxy is to serve as a front for a given object. And why on earth would you want to do that ? In most cases it's to override (or add) custom behavior for a given object without modifying the backing object itself. This process can be common in software testing, where it's helpful to temporarily alter or add new behavior to an object, without having to alter the original object's implementation.

A JavaScript Proxy[19] works with two key concepts: a target object and traps. A target object represents the object on which you want to override (or add) custom behaviors, this can be as simple as a vanilla object (e.g.{}) or as elaborate as an object instance from a custom data type or class (e.g. Language, Letter). Traps represent handlers to execute custom logic when certain actions are made on a target object. Traps -- which are defined as part of a Proxy -- can contain logic as simple as log messages or as complex as overriding elborate business logic in the underlying target object. In addition, there can be multiple types of traps to execute custom behaviors at different points in the lifecycle of an object (e.g. when getting an object property value, when executing a function call or when creating an object instance with new).

A JavaScript Proxy can include up to thirteen different traps[20]. Listing 2-8 illustrates the basic syntax of a JavaScript Proxy object data type using a get trap.

Listing 2-8. ES6: JavaScript Proxy object data type
class Language  {
   constructor(name,version) {
     this.name = name;
     this.version = version;
   }
   hello() {
     return "Hello from " + this.name;
   }
}

let instanceLanguage = new Language("Python","2.7");

let languageProxy = new Proxy(instanceLanguage, {
    get: function( target, key, context ) {
        console.log('Accessing ' + key + ' with value ' + target[key] + ' through languageProxy');
        if (key == "name" && target[key] == "Python") { 
           console.log("Python, really ? You know you can switch to JavaScript on the backend right ?")
        }
    }
});


languageProxy.name;
languageProxy.version;
languageProxy.hello;

Listing 2-8 starts with the creation of a JavaScript class named Language, followed by the creation of an instance of said class assigned to the instanceLanguage reference. Next, a Proxy instance is created with the intent to alter the behavior of the instanceLanguage object instance without modifying the underlying Language class. The syntax to create a Proxy follows the pattern new Proxy(target, handler), where target represents the object you want to apply the proxy to and handler the proxy logic (i.e.traps) applied to the target object.

In this case, the instanceLanguage object instance has a get trap applied to it which is used to execute logic every time an object's properties or methods are accessed. Next, you can see the logic executed by the get trap consists of outputting a few log messages and inspecting the object's properties and values.

Finally, notice how it's possible to call the languageProxy object instance directly and access the same properties and methods as the underlying object instance (i.e. name, version, hello). More importantly though, notice that when calling the properties and methods of the proxied object -- in this case instanceLanguage -- the output reflects the logic contained in the Proxy get trap. In this manner, it's possible to override (or add) behaviors to an object at different points in its lifecycle without modifying its underlying class.

The JavaScript Reflect[21] data type represents an effort to conceptually align JavaScript with mainstream OOP languages. In OOP languages like Java and C#, reflection is the umbrella term used to describe techniques that inspect, add or inclusively modify objects at run-time and for which each langauge has it own dedicated built-in package. However, reflection techniques in JavaScript prior to the appearance of the Reflect data type were fragmented.

For example, back in listing 2-1 you learned how the typeof operator outputs a reference's data type and in listing 2-5 you also learned how a call to Object.prototype.toString.call(reference) also outputs a reference's data type. Both of these are reflection techniques that inspect an object to determine its type. Inclusively, the all prevalent Object data type has several methods that produce reflection, such as Object.defineProperty() and Object.defineProperties() which can modify objects programmatically by adding a single or multiple properties to them, respectively.

Listing 2-9 illustrates a series of operations with the JavaScript reflect object data type demonstrating how it's possible to inspect, add or inclusively modify objects.

Listing 2-9. ES6: JavaScript Reflect object data type
class Language  {
   constructor(name,version) {
     this.name = name;
     this.version = version;
   }
   hello() {
     return "Hello from " + this.name;
   }
}

let instanceLanguage = new Language("Python","2.7");

let today = new Date();

let primeNumbers = [2,3,5,7,11];

// Inspect with Reflect
console.log(Reflect.apply(Object.prototype.toString,instanceLanguage,[]));
console.log(Reflect.apply(Date.prototype.toString,today,[]));
console.log(Reflect.apply(Math.max,undefined,primeNumbers));

console.log(Reflect.has(instanceLanguage, 'name'));
console.log(Reflect.has(today, 'day'));
console.log(Reflect.has(today, 'toString'));
console.log(Reflect.has(primeNumbers, 'length'));

// Add with Reflect 
console.log(instanceLanguage.author);
Reflect.defineProperty(instanceLanguage,'author',{'value':'Guido van Rossum'});
console.log(instanceLanguage.author);

console.log(primeNumbers);
Reflect.set(primeNumbers, 5, 13);
console.log(primeNumbers);

// Modify with Reflect
console.log(instanceLanguage.name);
Reflect.set(instanceLanguage,'name','JavaScript');
console.log(instanceLanguage.name);
Reflect.deleteProperty(instanceLanguage, 'name');
console.log(instanceLanguage.name);

console.log(today);
Reflect.apply(Date.prototype.setFullYear,today,[2030]);
console.log(today);

One important aspect of the Reflect data type is that all its operations are static. This means you don't create Reflect object instances with new, but rather make direct calls on data references as illustrated in listing 2-9. Note, the next chapter on JavaScript object-orientated and prototype-based programming contains more details about static JavaScript data type operations, specifically the to new or not to new, literals, singletons and static properties/methods section.

Listing 2-9 begins with a custom Language data type class and the instanceLanguage instance of said class, followed by a standard Date data type instance and an Array data type. The first section in listing 2-9 demonstrates how the Reflect.apply() and Reflect.has() methods are capable of inspecting object data types.

As you can see in listing 2-9, the Reflect.apply() method accepts three arguments: A function call, an object data type instance or this reference with which to call the function and an input argument array with which to call the function. The first two Reflect.apply() calls are made on the Object.prototype.toString and Date.prototype.toString functions, respectively. Each of these functions simply outputs the string representation of an object and data object and in each case a corresponding Object and Date instance are used as the second Reflect.apply() argument. Because both functions (i.e. Object.prototype.toString and Date.prototype.toString) don't require input arguments, the third argument of these first two Reflect.apply() calls is an empty array []. The third Reflect.apply() call is made on the Math.max function which determines the maximum number in an array. Because Math.max doesn't require an object instance and relies on an input array argument, the second argument to Reflect.apply() is undefined and the third argument is the primeNumbers array.The Reflect.has() method is used to determine if a data type instance has a given property and accepts two arguments: an object data type instance to inspect and a quoted value with a property name. You can see the Reflect.has() calls in listing 2-9 all output true with the exception of Reflect.has(today, 'day'), because the today instance -- a Date data type -- doesn't have a day property.

The second and third sections in listing 2-9 consist of adding properties and modifying object data types with Reflect operations. You can see in listing 2-9, the Reflect.defineProperty() call is used to add the 'author' property to the instanceLanguage object data type instance, where as the Reflect.set() call is used to add a prime number to the primeNumbers array, where the second argument represents an object property -- in this case index 5 -- and the third argument the actual value -- in this case prime number 13. Next, you can see another Reflect.set() call is used to modify the name property value of the instanceLanguage object data instance and a call to Reflect.delete() is used to the remove the name property altogether. Finally, you can see the Reflect.apply() method is used once again, but this time to invoke the Date.prototype.setFullYear function to modify the today date instance with the 2030 argument, which effectively change's the date instance's year.

So what should you make of the Reflect data type ? Particularly considering reflection-like operations are supported elsewhere in JavaScript ? From a practical standpoint, you should try to use the Reflect data type when you require reflection features, for the simple reason it provides a clear cut location to implement these tasks in JavaScript, just like it's done in languages like Java and C# that have built-in reflection packages (i.e. reflect tasks aren't spread out in operators like typeof or other data types like Object). You can of course keep using the different JavaScript reflection-like operators and methods supported outside of the Reflect data type, but it becomes much easier -- for yourself and others -- to identify and implement reflection in JavaScript if you do so with a consolidated and built-in data type like Reflect.

ES6: Map and WeakMap object data types

It's fairly common for software applications to require data structures to keep track of key-value pairs. To that end, most programming languages support this kind of data type since their first versions (e.g. Python and C# with dictionaries, Java and C++ with maps), but in JavaScript, support for this kind of data type came until ES6 with the appearance of the Map[22] data type. The reason it took JavaScript so many versions to get a dedicated data type to store key-value pairs is rooted in the versatilty of the all prevalent Object data type.

If you review some of the previous listings with the Object data type (e.g. listing-2-5) you can confirm an Object structure follows a key-value pattern, with each object property representing a key and the property value complementing the value pair. However, as flexible as the Object data type can appear to store key-value pairs in JavaScript, it has several shortcomings compared to dedicated data types designed for this purpose in other programming languages.

So just like the other JavaScript data types you've learned about that provide a more comprehensive set of features over a plain Object data type for certain scenarios, the Map data type offers some of the following advantages for storing key-value pairs:

Listing 2-10 illustrates several use cases of the Map data type.

Listing 2-10. ES6: JavaScript Map object data type
// Empty map
let stuff = new Map();

// Add elements to Map
stuff.set('vowels',['a','e','i','o','u']);
stuff.set('numbers', [1,2,3,4,5]);
stuff.set('pi','3.14159265359');

// Loop over Map
stuff.forEach(function(value, key) {
  console.log(key + ' = ' + value);
});

// Get specific Map value
console.log(stuff.get('numbers'));

// Get an array with keys in Map
console.log(Array.from(stuff.keys()));

// Get an array with values in Map
console.log(Array.from(stuff.values()));

// Populate Map on creation 
let languageCreators = new Map([['JavaScript', 'Brendan Eich'], ['Python', 'Guido van Rossum']]);

console.log("JavaScript was created by " + languageCreators.get('JavaScript'));
console.log("Python was created by " + languageCreators.get('Python'));

Listing 2-10 begins by creating an empty Map instance assigned to the stuff reference. Next, the Map's set() method is used to assign three key-value pairs to the stuff reference and immediatly after the Map's forEach() method is used to loop over the key-value pairs and output them to the console. Next, you can see the Map's get() method is used to extract a value based on a key and the keys() and values() methods are used to extract a Map's keys and values in the form of Array data structures. Finally, listing 2-10 illustrates how it's possible to create a Map instance with an initial set of values using Array data structures.

A WeakMap[23] data type is a more specialized kind of Map data type designed for better performance. The weak in WeakMap refers to the keys of the data structure being weakly referenced, a technique that inevitably requires some talk about JavaScript memory usage. In very simple terms, when a JavaScript object reference ceases to be used, the JavaScript engine kicks in and attempts to recover the memory used by said object -- a process known as garbage collection. However, there are circumstances when even stale JavaScript objects aren't able to be 'cleaned up' and their underlying memory is never released, leading to what's called a memory leak. Memory leaks are problematic because they mean memory that could be put to good use is held up until the JavaScript engine finishes or is shut down, a problem that can lead to a JavaScript engine or an underlying system becoming sluggish or crashing due to the starvation of memory resources caused by memory leaks.

And it's in a regular Map data type that uses Object data type keys that JavaScript memory leaks can occur. For example, given an object reference a and a Map reference named languages, an assignment like languages[a] = 'This is the value for language a' will cause a memory leak in the event the a object reference is garbage collected, because the memory is never recovered, something that isn't an issue with a WeakMap data type. For example, given an object reference a and a WeakMap reference named weakLanguages, an assignment like weakLanguages[a] = 'This is the value for language a' causes the WeakMap value associated with the a object to be removed in case the a object reference is garbage collected.

So does this mean you should always use a WeakMap data type over a Map data type to make better memory use ? Not really, as great as the WeakMap data sounds it has certain behaviors you need to be aware of. For starters, a WeakMap data type only works with Object data type keys, so if you need to use keys that are strings, integers or something else that isn't object references you won't be able to use a WeakMap, not to mention it becomes irrelevant to have weakly referenced keys when the keys themselves are concrete values that can't be garbage collected elsewhere. Another factor to consider is because a WeakMap data type has weakly referenced keys, its keys aren't readily accessible like a regular Map data type (e.g. in a Map data type you can use keys() method to instantly get all its keys, but in a WeakMap data type this isn't possible because its keys are weakly referenced and designed to be removed in case they're garbage collected elsewhere). In summary, if you need to manage values related to object reference keys, then a WeakMap can be a better option because it reduces the potential for memory leaks since it removes all data in a WeakMap once object references become stale elsewhere.

ES6: Set and WeakSet object data types

JavaScript ES6 introduced the Set[24] data type with the intent to fulfill another common requirement in software applications: to store unique values. Prior to ES6, the only way to store unique values was to use an Array data type with an ad-hoc mechanism (e.g. loop over elements and compare them among one another), with the Set data type this in no longer necessary as it ignores any duplicate values. Listing 2-11 illustrates several use cases of the Set data type.

Listing 2-11. ES6: JavaScript Set object data type
// Empty Set
 let vowels = new Set();

vowels.add('a').add('e').add('i').add('o').add('u');

// Verify Set value
console.log(vowels.has('a'));
console.log(vowels.has('n'));

// Loop over Set
vowels.forEach(function(value) { 
   console.log(value);
});

// Duplicate Set values are ignored 
console.log(vowels);
vowels.add('e');
console.log(vowels);

// Populate Set on creation 
let primeNumbers = new Set([2,3,5,7,11]);

// Delete Set value 
console.log(primeNumbers);
primeNumbers.delete(11);
console.log(primeNumbers);

Listing 2-11 begins by creating an empty Set instance assigned to the vowels reference and uses the Set's add() method to add various values to the set. Next, the Set's handy has() method is used to validate if the vowels set contains certain values and immediately after the Set's forEach() method is used to loop over the set values and output them to the console. Next, an attempt is made to add the e value to the vowels with the add() method, however, this has no effect because the e value pre-existed in the set. Finally, listing 2-11 illustrates how it's possible to create a Set instance with an initial set of values using an Array and how the Set's delete() method can remove a set element.

Similar to the Map/WeakMap relationship, the Set data type also has an equivalent WeakSet[25] data type designed for performance purposes. Just like it's key-value WeakMap companion explained earlier, a WeakSet data type can only store Object references and does not provide quick access to its values like a regular Set data type (e.g. WeakSet doesn't have forEach method like Set), because by design it has weakly referenced values to facilitate garbage collection. So just like the WeakMap data type, a WeakSet data type is a good option if you need to manage a large group of unique objects because it reduces the potential for memory leaks since it removes all data in a WeakSet once object references become stale elsewhere.

ES6: Improvements to pre-ES6 object data types

Along with the new JavaScript ES6 data types you've explored up to this point, JavaScript ES6 also incorporated new features into pre-exisitng JavaScript data types. Nearly all the object data types in table 2-2 -- some incorporated in ES5, others in earlier ECMAScript versions -- were improved in light of more obvious demands placed on the language.

The JavaScript Object data type incorporated the following methods in ES6:

Listing 2-12. ES6: Object data type improvements


The JavaScript String data type incorporated the following methods in ES6:

The JavaScript Number data type added the following properties and methods in ES6:

The JavaScript Function data type added the following method in ES6:

The JavaScript Array data type incorporated the following methods in ES6:

ES2016 (ES7)

Data types

Array
  • includes() method determines whether an array includes a certain value among its entries, returning true or false as appropriate.
  • ES2017 (ES8)

    Object String

    ES.Next

    Object
  • fromEntries() takes a list of key-value pairs, such as that produced by Object.entries, and returns a new object whose properties are given by those entries. String
  • trimEnd()
  • method.- Removes whitespace from the end of a string. trimRight() is an alias of this method.
  • trimStart()
  • method.- Removes whitespace from the beginning of a string. trimLeft() is an alias of this method. -->

    TypeScript

    1. http://es5.github.io/#x11.9.3    

    2. https://developer.mozilla.org/en-US/docs/Web/API/Window    

    3. https://nodejs.org/api/globals.html#globals_global    

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

    5. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String    

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

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

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

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

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

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

    12. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON    

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

    14. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp    

    15. https://en.wikipedia.org/wiki/Symbol_(programming)    

    16. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol    

    17. https://en.wikipedia.org/wiki/Proxy_pattern    

    18. https://en.wikipedia.org/wiki/Reflection_(computer_programming)    

    19. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy    

    20. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy#Methods_of_the_handler_object    

    21. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect    

    22. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map    

    23. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap    

    24. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set    

    25. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet