Table of Contents
- Encapsulation - Data can be grouped together with functionality that operates on that data. This, quite simply, is the definition of an object.
- Aggregation - One object can reference another object.
- Inheritance - A newly created object has the same characteristics as another object without explicitly duplicating its functionality.
- Polymorphism One interface may be implemented by multiple objects.
- Web application and Node.js developers trying to structure their code more effectively
Chapter 4: Constructors and Prototypes expands on the previous discussion of functions by looking more specifically at constructors. All constructors are functions, but they are used a little bit differently. This chapter explores the differences while also talking about creating your own custom types.
I’d like to thank Kate Matsudaira for convincing me that self-publishing an ebook was the best way to get this information out. Without her advice, I’d probably still be trying to figure out what I should do with the information contained in this book.
Thanks to Rob Friesel for once again providing excellent feedback on an early copy of this book, and Cody Lindley for his suggestions. Additional thanks to Angus Croll for his technical review of the finished version — his nitpicking made this book much better.
Thanks as well to Bill Pollock, whom I met at a conference and who started the ball rolling on publishing this book with No Starch Press.
If you have questions, comments, or other feedback about this book, please visit the mailing list at: http://groups.google.com/group/zakasbooks.
Of course, there are other differences between primitive and reference types.
Primitive types represent simple pieces of data that are stored as is, such as
- Number - Any integer or floating-point numeric value
Null - A primitive type that has only one value,
Undefined - A primitive type has only one value,
undefinedis the value assigned to a variable that is not initialized)
The first three types (Boolean, number, and string) behave in similar ways, while the last two (null and undefined) work a bit differently, as will be discussed throughout this chapter. All primitive types have literal representations of their values. Literals represent values that aren’t stored in a variable, such as a hardcoded name or price. Here are some examples of each type using its literal form:
color1 is assigned the value of
"red". The variable
color2 is then assigned the value of
color1, which stores
color2. Even though
color2 contain the same value, they are completely separate from each another, and you can change the value in
color1 without affecting
color2 and vice versa. That’s because there are two different storage locations, one for each variable. Figure 1-1 illustrates the variable object for this snippet of code.
Because each variable containing a primitive value uses its own storage space, changes to one variable are not reflected on the other. For example:
In this code,
color1 is changed to
color2 retains its original value of
The best way to identify primitive types is with the
typeof operator, which works on any variable and returns a string indicating the type of data. The
typeof operator works well with strings, numbers, Booleans, and undefined. The following shows the output when using
typeof on different primitive values:
As you might expect,
typeof returns “string” when the value is a string; “number” when the value is a number (regardless of integer or floating-point values); “boolean” when the value is a Boolean; and “undefined” when the value is undefined.
The tricky part involves null.
You wouldn’t be the first developer to be confused by the result of this line of code:
When you run
return value, but that’s still confusing.)
The best way to determine if a value is null is to compare it against null directly, like this:
Despite the fact that they’re primitive types, strings, numbers, and Booleans actually have methods. (The
undefined types have no methods.) Strings, in particular, have numerous methods to help you work with them. For example:
Of course, you must create objects before you can begin working
There are a couple of ways to create, or instantiate, objects. The first is to use the
Reference types do not store the object directly into the variable to which it is assigned, so the
object variable in this example doesn’t actually contain the object instance. Instead, it holds a pointer (or reference) to the location in memory where the object exists. This is the primary difference between objects and primitive values, as the primitive is stored directly in the variable.
When you assign an object to a variable, you’re actually assigning a pointer. That means if you assign one variable to another, each variable gets a copy of the pointer, and both still reference the same object in memory. For example:
This code first creates an object (with new) and stores a reference in
object2 is assigned the value of
object1. There is still only the one instance of the object that was created on the first line, but both variables now point to that object, as illustrated in Figure 1-3.
object1 is created and used before finally being set to
null. When there are no more references to an object in memory, the garbage collector can use that memory for something else. (Dereferencing objects is especially important in very large applications that use millions of objects)
myCustomProperty is added to
object1 with a value of “Awesome!”. That property is also accessible on
object2 because both
object2 point to the same object.
You’ve seen how to create and interact with generic objects created with
The built-in types are:
- Array - An ordered list of numerically indexed values
- Date - A date and time
- Error - A runtime error (there are also several more specific
- Function - A function
- Object - A generic object
- RegExp - A regular expression
You can instantiate each built-in reference type using
new, as shown here:
Several built-in reference types have literal forms. A literal is syntax that allows you to define a reference value without explicitly creating an object, using the new operator and the object’s constructor. (Earlier in this chapter, you saw examples of primitive literals including string literals, numeric literals, Boolean literals, the
null literal, and the
To create an object with object literal syntax, you can define the properties of a new object inside braces. Properties are made up of an identifier or string, a colon, and a value, with multiple properties separated by commas.
You can also use string literals as property names, which is useful when you want a property name to have spaces or other special characters:
This example is equivalent to the previous one despite the syntactic differences. Both examples are also logically equivalent to the following:
The outcome of each of the previous three examples is the same: an object with two properties. The choice of pattern is up to you because the functionality is ultimately the same.
You can define an array literal in a similar way by enclosing any number of comma-separated values inside square brackets. For example:
This code is equivalent to the following:
You almost always define functions using their literal form. In fact, using the
Function constructor is typically discouraged given the challenges of maintaining, reading, and debugging a string of code rather than actual code, so you’ll rarely see it in code.
Creating functions is much easier and less error prone when you use the literal form. For example:
This code defines the
reflect() function, which returns any value passed to it. Even in the case of this simple function, the literal form is easier to write and understand than the constructor form. Further, there
RegExp constructor. Regular expression literals look very similar to regular expressions in Perl: The pattern is contained between two slashes, and any additional options are single characters following the second slash. For example:
RegExp constructor, you pass the pattern in as a string, so you have to escape any backslashes. (That’s why
\d is used in the literal and
\\d is used in the constructor.) Regular expression literals are preferred over the constructor form in Java Script except when the regular expression is being constructed dynamically from one or more strings.
That said, with the exception of
Function, there really isn’t any right or wrong way to instantiate built-in types. Many developers prefer literals, while some prefer constructors. Choose whichever method you find more comfortable to use.
For example, you could write this code, which uses dot notation:
With bracket notation, the name of the method is now included in a string enclosed by square brackets, as in this example:
This syntax is very useful when you want to dynamically decide which property to access. For example, here bracket notation allows you to use a variable instead of the string literal to specify the property to access.
In this listing, the variable method has a value of
push() is called on the array. This capability is quite useful, as you’ll see throughout this book. The point to remember is that, other than syntax, the only difference—performance or otherwise—between dot notation and bracket notation is that bracket notation allows you to use special characters in property names. Developers tend to find dot notation easier to read, so you’ll see it used more frequently than bracket notation.
A function is the easiest reference type to identify because when you use the
typeof operator on a function, the operator should return
Other reference types are trickier to identify because, for all reference types other than functions,
instanceof operator takes an object and a constructor as parameters. When the value is an instance of the type that the constructor specifies,
true; otherwise, it returns false, as you can see here:
In this example, several values are tested using
instanceof and a constructor. Each reference type is correctly identified by using
instanceof and the constructor that represents its true type (even though the constructor wasn’t used in creating the variable).
instanceof operator can identify inherited types. That means every object is actually an instance of
Object because every reference type inherits from
To demonstrate, the following listing examines the three references previously created with
Each reference type is correctly identified as an instance of
Object, from which all reference types inherit.
Array, and all other built-in types. As a result, when you pass an array from one frame to another,
instanceof doesn’t work because the
array is actually an instance of
Array from a different frame.
To solve this problem, ECMAScript 5 introduced Array.isArray(), which definitively identifies the value as an instance of
Array regardless of the value’s origin. This method should return true when it receives a value that is a native array from any context. If your environment is ECMAScript 5 compliant,
Array.isArray() is the best way to identify arrays:
Array.isArray() method is supported in most environments, both in browsers and in Node.js. This method isn’t supported in Internet Explorer 8 and earlier.
Boolean). These special reference types exist to make working with primitive values as easy as working with objects. (It would be very confusing if you had to use a different syntax or switch to a procedural style just to get a substring of text.)
The primitive wrapper types are reference types that are automatically created behind the scenes whenever strings, numbers, or Booleans are read. For example, in the first line of this listing, a primitive string value is assigned to
name. The second line treats
name like an object and
charAt(0) using dot notation.
This is what happens behind the scenes:
String so that
charAt(0) will work. The
String object exists only for one statement before it’s destroyed (a process called autoboxing). To test this out, try adding a property to a string as if it were a regular object:
This code attempts to add the property last to the string
name. The code itself is just fine except that the property disappears. What happened? When working with regular objects, you can add properties at any time
and they stay until you manually remove them. With primitive wrapper types, properties seem to disappear because the object on which the property was assigned is destroyed immediately afterward.
Instead of assigning a new property to a string, the code actually creates a new property on a temporary object that is then destroyed. When you try to access that property later, a different object is temporarily created and the new property doesn’t exist there. Although reference values are created automatically for primitive values, when
instanceof checks for these types of values the result is
instanceof operator returns
false because a temporary object is created only when a value is read. Because
instanceof doesn’t actually read anything, no temporary objects are created, and it tells us the values aren’t instances of primitive wrapper types. You can create primitive wrapper types manually, but there are certain side effects:
As you can see, creating an instance of the primitive wrapper type just creates another object, which means that
typeof can’t identify the type of data you intend to store.
In addition, you can’t use
Boolean objects as you would primitive values. For example, the following code uses a
Boolean object. The
Boolean object is
false, yet console.log(“Found”) still executes because an object is always considered true inside a conditional statement. It doesn’t matter that the object represents false; it’s an object, so it evaluates to
Manually instantiating primitive wrappers can also be confusing in other ways, so unless you find a special case where it makes sense to do so, you should avoid it. Most of the time, using primitive wrapper objects instead of primitives only leads to errors.
undefined)represent simple values stored directly in the variable object for a given context. You can use
typeof to identify primitive types with the exception of
null, which must be compared directly against the special value
typeof operator. You should use
instanceof with a constructor to identify objects of any other reference type.