Chapter 1. JavaScript Objects
Creating Objects
In JavaScript, objects are king: Almost everything is an object or acts like an object. Understand objects and you will understand JavaScript. So let’s examine the creation of objects in JavaScript.
An object is just a container for a collection of named values (a.k.a. properties). Before we look at any JavaScript code, let’s first reason this out. Take myself, for example. Using plain language, we can express in a table, a “cody”:
cody | |
property: | property value: |
living | true |
age | 33 |
gender | male |
The word “cody” in the table above is just a label for the group of property names and corresponding values that make up exactly what a cody is. As you can see from the table, I am living, 33, and a male.
JavaScript, however, does not speak in tables. It speaks in objects, which are not unlike the parts contained in the “cody” table. Translating the above table into an actual JavaScript object would look like this:
<!DOCTYPE html><html lang="en"><body><script> // create the cody object... var cody = new Object(); // then fill the cody object with properties (using dot notation) cody.living = true; cody.age = 33; cody.gender = 'male'; console.log(cody); // logs Object {living = true, age = 33, gender = 'male'} </script></body></html>
Keep this at the forefront of your mind: objects are really just
containers for properties, each of which has a name and a value. This
notion of a container of properties with named values (i.e., an object) is
used by JavaScript as the building blocks for expressing values in
JavaScript. The cody
object is a value
which I expressed as a JavaScript object by creating an object, giving the
object a name, and then giving the object properties.
Up to this point, the cody
object
we are discussing has only static information. Since we are dealing with a
programing language, we want to program our cody
object to actually do something. Otherwise,
all we really have is a database, akin to JSON. In order to bring the cody
object to life, I need to add a property
method. Property methods perform a function. To be
precise, in
JavaScript, methods are properties that contain a Function()
object, whose intent is to operate on
the object the function is contained within.
If I were to update the cody
table with a getGender
method, in plain
English it would look like this:
cody | |
property: | property value: |
living | true |
age | 33 |
gender | male |
getGender | return the value of gender |
Using JavaScript, the getGender
method from the updated “cody” table above would look like this:
<!DOCTYPE html><html lang="en"><body><script> var cody = new Object(); cody.living = true; cody.age = 33; cody.gender = 'male'; cody.getGender = function(){return cody.gender;}; console.log(cody.getGender()); // logs 'male' </script></body></html>
The getGender
method, a property
of the cody
object, is used to return
one of cody
’s other property values:
the value “male” stored in the gender
property. What you must realize is that without methods, our object would
not do much except store static properties.
The cody
object we have discussed
thus far is what is known as an Object()
object. We created the cody
object using a blank object that was
provided to us by invoking the Object()
constructor function. Think of constructor functions as a template or
cookie cutter for producing pre-defined objects. In the case of the
cody
object, I used the Object()
constructor function to produce an
empty object which I named cody
. Now
since cody
is an object constructed
from the Object()
constructor, we call
cody
an Object()
object. What you really need to grok,
beyond the creation of a simple Object()
object like cody
, is that the majority of values expressed
in JavaScript are objects (primitive values like “foo”, 5, and true
are the exception but have equivalent
wrapper objects).
Consider that the cody
object
created from the Object()
constructor
function is not really different from, say, a string object created via
the String()
constructor function. To
drive this fact home, examine and contrast the code below:
<!DOCTYPE html><html lang="en"><body><script> var myObject = new Object(); // produces an Object() object myObject['0'] = 'f'; myObject['1'] = 'o'; myObject['2'] = 'o'; console.log(myObject); // logs Object { 0="f", 1="o", 2="o"} var myString = new String('foo'); // produces a String() object console.log(myString); // logs foo { 0="f", 1="o", 2="o"} </script></body></html>
As it turns out, myObject
and
myString
are both…objects! They both
can have properties, inherit properties, and are produced from a
constructor function. The myString
variable containing the 'foo'
string
value seems to be as simple as it goes, but amazingly it’s got an object
structure under its surface. If you examine both of the objects produced,
you will see that they are identical objects in substance but not in type.
More importantly, I hope you begin to see that JavaScript uses objects to
express values.
Note
You might find it odd to see the string value
'foo'
in the object form because typically a string
is represented in JavaScript as a primitive value (e.g., var myString = 'foo';
). I specifically used a
string object value here to highlight that anything can be an object,
including values that we might not typically think of as an object
(i.e., string, number, boolean). Also, I think this helps explain why
some say that everything in JavaScript can be an object.
JavaScript bakes the String()
and
Object()
constructor functions into the
language itself to make the creation of a String()
object and Object()
object trivial. But you, as a coder of
the JavaScript language, can also create equally powerful constructor
functions. Below, I demonstrate this by defining a non-native custom
Person()
constructor function, so that
I can create people from it.
<!DOCTYPE html><html lang="en"><body><script> /* define Person constructor function in order to create custom Person() objects later */ var Person = function(living, age, gender) { this.living = living; this.age = age; this.gender = gender; this.getGender = function() {return this.gender;}; }; // instantiate a Person object and store it in the cody variable var cody = new Person(true, 33, 'male'); console.log(cody); /* The String() constructor function below, having been defined by JavaScript, has the same pattern. Because the string constructor is native to JavaScript, all we have to do to get a string instance is instantiate it. But the pattern is the same whether we use native constructors like String() or user-defined constructors like Person(). */ // instantiate a String object stored in the myString variable var myString = new String('foo'); console.log(myString); </script></body></html>
The user-defined Person()
constructor function can produce person objects, just as the
native String()
constructor function
can produce string objects. The Person()
constructor is no less capable, and is
no more or less malleable, than the native String()
constructor or any of the native
constructors found in JavaScript.
Remember how the cody
object we
first looked at was produced from an Object()
. It’s important to note that the
Object()
constructor function and the
new Person()
constructor shown in the
last code example can give us identical outcomes. Both can produce an
identical object with the same properties and property methods. Examine
the two sections of code below, showing that codyA
and codyB
have the same object values, even though
they are produced in different ways.
<!DOCTYPE html><html lang="en"><body><script> // create a codyA object using the Object() constructor var codyA = new Object(); codyA.living = true; codyA.age = 33; codyA.gender = 'male'; codyA.getGender = function() {return codyA.gender;}; console.log(codyA); // logs Object {living=true, age=33, gender="male", ...} /* The same cody object is created below, but instead of using the native Object() constructor to create a one-off cody, we first define our own Person() constructor that can create a cody object (and any other Person object we like) and then instantiate it with "new". */ var Person = function(living, age, gender) { this.living = living; this.age = age; this.gender = gender; this.getGender = function() {return this.gender;}; }; // logs Object {living=true, age=33, gender="male", ...} var codyB = new Person(true, 33, 'male'); console.log(codyB); </script></body></html>
The main difference between the codyA
and codyB
objects is not found in the object itself,
but in the constructor functions used to produce the objects. The codyA
object was produced using an instance of
the Object()
constructor. The Person()
constructor constructed codyB
but can also be used as a powerful,
centrally defined object “factory” to be used for creating more Person()
objects. Crafting your own constructors
for producing custom objects also sets up prototypal inheritance for
Person()
instances.
Both solutions resulted in the same complex object being created. It’s these two patterns that are the most commonly used for constructing objects.
JavaScript is really just a language that is pre-packaged with a few
native object constructors used to produce complex objects which express a
very specific type of value (e.g., numbers, strings, functions, object,
arrays, etc.), as well as the raw materials via Function()
objects for crafting user-defined
object constructors [e.g., Person()
].
The end result—no matter the pattern for creating the object—is typically
the creation of a complex object.
Understanding the creation, nature, and usage of objects and their primitive equivalents is the focus of the rest of this book.
JavaScript Constructors Construct and Return Object Instances
The role of a constructor function is to create multiple objects that share certain qualities and behaviors. Basically a constructor function is a cookie cutter for producing objects that have default properties and property methods.
If you said, “A constructor is nothing more than a function,” then I
would reply, “You are correct—unless that function is invoked using the
new
keyword.” [e.g., new String('foo')
]. When this happens, a
function takes on a special role, and JavaScript treats the function as
special by setting the value of this
for the function to the new object that is being constructed. In addition
to this special behavior, the function will return the newly created
object (i.e., this
) by default instead
of a falsey value. The new object that is returned from the function is
considered to be an instance of the constructor function that constructs
it.
Consider the Person()
constructor
again, but this time read the comments in the code below carefully, as
they highlight the effect of the new
keyword.
<!DOCTYPE html><html lang="en"><body><script> /* Person is a constructor function. It was written with the intent of being used with the new keyword. */ var Person = function Person(living, age, gender) { /* "this" below is the new object that is being created (i.e., this = new Object();) */ this.living = living; this.age = age; this.gender = gender; this.getGender = function() {return this.gender;}; /* when the function is called with the new keyword "this" is returned instead of undefined */ }; // instantiate a Person object named cody var cody = new Person(true, 33, 'male'); // cody is an object and an instance of Person() console.log(typeof cody); // logs object console.log(cody); // logs the internal properties and values of cody console.log(cody.constructor); // logs the Person() function </script></body></html>
The above code leverages a user-defined constructor function [i.e.,
Person()
] to create the cody
object. This is no different from the
Array()
constructor creating an
Array()
object
[e.g., new Array()
]:
<!DOCTYPE html><html lang="en"><body><script> // instantiate an Array object named myArray var myArray = new Array(); // myArray is an instance of Array // myArray is an object and an instance of Array() constructor console.log(typeof myArray); // logs object! What? Yes, arrays are type of object console.log(myArray); // logs [ ] console.log(myArray.constructor); // logs Array() </script></body></html>
In JavaScript, most values (excluding primitive values) involve objects being created, or instantiated, from a constructor function. An object returned from a constructor is called an instance. Make sure you are comfortable with these semantics, as well as the pattern of leveraging constructors to construct objects.
The JavaScript Native/Built-In Object Constructors
The JavaScript language contains nine native (or built-in) object
constructors. These objects are used by JavaScript to construct the
language, and by “construct” I mean that these objects are used to express
object values in JavaScript code, as well as orchestrate several features
of the language. Thus, the native object constructors are multifaceted in
that they produce objects, but are also leveraged in facilitating many of
the language’s programming conventions. For example, functions are objects
created from the Function()
constructor, but are also used to create other objects when called as
constructor functions using the new
keyword.
Below, I list the nine native object constructors that come pre-packaged with JavaScript:
Number()
String()
Boolean()
Object()
Array()
Function()
Date()
RegExp()
Error()
JavaScript is mostly constructed from just these nine objects (as well as string, number, and boolean primitive values). Understanding these objects in detail is key to taking advantage of JavaScript’s unique programming power and language flexibility.
Notes
The
Math
object is the oddball here. It’s a static object, rather than a constructor function, meaning you can’t do this:var x = new Math()
. But you can use it as if it has already been instantiated (e.g.,Math.PI
). Truly,Math
is a just an object namespace set up by JavaScript to house math functions.The native objects are sometimes referred to as “global objects” since they are the objects that JavaScript has made natively available for use. Do not confuse the term global object with the “head” global object that is the topmost level of the scope chain, for example, the
window
object in all web browsers.The
Number()
,String()
, andBoolean()
constructors not only construct objects; they also provide a primitive value for a string, number and boolean, depending upon how the constructor is leveraged. If you called these constructors directly, then a complex object is returned. If you simply express a number, string, or boolean value in your code (primitive values like 5, “foo” andtrue
), then the constructor will return a primitive value instead of a complex object value.
User-Defined/Non-Native Object Constructor Functions
As you saw with the Person()
constructor, we can make our own constructor functions, from which we can
produce not just one but multiple custom
objects.
Below, I present the familiar Person()
constructor function:
<!DOCTYPE html><html lang="en"><body><script> var Person = function(living, age, gender) { this.living = living; this.age = age; this.gender = gender; this.getGender = function() {return this.gender;}; }; var cody = new Person(true, 33, 'male'); console.log(cody); // logs Object {living=true, age=33, gender="male", ...} var lisa = new Person(true, 34, 'female'); console.log(lisa); // logs Object {living=true, age=34, gender="female", ...} </script></body></html>
As you can see, by passing unique parameters and invoking the
Person()
constructor function, you
could easily create a vast number of unique people objects. This can be
pretty handy when you need more than two or three objects that possess the
same properties, but with different values. Come to think of it, this is
exactly what JavaScript does with the native objects. The Person()
constructor follows the same principles
as the Array()
constructor. So new Array('foo','bar')
is really not that
different than new Person(true, 33,
'male')
. Creating your own constructor functions is just using
the same pattern that JavaScript itself uses for its own native
constructor functions.
Notes
It is not required, but when creating custom constructor functions intended to be used with the
new
operator, it’s best practice to make the first character of the constructor name uppercase:Person()
rather thanperson()
.One tricky thing about constructor functions is the use of the
this
value inside of the function. Remember, a constructor function is just a cookie cutter. When used with thenew
keyword, it will create an object with properties and values defined inside of the constructor function. Whennew
is used, the valuethis
literally means the new object/instance that will be created based on the statements inside the constructor function. On the other hand, if you create a constructor function and call it without the use of thenew
keyword thethis
value will refer to the “parent” object that contains the function. More detail about this topic can be found in Chapter 6.It’s possible to forgo the use of the
new
keyword and the concept of a constructor function by explicitly having the function return an object. The function would have to be written explicitly to build anObject()
object and return it:var myFunction = function() {return {prop: val}};
. Doing this, however, sidesteps prototypal inheritance.
Instantiating Constructors Using the new Operator
A constructor function is basically a cookie cutter template used to
create pre-configured objects. Take String()
for example. This function, when used
with the new
operator [new String('foo')
] creates a string instance
based on the String()
“template.” Let’s
look at an example.
<!DOCTYPE html><html lang="en"><body><script> var myString = new String('foo'); console.log(myString); // logs foo {0 = "f", 1 = "o", 2 = "o"} </script></body></html>
Above, we created a new string object that is an instance of the
String()
constructor function. Just
like that, we have a string value expressed in JavaScript.
Note
I’m not suggesting that you use constructor functions instead of
their literal/primitive equivalents—like var
string="foo";
. I am, however, suggesting that you understand
what is going on behind literal/primitive values.
As previously mentioned, the JavaScript language has the following
native predefined constructors: Number()
, String()
, Boolean()
, Object()
, Array()
, Function()
, Date()
, RegExp()
, and Error()
. We can
instantiate an object instance from any of these constructor functions by
applying the new
operator. Below, I
construct these nine native JavaScript objects.
<!DOCTYPE html><html lang="en"><body><script> // instantiate an instance for each native constructor using the new keyword var myNumber = new Number(23); var myString = new String('male'); var myBoolean = new Boolean(false); var myObject = new Object(); var myArray = new Array('foo','bar'); var myFunction = new Function("x", "y", "return x*y"); var myDate = new Date(); var myRegExp = new RegExp('\bt[a-z]+\b'); var myError = new Error('Crap!'); // log/verify which constructor created the object console.log(myNumber.constructor); // logs Number() console.log(myString.constructor); // logs String() console.log(myBoolean.constructor); // logs Boolean() console.log(myObject.constructor); // logs Object() console.log(myArray.constructor); //logs Array(), in modern browsers console.log(myFunction.constructor); // logs Function() console.log(myDate.constructor); // logs Date() console.log(myRegExp.constructor); // logs RegExp() console.log(myError.constructor); // logs Error() </script></body></html>
By using the new
operator, we are
telling the JavaScript interpreter that we would like an object that is an
instance of the corresponding constructor function. For example, in the
code above, the Date()
constructor
function is used to create date objects. The Date()
constructor function is a cookie cutter
for date objects. That is, it produces date objects from a default pattern
defined by the Date()
constructor
function.
At this point, you should be well acquainted with creating object
instances from native [e.g., new
String('foo')
] and user-defined constructor functions [e.g.,
new Person(true, 33,
'male')
].
Creating Shorthand/Literal Values from Constructors
JavaScript provides shortcuts—called “literals”—for manufacturing
most of the native object values without having to use new Foo()
or new
Bar()
. For the most part, the literal syntax accomplishes the
same thing as using the new
operator.
The exceptions are: Number()
, String()
, and Boolean()
—see notes below.
If you come from other programming backgrounds, you are likely more
familiar with the literal way of creating objects. Below, I instantiate
the native JavaScript constructors using the new
operator and then create corresponding
literal equivalents.
<!DOCTYPE html><html lang="en"><body><script> var myNumber = new Number(23); // an object var myNumberLiteral = 23; // primitive number value, not an object var myString = new String('male'); // an object var myStringLiteral = 'male'; // primitive string value, not an object var myBoolean = new Boolean(false); // an object var myBooleanLiteral = false; // primitive boolean value, not an object var myObject = new Object(); var myObjectLiteral = {}; var myArray = new Array('foo', 'bar'); var myArrayLiteral = ['foo', 'bar']; var myFunction = new Function("x", "y", "return x*y"); var myFunctionLiteral = function(x, y) {return x*y}; var myRegExp = new RegExp('\bt[a-z]+\b'); var myRegExpLiteral = /\bt[a-z]+\b/; // verify that literals are created from same constructor console.log(myNumber.constructor,myNumberLiteral.constructor); console.log(myString.constructor,myStringLiteral.constructor); console.log(myBoolean.constructor,myBooleanLiteral.constructor); console.log(myObject.constructor,myObjectLiteral.constructor); console.log(myArray.constructor,myArrayLiteral.constructor); console.log(myFunction.constructor,myFunctionLiteral.constructor); console.log(myRegExp.constructor,myRegExpLiteral.constructor); </script></body></html>
What you need to take away here is the fact that, in general, using
literals simply conceals the underlying process identical to using the
new
operator. Maybe more importantly,
it’s a lot more convenient!
Okay, things are a little more complicated with respect to the primitive string, number, and boolean values. In these cases, literal values take on the characteristics of primitive values rather than complex object values. See my note below.
Note
When using literal values for string, number, and boolean, an
actual complex object is never created until the value is treated as an
object. In other words, you are dealing with a primitive datatype until
you attempt to use methods or retrieve properties associated with the
constructor (e.g., var charactersInFoo =
'foo'.length
). When this happens, JavaScript creates a wrapper
object for the literal value behind the scenes, allowing the value to be
treated as an object. Then, after the method is called, JavaScript
discards the wrapper object and the value returns to a literal type.
This is why string, number, and boolean are considered primitive (or
simple) datatypes. I hope this clarifies the misconception that
“everything in JavaScript is an object” with the concept that
“everything in JavaScript can act like an object.”
Primitive (a.k.a. Simple) Values
The JavaScript values 5
, 'foo'
, true
,
and false
, as well as null
and undefined
, are considered primitive because they
are irreducible. That is, a number is a number, a
string is a string, a boolean is either true or false, and null
and undefined
are just that, null
and undefined
. These values are inherently simple,
and do not represent values that can be made up of other values.
Examine the code below and ask yourself if the string, number,
boolean, null
, and undefined
values could be more complex. Contrast
this to what you know of an Object()
instance or Array()
instance or really any complex
object.
<!DOCTYPE html><html lang="en"><body><script> var myString = 'string' var myNumber = 10; var myBoolean = false; // could be true or false, but that is it var myNull = null; var myUndefined = undefined; console.log(myString, myNumber, myBoolean, myNull, myUndefined); /* Consider that a complex object like array or object can be made up of multiple primitive values, and thus becomes a complex set of multiple values. */ var myObject = { myString: 'string', myNumber: 10, myBoolean: false, myNull: null, myUndefined: undefined }; console.log(myObject); var myArray = ['string', 10, false, null, undefined]; console.log(myArray); </script></body></html>
Quite simply, primitive values represent the lowest form (i.e., simplest) of datum/information available in JavaScript.
Notes
As opposed to creating values with literal syntax, when a
String()
,Number()
, orBoolean()
value is created using thenew
keyword, the object created is actually a complex object.It’s critical that you understand the fact that the
String()
,Number()
, andBoolean()
constructors are dual-purpose constructors used to create literal/primitive values as well as complex values. These constructors do not always return objects, but instead, when used without thenew
operator, can return a primitive representation of the actual complex object value.
The Primitive Values null, undefined, “string”, 10, true, and false Are Not Objects
The null
and undefined
values are such trivial values that
they do not require a constructor function, nor the use of the new
operator to establish them as a JavaScript
value. To use null
or undefined
, all you do is use them as if they
were an operator. The remaining primitive values string, number, and
boolean, while technically returned from a constructor function, are not
objects.
Below, I contrast the difference between primitive values and the rest of the native JavaScript objects.
<!DOCTYPE html><html lang="en"><body><script> /* no object is created when producing primitive values, notice no use of the "new" keyword */ var primitiveString1 = "foo"; var primitiveString2 = String('foo'); var primitiveNumber1 = 10; var primitiveNumber2 = Number('10'); var primitiveBoolean1 = true; var primitiveBoolean2 = Boolean('true'); // confirm the typeof is not object console.log(typeof primitiveString1, typeof primitiveString2); // logs 'string,string' console.log(typeof primitiveNumber1, typeof primitiveNumber2); // logs 'number,number, console.log(typeof primitiveBoolean1, typeof primitiveBoolean2); // logs 'boolean,boolean' // versus the usage of a constructor and new keyword for creating objects var myNumber = new Number(23); var myString = new String('male'); var myBoolean = new Boolean(false); var myObject = new Object(); var myArray = new Array('foo', 'bar'); var myFunction = new Function("x", "y", "return x * y"); var myDate = new Date(); var myRegExp = new RegExp('\\bt[a-z]+\\b'); var myError = new Error('Crap!'); // logs 'object object object object object function object function object' console.log( typeof myNumber, typeof myString, typeof myBoolean, typeof myObject, typeof myArray, typeof myFunction, // BE AWARE typeof returns function for all function objects typeof myDate, typeof myRegExp, // BE AWARE typeof returns function for RegExp() typeof myError ); </script></body></html>
What I would like you to grasp from the previous code example is that primitive values are not objects. Primitive values are special in that they are used to represent simple values.
How Primitive Values Are Stored/Copied in JavaScript
It is extremely important to grok that primitive values are stored
and manipulated at “face value.” It might sound simple, but this means
that if I store the string value "foo"
in a variable called myString
, then the
value "foo"
is literally stored in
memory as such. Why is this important? Once you begin manipulating (e.g.,
copying) values, you have to be equipped with this knowledge, because
primitive values are copied literally.
In the example below, we store a copy of the myString
value ('foo'
) in the variable myStringCopy
, and its value is literally copied.
Even if we change the original value, the copied value, referenced by the
variable myStringCopy
, remains
unchanged.
<!DOCTYPE html><html lang="en"><body><script> var myString = 'foo' // create a primitive string object var myStringCopy = myString; // copy its value into a new variable var myString = null; // manipulate the value stored in the myString variable /* The original value from myString was copied to myStringCopy. This is confirmed by updating the value of myString then checking the value of myStringCopy */ console.log(myString, myStringCopy); // logs 'null foo' </script></body></html>
The takeaway here is that primitive values are stored and
manipulated as irreducible values. Referring to them
transfers their value. In the example above, we copied, or
cloned, the myString
value
to the variable myStringCopy
. When we
updated the myString
value, the
myStringCopy
value still had a copy of
the old myString
value. Remember this
and contrast the mechanics here with complex objects (discussed
below).
Primitive Values Are Equal by Value
Primitives can be compared to see if their values are literally the
same. As logic would suggest, if you compare a variable containing the
numeric value 10
with another variable
containing the numeric value 10
,
JavaScript will consider these equal because 10
is the same as 10
(i.e., 10 ===
10
). The same, of course, would apply if you compare the
primitive string 'foo'
to another
primitive string with a value of 'foo'
.
The comparison would say that they are equal to each other based on their
value (i.e., 'foo' ===
'foo'
).
In the code below, I demonstrate the “equal by value” concept using primitive numbers, as well as contrast this with a complex number object.
<!DOCTYPE html><html lang="en"><body><script> var price1 = 10; var price2 = 10; var price3 = new Number('10'); // a complex numeric object because new was used var price4 = price3; console.log(price1 === price2); // logs true /* logs false because price3 contains a complex number object and price 1 is a primitive value */ console.log(price1 === price3); // logs true because complex values are equal by reference, not value console.log(price4 === price3); // what if we update the price4 variable to contain a primitive value? price4 = 10; console.log(price4 === price3); /* logs false: price4 is now primitive rather than complex */ </script></body></html>
The takeaway here is that primitives, when compared, will check to
see if the expressed values are equal. When a string,
number, or boolean value is created using the new
keyword [e.g., new
Number('10')
], the value is no longer primitive. As such,
comparison does not work the same as if the value had been created via
literal syntax. This is not surprising, given that primitive values are
stored by value (does 10 === 10
?),
while complex values are stored by reference (do price3
and price4
contain a reference to the same
value?).
The String, Number, and Boolean Primitive Values Act Like Objects When Used Like Objects
When a primitive value is used as if it were an object created by a constructor, JavaScript converts it to an object in order to respond to the expression at hand, but then discards the object qualities and changes it back to a primitive value. In the code below, I take primitive values and showcase what happens when the values are treated like objects.
<!DOCTYPE html><html lang="en"><body><script> // Produce primitive values var myNull = null; var myUndefined = undefined; var primitiveString1 = "foo"; var primitiveString2 = String('foo'); // did not use new, so we get primitive var primitiveNumber1 = 10; var primitiveNumber2 = Number('10'); // did not use new, so we get primitive var primitiveBoolean1 = true; var primitiveBoolean2 = Boolean('true'); // did not use new, so we get primitive /* Access the toString() property method (inherited by objects from object.prototype) to demonstrate that the primitive values are converted to objects when treated like objects. */ // logs "string string" console.log(primitiveString1.toString(), primitiveString2.toString()); // logs "number number" console.log(primitiveNumber1.toString(), primitiveNumber2.toString()); // logs "boolean boolean" console.log(primitiveBoolean1.toString(), primitiveBoolean2.toString()); /* This will throw an error and not show up in firebug lite, as null and undefined do not convert to objects and do not have constructors. */ console.log(myNull.toString()); console.log(myUndefined.toString()); </script></body></html>
In the above code example, all of the primitive values (except
null
and undefined
) are converted to objects, so as to
leverage the toString()
method, and
then are returned to primitive values once the method is invoked and
returned.
Complex (a.k.a. Composite) Values
The native object constructors Object()
, Array()
, Function()
, Date()
, Error()
, and RegExp()
are complex because they can contain
one or or more primitive or complex values. Essentially, complex values
can be made up of many different types of JavaScript objects. It could be
said that complex objects have an unknown size in memory because complex
objects can contain any value and not a specific known value. In the code
below, we create an object and an array that houses all of the primitive
objects.
<!DOCTYPE html><html lang="en"><body><script> var object = { myString: 'string', myNumber: 10, myBoolean: false, myNull: null, myUndefined: undefined }; var array = ['string', 10, false, null, undefined]; /* Contrast this to the simplicity of the primitive values below. In a primitive form, none of the values below can be more complex than what you see while complex values can encapsulate any of the JavaScript values (seen above). */ var myString = 'string'; var myNumber = 10; var myBoolean = false; var myNull = null; var myUndefined = undefined; </script></body></html>
The takeaway here is that complex values are a composite of values and differ in complexity and composition to primitive values.
Note
The term “complex object” has also been expressed in other writings as “composite objects” or “reference types.” If it’s not obvious, all these names describe the nature of a JavaScript value excluding primitive values. Primitive values are not “referenced by value” and cannot represent a composite (i.e., a thing made up of several parts or elements) of other values. Complex objects, on the other hand, are “referenced by value” and can contain or encapsulate other values.
How Complex Values Are Stored/Copied in JavaScript
It is extremely important to grok that complex values are stored and
manipulated by reference. When creating a variable
containing a complex object, the value is stored in memory at an address.
When you reference a complex object, you’re using its name (i.e., variable
or object property) to retrieve the value at that address in memory. The
implications are significant when you consider what happens when you
attempt to copy a complex value. Below, we create an object stored in the
variable myObject
. Then the value in
myObject
is copied to the variable
copyOfMyObject
. Really, it is not a
copy of the object—more like a copy of the address of the
object.
<!DOCTYPE html><html lang="en"><body><script> var myObject = {}; var copyOfMyObject = myObject; /* not copied by value, just the reference is copied */ myObject.foo = 'bar'; // manipulate the value stored in myObject /* Now if we log myObject and copyOfMyObject, they will have a foo property because they reference the same object. */ console.log(myObject, copyOfMyObject); /* logs 'Object { foo="bar"} Object { foo="bar"}' */ </script></body></html>
What you need to realize is that, unlike primitive values that would
copy a value, objects (a.k.a. complex values) are stored by reference. As
such, the reference (a.k.a. address) is copied, but not the actual value.
This means that objects are not copied at all. Like I said, what is copied
is the address or reference to the object in the memory stack. In our code
example, myObject
and copyOfMyObject
point to the same object stored
in memory.
The big takeaway here is that when you change a complex
value—because it is stored by reference—you change the value stored in all
variables that reference that complex value. In our code example, both
myObject
and copyOfMyObject
are changed when you update the
object stored in either variable.
Notes
When the values
String()
,Number()
, andBoolean()
are created using thenew
keyword, or converted to complex objects behind the scenes, the values continue to be stored/copied by value. So, even though primitive values can be treated like complex values, they do not take on the quality of being copied by reference.To truly make a copy of an object, you have to extract the values from the old object, and inject them into a new object.
Complex Objects Are Equal by Reference
When comparing complex objects, they are equal only when they reference the same object (i.e., have the same address). Two variables containing identical objects are not equal to each other since they do not actually point at the same object.
Below, objectFoo
and objectBar
have the same properties and are, in
fact, identical objects, but when asked if they are equal via ===
, JavaScript tells us they are not.
<!DOCTYPE html><html lang="en"><body><script> var objectFoo = {same: 'same'}; var objectBar = {same: 'same'}; /* logs false, JS does not care that they are identical and of the same object type */ console.log(objectFoo === objectBar); // how complex objects are measured for equality var objectA = {foo: 'bar'}; var objectB = objectA; console.log(objectA === objectB); /* logs true because they reference the same object */ </script></body></html>
The takeaway here is that variables that point to a complex object in memory are equal only because they are using the same “address.” Conversely, two independently created objects are not equal even if they are of the same type and possess the exact same properties.
Complex Objects Have Dynamic Properties
A new variable that points to an existing complex object does not copy the object. This is why complex objects are sometimes called reference objects. A complex object can have as many references as you want, and they will always refer to the same object, even as that object changes.
<!DOCTYPE html><html lang="en"><body><script> var objA = {property: 'value'}; var pointer1 = objA; var pointer2 = pointer1; // update the objA.property, and all references (pointer1 and pointer2) are updated objA.property = null; /* logs 'null null null' because objA, pointer1, and pointer2 all reference the same object */ console.log(objA.property, pointer1.property, pointer2.property); </script></body></html>
This allows for dynamic object properties because you can define an object, create references, update the object, and all of the variables referring to the object will “get” that update.
The typeof Operator Used on Primitive and Complex Values
The typeof
operator can be used
to return the type of value you are dealing with. But the values returned
from it are not exactly consistent or what some might say, logical. The
following code exhibits the returned values from using the typeof
operator.
<!DOCTYPE html><html lang="en"><body><script> // primitive values var myNull = null; var myUndefined = undefined; var primitiveString1 = "string"; var primitiveString2 = String('string'); var primitiveNumber1 = 10; var primitiveNumber2 = Number('10'); var primitiveBoolean1 = true; var primitiveBoolean2 = Boolean('true'); console.log(typeof myNull); // logs object? WHAT? Be aware... console.log(typeof myUndefined); // logs undefined console.log(typeof primitiveString1, typeof primitiveString2); // logs string string console.log(typeof primitiveNumber1, typeof primitiveNumber2); // logs number number console.log(typeof primitiveBoolean1, typeof primitiveBoolean2); // logs boolean boolean // Complex Values var myNumber = new Number(23); var myString = new String('male'); var myBoolean = new Boolean(false); var myObject = new Object(); var myArray = new Array('foo', 'bar'); var myFunction = new Function("x", "y", "return x * y"); var myDate = new Date(); var myRegExp = new RegExp('\\bt[a-z]+\\b'); var myError = new Error('Crap!'); console.log(typeof myNumber); // logs object console.log(typeof myString); // logs object console.log(typeof myBoolean); // logs object console.log(typeof myObject); // logs object console.log(typeof myArray); // logs object console.log(typeof myFunction); // logs function? WHAT? Be aware... console.log(typeof myDate); // logs object console.log(typeof myRegExp); // logs function? WHAT? Be aware... console.log(typeof myError); // logs object </script></body></html>
When using this operator on values, you should be aware of the potential values returned given the type of value (primitive or complex) that you are dealing with.
Dynamic Properties Allow for Mutable Objects
Complex objects are made up of dynamic properties. This allows for user-defined objects—and most of the native objects—to be mutated. This means that the majority of objects in JavaScript can be updated or changed at any time. Because of this, we can change the native pre-configured nature of JavaScript itself by augmenting its native objects. However, I am not telling you to do this; in fact, I do not think you should. But let’s not cloud what is possible with opinions.
This means it’s possible to store properties on native constructors and add new methods to the native objects with additions to their prototype objects.
In the code below, I mutate the String()
constructor function and String.prototype
.
<!DOCTYPE html><html lang="en"><body><script> /* augment the built-in String constructor Function() with the augmentedProperties property */ String.augmentedProperties = []; if (!String.prototype.trimIT) { // if the prototype does not have trimIT() add it String.prototype.trimIT = function() { return this.replace(/^\s+|\s+$/g, ''); } // now add trimIT string to the augmentedProperties array String.augmentedProperties.push('trimIT'); } var myString = ' trim me '; console.log(myString.trimIT()); /* invoke our custom trimIT string method, logs 'trim me' */ console.log(String.augmentedProperties.join()); // logs 'trimIT' </script></body></html>
I want to drive home the fact that objects in JavaScript are
dynamic. This allows objects in JavaScript to be mutated. Essentially, the
entire language can be mutated into a custom version (e.g., trimIT
string method). Again, I am not
recommending this—I am just pointing out that it is part of the nature of
objects in JavaScript.
All Constructor Instances Have Constructor Properties that Point to Their Constructor Function
When any object is instantiated, the constructor
property is created behind the
scenes as a property of that object/instance. This points to the
constructor function that created the object. Below, we create an Object()
object, stored in the foo
variable, and then verify that the constructor
property is available for the object
we created.
<!DOCTYPE html><html lang="en"><body><script> var foo = {}; console.log(foo.constructor === Object) /* logs true, because object() constructed foo */ console.log(foo.constructor) // points to the Object() constructor function </script></body></html>
This can be handy: if I’m working with some instance, and I can’t see who or what created it (especially if it was someone else’s code), I can determine if it’s an array, an object, or whatever.
Below, you can see that I have instantiated most of the
pre-configured objects that come included with the JavaScript language.
Note that using the constructor
property on
literal/primitive values correctly resolves (i.e., points) to the right
constructor.
<!DOCTYPE html><html lang="en"><body><script> var myNumber = new Number('23'); var myNumberL = 23; // literal shorthand var myString = new String('male'); var myStringL = 'male'; // literal shorthand var myBoolean = new Boolean('true'); var myBooleanL = true; // literal shorthand var myObject = new Object(); var myObjectL = {}; // literal shorthand var myArray = new Array(); var myArrayL = []; // literal shorthand var myFunction = new Function(); var myFunctionL = function() {}; // literal shorthand var myDate = new Date(); var myRegExp = new RegExp('/./'); var myRegExpL = /./; // literal shorthand var myError = new Error(); console.log( // all of these return true myNumber.constructor === Number, myNumberL.constructor === Number, myString.constructor === String, myStringL.constructor === String, myBoolean.constructor === Boolean, myBooleanL.constructor === Boolean, myObject.constructor === Object, myObjectL.constructor === Object, myArray.constructor === Array, myArrayL.constructor === Array, myFunction.constructor === Function, myFunctionL.constructor === Function, myDate.constructor === Date, myRegExp.constructor === RegExp, myRegExpL.constructor === RegExp, myError.constructor === Error ); </script></body></html>
The constructor property also works on user-defined constructor
functions. Below, we define a CustomConstructor()
constructor function, then
using the keyword new
, we invoke the
function to produce an object. Once we have our object, we can then
leverage the constructor property.
<!DOCTYPE html><html lang="en"><body><script> var CustomConstructor = function CustomConstructor(){ return 'Wow!'; }; var instanceOfCustomObject = new CustomConstructor(); // logs true console.log(instanceOfCustomObject.constructor === CustomConstructor); // returns a reference to CustomConstructor() function // returns 'function() { return 'Wow!'; };' console.log(instanceOfCustomObject.constructor); </script></body></html>
Notes
You might be confused as to why primitive values have constructor properties that point to constructor functions when objects are not returned. By using a primitive value, the constructor is still called, so there is still a relationship with primitive values and constructor functions. However, the end result is a primitive value.
If you would like the constructor property to log the actual name of the constructor for user-defined constructor function expressions, you have to give the constructor function expressions an actual name (e.g.,
var Person = function Person(){};
).
Verify that an Object Is an Instance of a Particular Constructor Function
By using the instanceof
operator,
we can determine (true or false) if an object is an instance of a
particular constructor function.
Below, we are verifying if the object InstanceOfCustomObject
is an instance of the
CustomConstructor
constructor function.
This works with user-defined objects as well as native objects created
with the new
operator.
<!DOCTYPE html><html lang="en"><body><script> // user-defined object constructor var CustomConstructor = function() {this.foo = 'bar';}; // instantiate an instance of CustomConstructor var instanceOfCustomObject = new CustomConstructor(); console.log(instanceOfCustomObject instanceof CustomConstructor); // logs true // works the same as a native object console.log(new Array('foo') instanceof Array) // logs true </script></body></html>
Notes
One thing to watch out for when dealing with the
instanceof
operator is that it will return true any time you ask if an object is an instance of Object since all objects inherit from theObject()
Constructor.The
instanceof
operator will return false when dealing with primitive values that leverage object wrappers (e.g.,'foo' instanceof String
// returns false). Had the string'foo'
been created with thenew
operator, theinstanceof
operator would have returned true. So, keep in mind thatinstanceof
really only works with complex objects and instances created from constructor functions that return objects.
An Instance Created From a Constructor Can Have Its Own Independent Properties (Instance Properties)
In JavaScript, objects can be augmented at any time (i.e., dynamic properties). As previously mentioned, and to be exact, JavaScript has mutable objects. This means that objects created from a constructor function can be augmented with properties.
Below, I create an instance from the Array()
constructor and then augment it with its
own property.
<!DOCTYPE html><html lang="en"><body><script> var myArray = new Array(); myArray.prop = 'test'; console.log(myArray.prop) // logs 'test' </script></body></html>
This could be done with Object()
,
RegExp()
, or any of the other
non-primitive constructors—even
Boolean()
.
<!DOCTYPE html><html lang="en"><body><script> /* this can be done with any of the native constructors that actually produce an object */ var myString = new String(); var myNumber = new Number(); var myBoolean = new Boolean(true); var myObject = new Object(); var myArray = new Array(); var myFunction = new Function('return 2+2'); var myRegExp = new RegExp('\bt[a-z]+\b'); myString.prop = 'test'; myNumber.prop = 'test'; myBoolean.prop = 'test'; myObject.prop = 'test'; myArray.prop = 'test'; myFunction.prop = 'test'; myRegExp.prop = 'test'; // logs 'test', 'test', 'test', 'test', 'test', 'test', 'test' console.log(myString.prop,myNumber.prop,myBoolean.prop,myObject.prop, myArray.prop,myFunction.prop, myRegExp.prop); // be aware: instance properties do not work with primitive/literal values var myString = 'string'; var myNumber = 1; var myBoolean = true; myString.prop = true; myNumber.prop = true; myBoolean.prop = true; // logs undefined, undefined, undefined console.log(myString.prop, myNumber.prop, myBoolean.prop); </script></body></html>
Adding properties to objects created from a constructor function is not uncommon. Remember: object instances created from constructor functions are just plain old objects.
The Semantics of “JavaScript Objects” and “Object() Objects”
Do not confuse the general term “JavaScript objects”, which refers
to the notion of objects in
JavaScript, with Object()
objects. An Object()
object [e.g.,
var myObject = new Object()
] is a very
specific type of value expressed in JavaScript. Just as an Array()
object is a type of object called
array, an Object()
object is a type of object called object. The gist is
that the Object()
constructor function
produces an empty generic object container, which is referred to as an
Object()
object. Similarly, the
Array()
constructor function produces
an array object, and we refer to these objects as Array()
objects.
In this book, the term “JavaScript object” is used to refer to all objects in JavaScript, because most of the values in JavaScript can act like objects. This is due to the fact that the majority of JavaScript values are created from a native constructor function which produces a very specific type of object.
What you need to remember is that an Object()
object is a very specific kind of
value. It’s a generic empty object. Do not confuse this with the term
“JavaScript objects” used to refer to most of the values that can be
expressed in JavaScript as an object.
Get JavaScript Enlightenment now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.