Features, Functions, and Objects - Pro JavaScript Techniques, Second Edition (2015)

Pro JavaScript Techniques, Second Edition (2015)

2. Features, Functions, and Objects

John Resig1, Russ Ferguson1 and John Paxton1

(1)

NJ, United States

Objects are the fundamental units of JavaScript. Virtually everything in JavaScript is an object and interacts on an object-oriented level. To build up this solid object-oriented language, JavaScript includes an arsenal of features that make it unique in both its foundation and its capabilities.

This chapter covers some of the most important aspects of the JavaScript language, such as references, scope, closures, and context. These are not necessarily the cornerstones of the language, but the elegant arches, which both support and refine JavaScript. We will delve into the tools available for working with objects as data structures. A dive into the nature of object-oriented JavaScript follows, including a discussion of classes vs. prototypes. Finally, the chapter explores the use of object-oriented JavaScript, including exactly how objects behave and how to create new ones. This is quite possibly the most important chapter in this book if taken to heart, as it will completely change the way you look at JavaScript as a language.

Language Features

JavaScript has a number of features that are fundamental to making the language what it is. There are very few other languages like it. We find the combination of features to fit just right, contributing to a deceptively powerful language.

References and Values

JavaScript variables hold data in one of two ways: by copies and references. Anything that is a primitive value is copied into a variable. Primitives are strings, numbers, Booleans, null, and undefined. The most important characteristic of primitives is that they are assigned, copied, and passed to and returned from functions by value.

The rest of JavaScript relies on references. Any variable that does not hold one of the aforementioned primitive values holds a reference to an object. A reference is a pointer to the location in memory of an object (or array, or date, or what-have-you). The actual object (array, date, or whatever) is called the referent. This is an incredibly powerful feature, present in many languages. It allows for certain efficiencies: two (or more!) variables do not have their own copies of an object; they simply refer to the same object. Updates to the referent made via one reference are reflected in the other reference. By maintaining sets of references to objects, JavaScript affords you much more flexibility. An example of this is shown in Listing 2-1, where two variables point to the same object, and the modification of the object’s contents via one reference is reflected in the other reference.

Listing 2-1. Example of Multiple Variables Referring to a Single Object

// Set obj to an empty object

// (Using {} is shorter than 'new Object()')

var obj = {};

// objRef now refers to the other object

var refToObj = obj;

// Modify a property in the original object

obj.oneProperty = true;

// We now see that the change is represented in both variables

// (Since they both refer to the same object)

console.log( obj.oneProperty === refToObj.oneProperty );

// This change goes both ways, since obj and refToObj are both references

refToObj.anotherProperty = 1;

console.log( obj.anotherProperty === refToObj.anotherProperty );

Objects have two features: properties and methods. These are often referred to collectively as the members of an object. Properties contain the data of an object. Properties can be primitives or objects themselves. Methods are functions that act upon the data of an object. In some discussions of JavaScript, methods are included in the set of properties. But the distinction is often useful.

Self-modifying objects are very rare in JavaScript. Let’s look at one popular instance where this occurs. The Array object is able to add additional items to itself using the push method. Since, at the core of an Array object, the values are stored as object properties, the result is a situation similar to that shown in Listing 2-1, where an object becomes globally modified (resulting in multiple variables’ contents being simultaneously changed). An example of this situation can be found in Listing 2-2.

Listing 2-2. Example of a Self-Modifying Object

// Create an array of items

// (Similar to 2-1, using [] is shorter than 'new Array()')

var items = [ 'one', 'two', 'three' ];

// Create a reference to the array of items

var itemsRef = items;

// Add an item to the original array

items.push( 'four' );

// The length of each array should be the same,

// since they both point to the same array object

console.log( items.length == itemsRef.length );

It’s important to remember that references point only to the referent object, not to another reference. In Perl, for example, it’s possible to have a reference point to another variable that also is a reference. In JavaScript, however, it traverses down the reference chain and only points to the core object. An example of this situation can be seen in Listing 2-3, where the physical object is changed but the reference continues to point back to the old object.

Listing 2-3. Changing the Reference of an Object While Maintaining Integrity

// Set items to an array (object) of strings

var items = [ 'one', 'two', 'three' ];

// Set itemsRef to a reference to items

var itemsRef = items;

// Set items to equal a new object

items = [ 'new', 'array' ];

// items and itemsRef now point to different objects.

// items points to [ 'new', 'array' ]

// itemsRef points to [ 'one', 'two', 'three' ]

console.log( items !== itemsRef );

Finally, let’s look at a strange instance that you might think would involve references but does not. When performing string concatenation, the result is always a new string object rather than a modified version of the original string. Because strings (like numbers and Booleans) are primitives, they are not actually referents, and the variables that contain them are not references. This can be seen in Listing 2-4.

Listing 2-4. Example of Object Modification Resulting in a New Object, Not a Self-Modified Object

// Set item equal to a new string object

var item = 'test';

// itemRef now refers to the same string object

var itemRef = item;

// Concatenate some new text onto the string object

// NOTE: This creates a new object and does not modify

// the original object.

item += 'ing';

// The values of item and itemRef are NOT equal, as a whole

// new string object has been created

console.log( item != itemRef );

Strings are often particularly confusing because they act like objects. You can create instances of strings via a call to new String. Strings have properties like length. Strings also have methods like indexOf and toUpperCase. But when interacting with variables or functions, strings are very much primitives.

References can be a tricky subject to wrap your mind around, if you are new to them. Nonetheless, understanding how references work is paramount to writing good, clean JavaScript code. In the next couple of sections we’re going to look at features that aren’t necessarily new or exciting but are important for writing good, clean code.

Scope

Scope is a tricky feature of JavaScript. Most programming languages have some form of scope; the differences lie in the duration of that scope. There are only two scopes in JavaScript: functional scope and global scope. This is deceptively simple. Functions have their own scope, but blocks (such as while, if, and for statements) do not. This may seem strange if you are coming from a block-scoped language. Listing 2-5 shows an example of the implications of function-scoped code.

Listing 2-5. Example of How the Variable Scope in JavaScript Works

// Set a global variable, foo, equal to test

var foo = 'test';

// Within an if block

if ( true ) {

// Set foo equal to 'new test'

// NOTE: This still belongs to the global scope!

var foo = 'new test';

}

// As we can see here, as foo is now equal to 'new test'

console.log( foo === 'new test' );

// Create a function that will modify the variable foo

function test() {

var foo = 'old test';

}

// However, when called, 'foo' remains within the scope

// of the function

test();

// Which is confirmed, as foo is still equal to 'new test'

console.log( foo === 'new test' );

You’ll notice that in Listing 2-5, the variables are within the global scope. All globally scoped variables are actually visible as properties of the window object in browser-based JavaScript. In other environments, there will be a global context to which all globally-scoped variables belong.

In Listing 2-6 a value is assigned to a variable, foo, within the scope of the test() function. However, nowhere in Listing 2-6 is the scope of the variable actually declared (using var foo). When the foo variable isn’t explicitly scoped, it will become defined globally, even though it is only intended to be used within the context of the function.

Listing 2-6. Example of Implicit Globally Scoped Variable Declaration

// A function in which the value of foo is set

function test() {

foo = 'test';

}

// Call the function to set the value of foo

test();

// We see that foo is now globally scoped

console.log( window.foo === 'test' );

JavaScript’s scoping is often a source of confusion. If you are coming from a block-scoped language, this confusion can lead to accidentally global variables, as shown here. Often, this confusion is compounded by imprecise usage of the var keyword. For simplicity’s sake, the pro JavaScript programmer should always initialize variables with var, regardless of scope. This way, your variables will have the scope you expected, and you can avoid accidental globals.

When declaring variables within a function, be aware of the issue of hoisting. Any variable declared within a function has its declaration (not the value it is initialized with) hoisted to the top of the scope. JavaScript does this to ensure that the variable’s name is available throughout the scope.

Especially when we combine scope with the concept of context and closures, discussed in the next two sections, JavaScript reveals itself as a powerful scripting language.

Context

Your code will always have some form of context (a scope within which the code is operating). Context can be a powerful tool and is essential for object-oriented code. It is a common feature of other languages, but JavaScript, as is often the case, has a subtly different take on it.

You access context through the variable this, which will always refer to the context that the code is running inside. Recall that global objects are actually properties of the window object. This means that even in a global context, this will still refer to an object. Listing 2-7 shows some simple examples of working with context.

Listing 2-7. Examples of Using Functions Within Context and Then Switching Context to Another Variable

function setFoo(fooInput) {

this.foo = fooInput;

}

var foo = 5;

console.log( 'foo at the window level is set to: ' + foo );

var obj = {

foo : 10

};

console.log( 'foo inside of obj is set to: ' + obj.foo );

// This will change window-level foo

setFoo( 15 );

console.log( 'foo at the window level is now set to: ' + foo );

// This will change the foo inside the object

obj.setFoo = setFoo;

obj.setFoo( 20 );

console.log( 'foo inside of obj is now set to: ' + obj.foo );

In Listing 2-7, our setFoo function looks a bit odd. We do not typically use this inside a generic utility function. Knowing that we were eventually going to attach setFoo to obj, we used this so we could access the context of obj. However, this approach is not strictly necessary. JavaScript has two methods that allow you to run a function in an arbitrary, specified context. Listing 2-8 shows the two methods, call and apply, that can be used to achieve just that.

Listing 2-8. Examples of Changing the Context of Functions

// A simple function that sets the color style of its context

function changeColor( color ) {

this.style.color = color;

}

// Calling it on the window object, which fails, since it doesn't

// have a style object

changeColor('white' );

// Create a new div element, which will have a style object

var main = document.createElement('div');

// Set its color to black, using the call method

// The call method sets the context with the first argument

// and passes all the other arguments as arguments to the function

changeColor.call( main, 'black' );

//Check results using console.log

//The output should say 'black'

console.log(main.style.color);

// A function that sets the color on the body element

function setBodyColor() {

// The apply method sets the context to the body element

// with the first argument, and the second argument is an array

// of arguments that gets passed to the function

changeColor.apply( document.body, arguments );

}

// Set the background color of the body to black

setBodyColor('black' );

While the usefulness of context may not be immediately apparent, it will become clearer when we look at object orientation soon.

Closures

Closures are a means through which an inner function can refer to the variables present in its outer enclosing function after its parent functions have already terminated. That’s the technical definition, anyway. Perhaps it is more useful to think of closures tied to contexts. Up to this point, when we have defined an object literal, that object was open for modification. We have seen that we can add properties and functions to the object at any time. But what if we wanted a context that was locked? A context that “saved” values as defaults. What about a context that could not be accessed without the API we provide? This is what a closure provides: a context that is accessible only in the manner we choose.

This topic can be very powerful and very complex. We highly recommend referring to the sites mentioned at the end of this section, as they have some excellent information about closures.

Let’s begin by looking at two simple examples of closures, shown in Listing 2-9.

Listing 2-9. Two Examples of How Closures Can Improve the Clarity of Your Code

// Find the element with an ID of 'main'

var obj = document.getElementById('main');

// Change its border styling

obj.style.border = '1px solid red';

// Initialize a callback that will occur in one second

setTimeout(function(){

// Which will hide the object

obj.style.display = 'none';

}, 1000);

// A generic function for displaying a delayed alert message

function delayedAlert( msg, time ) {

// Initialize an enclosed callback

setTimeout(function(){

// Which utilizes the msg passed in from the enclosing function

console.log( msg );

}, time );

}

// Call the delayedAlert function with two arguments

delayedAlert('Welcome!', 2000 );

The first function call to setTimeout shows an instance where new JavaScript developers often have problems. It’s not uncommon to see code like this in a new developer’s program:

setTimeout('otherFunction()', 1000);

or even...

setTimeout('otherFunction(' + num + ',' + num2 + ')', 1000);

In both examples, the functions being called are expressed as strings. This can cause problems with the minification process when you are about to move your code into production. By using closures, you can call functions, use variables, and pass parameters as originally intended.

Using the concept of closures, it’s entirely possible to circumnavigate this mess of code. The first example in Listing 2-9 is simple; there is a setTimeout callback being called 1,000 milliseconds after it is first called, but still referring to the obj variable (which is defined globally as the element with an ID of main). The second function defined, delayedAlert, shows a solution to the setTimeout mess that occurs, along with the ability to have closures within function scope.

You should find that when using simple closures such as these in your code, the clarity of what you’re writing increases instead of turning into a syntactical soup.

Let’s look at a fun side effect of what’s possible with closures. In some functional programming languages, there’s the concept of currying, a way to prefill a number of arguments to a function, creating a new, simpler function. Listing 2-10 has a simple example of currying, creating a new function that prefills an argument to another function.

Listing 2-10. Example of Function Currying Using Closures

// A function that generates a new function for adding numbers

