Working With Objects and Properties

A complex object can hold any permitted JavaScript value. In the following code, I create an Object() object called myObject and then add properties representing the majority of values available in JavaScript.


Complex Objects

Sample: sample29.html

The simple concept to learn here is that complex objects can containor refer toanything you can nominally express in JavaScript. You should not be surprised when you see this done, as all of the native objects can be mutated. This even applies to String(), Number(), and Boolean() values in their object form i.e. when they are created with the new operator.


Encapsulating Complex Objects In a Programmatically Beneficial Way

The Object(), Array(), and Function() objects can contain other complex objects. In the following sample, I demonstrate this by setting up an object tree using Object() objects.

Sample: sample30.html

The same thing can be done with an Array() object (aka multidimensional array), or with a Function() object.

Sample: sample31.html

The main concept to take away here is that some of the complex objects are designed to encapsulate other objects in a programmatically beneficial way.


Getting, Setting, and Updating an Object's Properties Using Dot Notation or Bracket Notation

We can get, set, or update an object's properties using either dot notation or bracket notation.

In the following sample, I demonstrate dot notation, which is accomplished by using the object name followed by a period, and then followed by the property to get, set, or update (e.g., objectName.property).

Sample: sample32.html

Dot notation is the most common notation for getting, setting, or updating an object's properties.

Bracket notation, unless required, is not as commonly used. In the following sample, I replace the dot notation used in the previous sample with bracket notation. The object name is followed by an opening bracket, the property name (in quotes), and then a closing bracket:

Sample: sample33.html

Bracket notation can be very useful when you need to access a property key and what you have to work with is a variable that contains a string value representing the property name. In the next sample, I demonstrate the advantage of bracket notation over dot notation by using it to access the property foobar. I do this using two variables that, when joined, produce the string version of the property key contained in foobarObject.

Sample: sample34.html

Additionally, bracket notation can come in handy for getting at property names that are invalid JavaScript identifiers. In the following code, I use a number and a reserved keyword as a property name (valid as a string) that only bracket notation can access.

Sample: sample35.html

Because objects can contain other objects, cody.object.object.object.object or cody['object']['object']['object']['object'] can be seen at times. This is called object chaining. The encapsulation of objects can go on indefinitely.

Objects are mutable in JavaScript, meaning that getting, setting, or updating them can be performed on most objects at any time. By using the bracket notation (e.g., cody['age']), you can mimic associative arrays found in other languages.

If a property inside an object is a method, all you have to do is use the () operators (e.g., cody.getGender()) to invoke the property method.


Deleting Object Properties

The delete operator can be used to completely remove properties from an object. In the following code snippet, we delete the bar property from the foo object.

Sample: sample36.html

delete will not delete properties that are found on the prototype chain.

Deleting is the only way to actually remove a property from an object. Setting a property to undefined or null only changes the value of the property. It does not remove the property from the object.


How References to Object Properties Are Resolved

If you attempt to access a property that is not contained in an object, JavaScript will attempt to find the property or method using the prototype chain. In the following sample, I create an array and attempt to access a property called foo that has not yet been defined. You might think that because myArray.foo is not a property of the myArray object, JavaScript will immediately return undefined. But JavaScript will look in two more places (Array.prototype and then Object.prototype) for the value of foo before it returns undefined.

Sample: sample37.html

the property. If it has the property, it will return the value of the property, and there is no inheritance occurring because the prototype chain is not leveraged. If the instance does not have the property, JavaScript will then look for it on the object's constructor function prototype object.

All object instances have a property that is a secret link (aka __proto__) to the constructor function that created the instance. This secret link can be leveraged to grab the constructor function, specifically the prototype property of the instances constructor function.

This is one of the most confusing aspects of objects in JavaScript. But let's reason this out. Remember that a function is also an object with properties. It makes sense to allow objects to inherit properties from other objects. Just like saying: "Hey object B, I would like you to share all the properties that object A has." JavaScript wires this all up for native objects by default via the prototype object. When you create your own constructor functions, you can leverage prototype chaining as well.

How exactly JavaScript accomplishes this is confusing until you see it for what it is: just a set of rules. Let's create an array to examine the prototype property closer.

Sample: sample38.html

Our Array() instance is an object with properties and methods. As we access one of the array methods, such as join(), let’s ask ourselves: Does the myArray instance created from the Array() constructor have its own join() method? Let's check.

Sample: sample39.html

No it does not. Yet myArray has access to the join() method as if it were its own property. What happened here? Well, you just observed the prototype chain in action. We accessed a property that, although not contained in the myArray object, could be found by JavaScript somewhere else. That somewhere else is very specific. When the Array() constructor was created by JavaScript, the join() method was added (among others) as a property of the prototype property of Array().

To reiterate, if you try to access a property on an object that does not contain it, JavaScript will search the prototype chain for this value. First it will look at the constructor function that created the object (e.g., Array), and inspect its prototype (e.g., Array.prototype) to see if the property can be found there. If the first prototype object does not have the property, then JavaScript keeps searching up the chain at the constructor behind the initial constructor. It can do this all the way up to the end of the chain.

Where does the chain end? Let's examine the example again, invoking the toLocaleString() method on myArray.

Sample: sample40.html

The toLocaleString() method is not defined within the myArray object. So, the prototype chaining rule is invoked and JavaScript looks for the property in the Array constructors prototype property (e.g., Array.prototype). It is not there either, so the chain rule is invoked again and we look for the property in the Object() prototype property (Object.prototype). And yes, it is found there. Had it not been found there, JavaScript would have produced an error stating that the property was undefined.

Since all prototype properties are objects, the final link in the chain is Object.prototype. There is no other constructor prototype property that can be examined.

