Part 4 of a series on Javascript Fundamentals. See the full list of posts

The goal of this post is to, at a high level, cover some of the basics of creating, using and implementing objects in Javascript. In a second part, we'll follow up with understanding Javascript object prototype, the prototype chain and making better use of composition and delegation over inheritance.

I'll mention objects being "prototype linked" to other objects in this article quite often. If you're still confused by Javascript's prototype based object delegation scheme, just hold tight for the second part coming soon

Making Objects

The easiest way to create objects in Javascript, and the most efficient, is using object literals.

var actor = {  
  name: "David Tennant",
  age: 44
};

Here,actor is an object with two properties, name, which contains a string, and age which contains a number. This is the same as using var obj = new Object() and then assigning properties. Both objects are prototype linked the standard Object prototype.

We can also create a function to make objects by calling it as a constructor function using the new operator.

function Person(name, age) {  
   this.name = name;
   this.age = age;
}
var actor = new Person("David Tennant", 44);  

Using the new operator introduces some implicit operations, which we'll discuss shortly.

We also have Object.create() as well, though it works a bit differently.

Object.create() takes two parameters and returns a new object. This object is prototype linked to the first object passed as a parameter. The second parameter is an optional object containing property descriptors to assign directly on the newly returned object.

A property descriptor object is an object containing definitions for properties. The property names in this descriptor object are themselves objects which contain various attributes attributed to that property, such as: value, writable, configurable, and enumerable.

Try the following in Chrome DevTools to see how basic object properties are setup:

var obj = { a: 42 };  
console.log("%o", Object.getOwnPropertyDescriptor(obj, 'a'));  
// Object
//    configurable: true
//    enumerable: true
//    value: 42
//    writable: true
//    ...

Using Object.create() we can create objects using another object as a prototype (an exemplar); and an optional property descriptor object. Any properties on the exemplar are accessed via the object's prototype chain; and any properties in the descriptor object are own properties assigned directly to the new object returned.

// creates a new, empty object linked to Object.prototype
var obj = Object.create({});  

// create a new, empty object not linked to Object.prototype
obj = Object.create(null);

var person_proto = {  
      name: "", age: null
    },
    descriptor = {
      tardis: { value: "blue box" }
    };

// creates a new object based on person_proto
var actor = Object.create(person_proto, descriptor)  
actor.name = "David Tennant";  
actor.age = 44;  
// actor:
//   name: "David Tennant"
//   age: 44
//   tardis: "blue box"

In ES6, we could use the following pattern in creating our actor instance instead:

var actor = Object.assign(Object.create(person_proto), {  
  tardis: "blue box"
});

ES6's Object.assign() is similar to the many extend() style functions that libraries like jQuery and Underscore use to create/extend objects using one or more objects.

Understanding what new does

There is much magic happening in a function invoked with the new operator. At its most basic, when using new, the function invocation is hijacked and given a new, empty object as context, which is assigned to the local variable this inside the function.

The expectation of functions called as constructors is that they do something with that context: assigning properties or otherwise manipulating it; and magically, that same context object is returned from the function.

Using new works similarly to the following function:

function _new(/* constructor, param, ... */) {  
  var  args = [].slice.call(arguments),
       constructor = args.shift(), 
       context = Object.create(constructor.prototype),
       res;

  res = constructor.apply(context, args);
  return (typeof res === 'object' && res != null) ? res : context;
}
var actor = _new(Person, "David Tennant", 44);  

This is very simplified, but it gives you an idea of what's going on.

But as Eric Elliot, Reginald Braithwaite and Kyle Simpson point out using new and constructor functions can be problematic for a number of reasons.

“If a feature is sometimes dangerous, and there is a better option, then always use the better option.” -- Douglas Crockford

Object Access

Objects have properties. Properties are named keys on the object that can hold values. Those values can be basic types like strings, numbers, booleans or even object types like arrays, functions or other objects stored as references. The easiest way to access properties on an object is using the . operator.

var obj = {  
  foo: "bar", 
  baz: 42,
  print: function(s){ console.log(s); }
};

obj.foo;  
obj.print("hi");  

You can also access object properties using the [] operator. The [] operator expects a string argument that identifies the property key you're trying to access. Any non-string value will be coerced to a string and then used.

var obj = {};  
obj["name"] = "David Tennant";  
obj["age"] = 44;  
obj["33"] = "what?";  // can only access using [] operator  
obj[true] = "true";   // coercion to property named "true"

obj.33;      // syntax error!  
obj["33"];   // "what?"  
obj["true"]; // "true"  
obj.true;    // "true"  

This makes it easy to dynamically access properties at runtime by storing or building the property names.

ES6 gives us a way to specify properties as shorthand, as well as computed property names. For example,

let age = 44;  
let obj = {  
  name: "David Tennant",
  // short for, who: function(){...}
  who() { console.log("the doctor"); },
  // short for age: age
  age,
  // computed property names!
  ["hello_" + (() => "sweetie")()]: "the wife"
};

obj.who();                       // "the doctor"  
console.log(obj.hello_sweetie);  // "the wife"  

I'll point out some more resources on the new ES6 object features at the end of this post. Outside of computed property names, most of the object literal shorthand notation is just syntactic sugar to make declaring and working with objects in Javascript easier.

Iterating Objects

Now that our objects have all these properties, maybe we want to iterate through them. Objects themselves are not iterable objects like arrays - they don't have an iterator defined for them, as access isn't as straight forward as one might think - you might iterate over the property names or property values; and what about prototype linked objects?

You can iterate over objects using the special for..in loop, which does iterate over the object's property keys, not it's values. But, for..in will iterate over prototype linked, enumerable properties as well, so be sure and use a .hasOwnProperty() to help limit the scope and recursion into the prototype chain of the object you're iterating.

var obj = { a: 1, b: 2 },  
    obj2 = Object.create(obj, {
      c: { value: 3, enumerable: true },
      d: { value: 4, enumerable: true } 
    });

for (var prop in obj2) {  
    console.log(prop);
}
// a b c d
for (var prop in obj2) {  
    if (obj2.hasOwnProperty(prop)) {
        console.log(prop);
    }
}
// c d

You can get access to an object's property keys using Object.keys(). The benefit of which, is that it only returns enumerable properties directly on the object in question - not prototype linked, enumerable properties.

Object.keys(obj2).forEach(function(p) {  
  console.log(p + " = " + obj2[p]);
});
// c = 3
// d = 4

For information on ES6's new object shorthand properties and computed properties, check out Strongloop's article and MDN.

Also take a look at Kyle Simpson's You Don't Know JS: this & Object properties for more details about Javascript objects, new and more.

In the next article I'll cover specifically Javascript objects and prototypes and talk about why you should be using composition and delegation instead of trying to mimic "class" style inheritance with new and constructor functions.

Tags: