CHAPTER 2

image

Features, Functions, and Objects

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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, at http://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.

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 the arguments object into an array, as in Listing 2-14.

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.)

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.

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 using instanceof.

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. Listing 2-18 shows two basic examples of the creation of an object with a set of properties.

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.

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 a TypeError. Existing writable properties can be updated without resulting in an error. Listing 2-20 shows an example.

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.

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.248.0