function addGenerator( num ) {

// Return a simple function for adding two numbers

// with the first number borrowed from the generator

return function( toAdd ) {

return num + toAdd

};

}

// addFive now contains a function that takes one argument,

// adds five to it, and returns the resulting number

var addFive = addGenerator( 5 );

// We can see here that the result of the addFive function is 9,

// when passed an argument of 4

console.log( addFive( 4 ) == 9 );

There’s another common JavaScript-coding problem that closures can solve. New JavaScript developers often accidentally leave a lot of extra variables sitting in the global scope. This is generally considered bad practice, as those extra variables could quietly interfere with other libraries, causing confusing problems to occur. Using a self-executing anonymous function, you can essentially hide all normally global variables from being seen by other code, as shown in Listing 2-11.

Listing 2-11. Example of Using Anonymous Functions to Hide Variables from the Global Scope

// Create a new anonymous function, to use as a wrapper

(function(){

// The variable that would normally be global

var msg = 'Thanks for visiting! ';

// Binding a new function to a global object

window.onload = function(){

// Which uses the 'hidden' variable

console.log( msg );

};

// Close off the anonymous function and execute it

})();

Finally, let’s look at one problem that occurs with closures. Remember that a closure allows you to reference variables that exist within the parent function. However, it does not provide the value of the variable at the time it is created; it provides the last value of the variable within the parent function. You’ll most commonly see this occur during a for loop. There is one variable being used as the iterator (i). Inside the for loop, new functions are being created that utilize the closure to reference the iterator again. The problem is that by the time the new closured functions are called, they will reference the last value of the iterator (that is, the last position in an array), not the value that you would expect. Listing 2-12 shows an example of using anonymous functions to induce scope, to create an instance where expected closure is possible.

Listing 2-12. Example of Using Anonymous Functions to Induce the Scope Needed to Create Multiple Closure-Using Functions

// An element with an ID of main

var obj = document.getElementById('main');

// An array of items to bind to

var items = ['click', 'keypress' ];

// Iterate through each of the items

for ( var i = 0; i < items.length; i++ ) {

// Use a self-executed anonymous function to induce scope

(function(){

// Remember the value within this scope

// Each 'item' is unique.

//Not relying on variables created in the parent context.

var item = items[i];

// Bind a function to the element

obj['on' + item ] = function() {

// item refers to a parent variable that has been successfully

// scoped within the context of this for loop

console.log('Thanks for your ' + item );

};

})();

}

We will return to closures in our section on object-oriented code, where they will help us to implement private properties.

The concept of closures is not a simple one to grasp; it took us a lot of time and effort to truly wrap our minds around how powerful closures are. Luckily, there are some excellent resources explaining how closures work in JavaScript: “JavaScript Closures” by Richard Cornford, athttp://jibbering.com/faq/faq_notes/closures.html , and another explanation at the Mozilla Developer Network, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures .

Function Overloading and Type-Checking

A common feature in other object-oriented languages is the ability to overload functions to perform different behaviors depending on the type or number of arguments passed in. While this ability isn’t a language feature in JavaScript, we can use existing capabilities to implement overloading of functions.

Our overloaded functions need to know two things: how many arguments have been passed in and what type of arguments have been passed. Let’s start by looking at the number of arguments provided.

Inside every function in JavaScript there exists a contextual variable named arguments that acts as an array-like object containing all the, well, arguments passed into the function. The arguments object isn’t a true array; it does not share a prototype with Array, and it does not have array-processing functions like push or indexOf. It does have positional array access (for example, arguments[2] returns the third argument), and there is a length property. There are two examples of this in Listing 2-13.

Listing 2-13. Two Examples of Function Overloading in JavaScript

// A simple function for sending a message

function sendMessage( msg, obj ) {

// If both a message and an object are provided

if ( arguments.length === 2 ) {

// Send the message to the object

// (Assumes that obj has a log property!)

obj.log( msg );

} else {

// Otherwise, assume that only a message was provided

// So just display the default error message

console.log( msg );

}

}

// Both of these function calls work

sendMessage( 'Hello, World!' );

sendMessage( 'How are you?', console );

You may wonder if there is a way to have the full functionality of an array available to the arguments object. It is not possible with arguments itself, but it is possible to create a copy of arguments that is an array. By invoking the slice method from the Array prototype, we can quickly copy thearguments object into an array, as in Listing 2-14.

Listing 2-14. Converting Arguments to an Array

