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
<!DOCTYPE html><html lang="en"><body><script> var myObject = {}; // Contain properties inside of myObject representing most of the native JavaScript values. myObject.myFunction = function () { }; myObject.myArray = []; myObject.myString = 'string'; myObject.myNumber = 33; myObject.myDate = new Date(); myObject.myRegExp = /a/; myObject.myNull = null; myObject.myUndefined = undefined; myObject.myObject = {}; myObject.myMath_PI = Math.PI; myObject.myError = new Error('Darn!'); console.log(myObject.myFunction, myObject.myArray, myObject.myString, myObject.myNumber, myObject.myDate, myObject.myRegExp, myObject.myNull, myObject.myNull, myObject.myUndefined, myObject.myObject, myObject.myMath_PI, myObject.myError); /* Works the same with any of the complex objects, for example a function. */ var myFunction = function () { }; myFunction.myFunction = function () { }; myFunction.myArray = []; myFunction.myString = 'string'; myFunction.myNumber = 33; myFunction.myDate = new Date(); myFunction.myRegExp = /a/; myFunction.myNull = null; myFunction.myUndefined = undefined; myFunction.myObject = {}; myFunction.myMath_PI = Math.PI; myFunction.myError = new Error('Darn!'); console.log(myFunction.myFunction, myFunction.myArray, myFunction.myString, myFunction.myNumber, myFunction.myDate, myFunction.myRegExp, myFunction.myNull, myFunction.myNull, myFunction.myUndefined, myFunction.myObject, myFunction.myMath_PI, myFunction.myError); </script></body></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
<!DOCTYPE html><html lang="en"><body><script> // Encapsulation using objects creates object chains. var object1 = { object1_1: { object1_1_1: {foo: 'bar'}, object1_1_2: {}, }, object1_2: { object1_2_1: {}, object1_2_2: {}, } }; console.log(object1.object1_1.object1_1_1.foo); // Logs 'bar'. </script></body></html>
The same thing can be done with an Array()
object (aka multidimensional array), or with a Function()
object.
Sample: sample31.html
<!DOCTYPE html><html lang="en"><body><script> // Encapsulation using arrays creates a multidimensional array chain. var myArray = [[[]]]; // An empty array, inside an empty array, inside an empty array. /* Here is an example of encapsulation using functions: An empty function inside an empty function inside an empty function. */ var myFunction = function () { // Empty function. var myFunction = function () { // Empty function. var myFunction = function () { // Empty function. }; }; }; // We can get crazy and mix and match too. var foo = [{ foo: [{ bar: { say: function () { return 'hi'; } }}]}]; console.log(foo[0].foo[0].bar.say()); // Logs 'hi'. </script></body></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
<!DOCTYPE html><html lang="en"><body><script> // Create a cody Object() object. var cody = new Object(); // Setting properties. cody.living = true; cody.age = 33; cody.gender = 'male'; cody.getGender = function () { return cody.gender; }; // Getting properties. console.log( cody.living, cody.age, cody.gender, cody.getGender() ); // Logs 'true 33 male male'. // Updating properties, exactly like setting. cody.living = false; cody.age = 99; cody.gender = 'female'; cody.getGender = function () { return 'Gender = ' + cody.gender; }; console.log(cody); </script></body></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
<!DOCTYPE html><html lang="en"><body><script> // Creating a cody Object() object. var cody = new Object(); // Setting properties. cody['living'] = true; cody['age'] = 33; cody['gender'] = 'male'; cody['getGender'] = function () { return cody.gender; }; // Getting properties. console.log( cody['living'], cody['age'], cody['gender'], cody['getGender']() // Just slap the function invocation on the end! ); // Logs 'true 33 male male'. // Updating properties, very similar to setting. cody['living'] = false; cody['age'] = 99; cody['gender'] = 'female'; cody['getGender'] = function () { return 'Gender = ' + cody.gender; }; console.log(cody); </script></body></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
<!DOCTYPE html><html lang="en"><body><script> var foobarObject = { foobar: 'Foobar is code for no code' }; var string1 = 'foo'; var string2 = 'bar'; console.log(foobarObject[string1 + string2]); // Let's see dot notation do this! </script></body></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
<!DOCTYPE html><html lang="en"><body><script> var myObject = { '123': 'zero', 'class': 'foo' }; // Let's see dot notation do this! Keep in mind 'class' is a keyword in JavaScript. console.log(myObject['123'], myObject['class']); //Logs 'zero foo'. // It can't do what bracket notation can do, in fact it causes an error. // console.log(myObject.0, myObject.class); </script></body></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
<!DOCTYPE html><html lang="en"><body><script> var foo = { bar: 'bar' }; delete foo.bar; console.log('bar' in foo); // Logs false, because bar was deleted from foo. </script></body></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
<!DOCTYPE html><html lang="en"><body><script> var myArray = []; console.log(myArray.foo); // Logs undefined. /* JS will look at Array.prototype for Array.prototype.foo, but it is not there. Then it will look for it at Object.prototype, but it is not there either, so undefined is returned! */ </script></body></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
<!DOCTYPE html><html lang="en"><body><script> // myArray is an Array object. var myArray = ['foo', 'bar']; console.log(myArray.join()); // join() is actually defined at Array.prototype.join </script></body></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
<!DOCTYPE html><html lang="en"><body><script> var myArray = ['foo', 'bar']; console.log(myArray.hasOwnProperty('join')); // Logs false. </script></body></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
<!DOCTYPE html><html lang="en"><body><script> // myArray and Array.prototype contain no toLocaleString() method. var myArray = ['foo', 'bar']; // toLocaleString() is actually defined at Object.prototype.toLocaleString console.log(myArray.toLocaleString()); // Logs 'foo,bar'. </script></body></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
<!DOCTYPE html><html lang="en"><body><script> var myObject = {foo: 'value'}; console.log(myObject.hasOwnProperty('foo')) // Logs true. // Versus a property from the prototype chain. console.log(myObject.hasOwnProperty('toString')); // Logs false. </script></body></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
<!DOCTYPE html><html lang="en"><body><script> var myObject = { foo: 'value' }; console.log('foo' in myObject); // Logs true. </script></body></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
<!DOCTYPE html><html lang="en"><body><script> var myObject = { foo: 'value' }; console.log('toString' in myObject); // Logs true. </script></body></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
<!DOCTYPE html><html lang="en"><body><script> var cody = { age: 23, gender: 'male' }; for (var key in cody) { // key is a variable used to represent each property name. // Avoid properties inherited from the prototype chain. if (cody.hasOwnProperty(key)) { console.log(key); } } </script></body></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
<!DOCTYPE html><html lang="en"><body><script> for (x in window) { console.log(x); // Logs all of the properties of the window/head object. } </script></body></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
<!DOCTYPE html><html lang="en"><body><script> for (x in window.document) { console.log(); } </script></body></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.
Comments