There is an entire chapter ahead that breaks down the prototype chain into smaller parts, so if this was completely lost on you, read that chapter and then come back to this explanation to solidify your understanding. From this short read on the matter, I hope you understand that when a property is not found (and deemed undefined), JavaScript will have looked at several prototype objects to determine that a property is undefined. A lookup always occurs, and this lookup process is how JavaScript handles inheritance as well as simple property lookups.


Using hasOwnProperty to Verify That an Object Property Is Not From the Prototype Chain

While the in operator can check for properties of an object, including properties from the prototype chain, the hasOwnProperty method can check an object for a property that is not from the prototype chain.

In the following sample, we want to know if myObject contains the property foo, and that it is not inheriting the property from the prototype chain. To do this, we ask if myObject has its own property called foo.

Sample: sample41.html

The hasOwnProperty method should be leveraged when you need to determine whether a property is local to an object or inherited from the prototype chain.


Checking if an Object Contains a Given Property Using the in Operator

The in operator is used to verify (true or false) if an object contains a given property. In this sample, we are checking to see if foo is a property in myObject.

Sample: sample42.html

You should be aware that the in operator not only checks for properties contained in the object referenced, but also for any properties that object inherits via the prototype chain. Thus, the same property lookup rules apply and the property, if not in the current object, will be searched for on the prototype chain.

This means that myObject in the previous sample actually contains a toString property method via the prototype chain (Object.prototype.toString), even if we did not specify one (e.g., myObject.toString = 'foo').

Sample: sample43.html

In the last code example, the toString property is not literally inside of the myObject object. However, it is inherited from Object.prototype, and so the in operator concludes that myObject does in fact have an inherited toString() property method.


Enumerate (Loop Over) an Object’s Properties Using the for in Loop

By using for in, we can loop over each property in an object. In the following sample, we are using the for in loop to retrieve the property names from the cody object.

Sample: sample44.html

The for in loop has a drawback. It will not only access the properties of the specific object being looped over. It will also include in the loop any properties inherited (via the prototype chain) by the object. Thus, if this is not the desired result, and most of the time it is not, we have to use a simple if statement inside of the loop to make sure we only access the properties contained within the specific object we are looping over. This can be done by using the hasOwnProperty() method inherited by all objects.

The order in which the properties are accessed in the loop is not always the order in which they are defined within the loop. Additionally, the order in which you defined properties is not necessarily the order they are accessed.

Only properties that are enumerable (i.e. available when looping over an objects properties) show up with the for in loop. For example, the constructor property will not show up. It is possible to check which properties are enumerable with the propertyIsEnumerable() method.


Host Objects and Native Objects

You should be aware that the environment (e.g., a web browser) in which JavaScript is executed typically contains what are known as host objects. Host objects are not part of the ECMAScript implementation, but are available as objects during execution. Of course, the availability and behavior of a host object depends completely on what the host environment provides.

For example, in the web browser environment the window/head object and all of its containing objects (excluding what JavaScript provides) are considered host objects.

In the following example, I examine the properties of the window object.

Sample: sample45.html

You might have noticed that native JavaScript objects are not listed among the host objects. Its fairly common that a browser distinguishes between host objects and native objects.

As it pertains to web browsers, the most famous of all hosted objects is the interface for working with HTML documents, also known as the DOM. The following sample is a method to list all of the objects contained inside the window.document object provided by the browser environment.

Sample: sample46.html

What I want you to learn here is that the JavaScript specification does not concern itself with host objects and vice versa. There is a dividing line between what JavaScript provides (e.g., JavaScript 1.5, ECMA-262, Edition 3 versus Mozilla's JavaScript 1.6, 1.7, 1.8, 1.8.1, 1.8.5) and what the host environment provides, and these two should not be confused.

The host environment (e.g., a web browser) that runs JavaScript code typically provides the head object (e.g., window object in a web browser) where the native portions of the language are stored along with host objects (e.g., window.location in a web browser) and user-defined objects (e.g., the code you write to run in the web browser).

Sometimes a web browser manufacturer, as the host of the JavaScript interpreter, will push forward a version of JavaScript or add future specifications to JavaScript before they have been approved (e.g., Mozilla's Firefox JavaScript 1.6, 1.7, 1.8, 1.8.1, 1.8.5).


Enhancing and Extending Objects With Underscore.js

JavaScript 1.5 is lacking when it comes time to seriously manipulate and manage objects. If you are running JavaScript in a web browser, I would like to be bold here and suggest the usage of Underscore.js when you need more functionality than is provided by JavaScript 1.5. Underscore.js provides the following functionality when dealing with objects.

These functions work on all objects and arrays:

  • each()
  • map()
  • reduce()
  • reduceRight()
  • detect()
  • select()
  • reject()
  • all()
  • any()
  • include()
  • invoke()
  • pluck()
  • max()
  • min()
  • sortBy()
  • sortIndex()
  • toArray()
  • size()

These functions work on all objects:

  • keys()
  • values()
  • functions()
  • extend()
  • clone()
  • tap()
  • isEqual()
  • isEmpty()
  • isElement()
  • isArray()
  • isArguments
  • isFunction()
  • isString()
  • isNumber
  • isBoolean
  • isDate
  • isRegExp
  • isNaN
  • isNull
  • isUndefined

Conclusion

I like this library because it takes advantage of the new native additions to JavaScript where browsers support them, but also provides the same functionality to browsers that do not, all without changing the native implementation of JavaScript unless it has to.

Before you start to use Underscore.js, make sure the functionality you need is not already provided by a JavaScript library or framework that might already be in use in your code.

Tags:

Comments

Related Articles