function aFunction(x, y, z) {

var argsArray = Array.prototype.slice.call( arguments, 0 );

console.log( 'The last argument is: ' + argsArray.pop() );

}

// Will output 'The last argument is 3'.

aFunction( 1, 2, 3 );

We will learn more about the prototype property very soon. For the moment, suffice it to say that the prototype allows us to access object methods in a static manner.

What if the message were not defined? We need to be able to check not just for the presence of an argument, but also its absence. We can take advantage of the fact that any argument that isn’t provided has a value of undefined. Listing 2-15 shows a simple function for displaying an error message and providing a default message if a particular argument is not provided. (Note that we must use typeof here, because otherwise, an argument with the literal string “undefined” would indicate an error.)

Listing 2-15. Displaying an Error Message and a Default Message

function displayError( msg ) {

// Check and make sure that msg is not undefined

if ( typeof msg === 'undefined' ) {

// If it is, set a default message

msg = 'An error occurred.';

}

// Display the message

console.log( msg );

}

displayError();

The use of the typeof statement helps to lead us into the topic of type-checking. Because JavaScript is a dynamically typed language, this proves to be a very useful and important topic. There are a number of ways to check the type of a variable; we’re going to look at two that are particularly useful.

The first way of checking the type of an object is by using the obvious-sounding typeof operator. This utility gives us a string name representing the type of the contents of a variable. An example of this method can be seen in Listing 2-16.

Listing 2-16. Example of Using typeof to Determine the Type of an Object

var num = '50';

var arr = 'apples,oranges,pears';

// Check to see if our number is actually a string

if ( typeof num === 'string' ) {

// If it is, then parse a number out of it

num = parseInt( num );

}

// Check to see if our array is actually a string

if ( typeof arr == 'string' ) {

// If that's the case, make an array, splitting on commas

arr = arr.split( ',' );

}

The advantage of typeof is that you do not have to know what the actual type of the tested variable is. This would be the perfect solution except that for variables of type Object or Array, or a custom object such as User, typeof only returns “object”, making it hard to differentiate between specific object types. The next two ways to figure out the type of a variable require you to test against a specific existing type.

The second way to check the type of an object is to use the instanceof operator. This operator checks the left operand against the constructor of the right operand, which may sound a bit more complex than it actually is! Take a look at Listing 2-17, showing an example of usinginstanceof.

Listing 2-17. Example of Using instanceof

var today = new Date();

var re = /[a-z]+/i;

// These don't give us enough details

console.log('typeof today: ' + typeof today);

console.log('typeof re: ' + typeof re);

// Let's find out if the variables are of a more specific type

if (today instanceof Date) {

console.log('today is an instance of a Date.');

}

if (re instanceof RegExp) {

console.log( 're is an instance of a RegExp object.' );

}

In the next chapter, when we look at object-oriented JavaScript, we will discuss the Object.isPrototypeOf() function, which also helps in type determination.

Type-checking variables and verifying the length of argument arrays are simple concepts at heart but can be used to provide complex methods that can adapt and provide a better experience to both the developer and code users. When you need specific type-checking (is this an Array? Is it a Date? A specific type of custom object?), we advise creating a custom function for determining the type. Many frameworks have convenience functions for determining Arrays, Dates, and so on. Encapsulating this code into a function ensures that you have one and only one place to check for that specific type, instead of having checking code scattered throughout your codebase.

New Object Tools

One of the more exciting developments in JavaScript the language has been the expansion of tools for managing objects. As we will see, these tools can be used on object literals (which are more like data structures) and on object instances.

Objects

Objects are the foundation of JavaScript. Virtually everything within the language is an object. Much of the power of the language is derived from this fact. At their most basic level, objects exist as a collection of properties, almost like a hash construct that you see in other languages. Listing2-18 shows two basic examples of the creation of an object with a set of properties.

Listing 2-18. Two Examples of Creating a Simple Object and Setting Properties

// Creates a new Object object and stores it in 'obj'

var obj = new Object();

// Set some properties of the object to different values

obj.val = 5;

obj.click = function(){

console.log('hello');

};

// Here is some equivalent code, using the {...} shorthand

// along with key-value pairs for defining properties

var obj = {

// Set the property names and values using key/value pairs

val: 5,

click: function(){

console.log('hello');

}

};

In reality there isn’t much more to objects than that. Where things get tricky, however, is in the creation of new objects, especially ones that inherit the properties of other objects.

Modifying Objects

JavaScript now has three methods that can help you control whether an object can be modified. We will look at them on a scale of restrictiveness, from least to greatest.

An object in JavaScript by default can be modified at any time. By using Object.preventExtensions(), you can prevent new properties from being added to the object. When this happens, all current properties can be used but no new ones can be added. Trying to add a new property will result in a TypeError—or will fail silently; you are more likely to see the error when running in strict mode. Listing 2-19 shows an example.

Listing 2-19. An example of using Object.preventExtensions()

// Creates a new object and stores it in 'obj'

var obj = {};

// Creates a new Object object using preventExtensions

var obj2 = Object.preventExtensions(obj);

// Generates TypeError when trying to define a new property

function makeTypeError(){

'use strict';

//Generates TypeError when trying to define a new property

Object.defineProperty(obj2, 'greeting',{value: 'Hello World'});

}

makeTypeError();

Using Object.seal(), you can restrict the ability of an object, similar to what you did with Object.preventExtensions(). Unlike our previous example, however, properties cannot be deleted or converted into accessors (getter methods). Trying to delete or add properties will also result in aTypeError. Existing writable properties can be updated without resulting in an error. Listing 2-20 shows an example.

Listing 2-20. An example of using Object.seal()

// Creates a new object and uses object.seal to restrict it

var obj = {};

obj.greeting = 'Welcome';

Object.seal(obj);

//Updating the existing writable property

//Cannot convert existing property to accessor, throws TypeErrors

obj.greeting = 'Hello World';

Object.defineProperty(obj, 'greeting', {get:function(){return 'Hello World'; } });

// Cannot delete property, fails silently

delete obj.greeting;

function makeTypeError(){

'use strict';

//Generates TypeError when trying to delete a property

delete obj.greeting;

//Can still update property

obj.greeting = 'Welcome';

console.log(obj.greeting);

}

makeTypeError();

Object.freeze(), demonstrated in Listing 2-21, is the most restrictive of the three methods. Once it is used, an object is considered immutable. Properties cannot be added, deleted or updated. Any attempts will result in a TypeError. If a property is itself an object, that can be updated. This is called a shallow freeze. In order to make an object fully immutable, all properties whose values contain objects must also be frozen.

Listing 2-21. An example of using Object.freeze()

//Creates a new object with two properties. Second property is an object

var obj = {

greeting: "Welcome",

innerObj: {}

};

//Freeezes our obj

Object.freeze(obj);

//silently fails

obj.greeting = 'Hello World';

//innerObj can still be updated

obj.innerObj.greeting = 'Hello World';

console.log('obj.innerObj.greeting = ' + obj.innerObj.greeting);

//Cannot convert existing property to accessor

//Throws TypeError

Object.defineProperty(obj, 'greeting', {get:function(){return 'Hello World'; } });

// Cannot delete property, fails silently

delete obj.greeting;

function makeTypeError(){

'use strict';

}

//Generates TypeError when trying to delete a property

delete obj.greeting;

//Freeze inner object

Object.freeze(obj.innerObj);

//innerObj is now frozen. Fails silently

obj.innerObj.greeting = 'Worked so far...';

function makeTypeError(){

'use strict';

//all attempts will throw TypeErrors

delete obj.greeting;

obj.innerObj.greeting = 'Worked so far...';

obj.greeting = "Welcome";

};

makeTypeError();

By understanding how you can control the mutability of an object, you can create a level of consistency. For example, if you have an object named User, you can be sure that every new object based on that will have the same properties as the first. Any properties that could be added at runtime would fail.

Summary

The importance of understanding the concepts outlined in this chapter cannot be understated. The first half of the chapter, giving you a good understanding of how JavaScript behaves and how it can be best used, is the starting point for fully grasping how to use JavaScript professionally. Simply understanding how objects act, references are handled, and scope is decided can unquestionably change how you write JavaScript code.

Building on these skills, advanced techniques provide us with additional ways to solve problems with JavaScript. Understanding scope and context led to using closures. Looking into how to determine types in JavaScript allowed us to add function overloading to a language that doesn’t have it as a native feature. And then we spent time with one of the foundational types in JavaScript: the Object. The various new features in the Object type allow us much greater control over the object literals we create. This will lead naturally into the next chapter, where we start building our own object-oriented JavaScript.