objects.js

Objects in JavaScript

Creating objects without constructors

console.log('Creating objects without constructors\n\n')

We can create objects without constructors, just POJO (plain old javascript objects).

Define an object

var obj = {
  aProperty: 'bar',
  aMethod: function (val) {
    return val*val;
  }
}

Assert some things about it. This is exactly as we would expect, and most people are used to.

console.dir( obj )              //=> { aProperty: 'bar', aMethod => [Function] }
console.log( obj.aProperty );   //=> 'bar'
console.log( obj.aMethod(2) )   //=> 4

This style of object is okay, but it's more like a data-structure than an object. It's hard to create new instances of our object without cloning it.

Using a constructor

console.log('Using a constructor\n\n')

We can use a constructor function to construct new instances of our "class".

My constructor function. There is nothing special about constructor functions, only context. Generally if a function is going to be used as a constructor it is capitalized (but only by convention. When a function is used as a constructor, this is a new object instance. Here we define properties and methods directly on the object itself inside the constructor.

function MyObject() {
  this.aProperty = 'bar'
  this.aMethod = function (val) {
    return val*val
  } // We don't need to return anything if it's being used as a constructor
}

Construct a new instance

var obj = new MyObject();

Everything works just as it did for our object literal above.

console.dir( obj );             //=> { aProperty: 'bar', aMethod => [Function] }
console.log( obj.aProperty );   //=> 'bar'
console.log( obj.aMethod(2) );  //=> 4

There is nothing special about constructors

console.log( 'There is nothing special about constructors\n\n' );

A constructor is just a function. It's use "as a constructor" is defined by the use of the new keyword, nothing else. We'll come back to the new keyword ina second, but this means that...

function AnotherObject() {
  console.log( '`this is` ', this );
  this.foo = 'bar'
}

Calling with a constructor as normal, creates a new object

var obj = new AnotherObject()  // logs "`this` is {}" 
console.log( obj.foo )           //=> 'bar'

We can call a constructor without the new keyword. But then it just runs as a function, and this will be the global object (window in the browser)

obj = AnotherObject()         // logs "`this` is > Window"
console.log( obj )              //=> undefined since AnotherMethod() doesn't return anything

Returning from a constructor

console.log( 'Returning from a constructor\n\n' );

Normally we don't need to explicitly return anything from a constructor. This isn't really magic: inside the constructor this is set, by reference, to our new object, and that object is modified with calls like this.property = 'foo', so we don't need to return anything. The new keyword will ensure that our newly created object is returned, so we can save it off into a variable.

But we can if we like explicitly return objects from our constuctor.

console.log( 'But we _can_ if we like explicitly return objects from our constuctor\n\n' );

If an object is returned from the constructor it will be returned by the new keyword instead of our newly created object

function MyConstructor() {
  this.foo = 'bar'; // this effectively does nothing as we return an explicit object

  return { foo: "A different object" };
}

var obj = new MyConstructor()
console.log( obj.foo )           //=> "A different object"

If the object returned is a string/number/etc it will not be returned instead

So here, the fact we return 10 is ignored.

function MyConstructor2() {
  this.foo = 'bar';

  return 10;
}

var obj = new MyConstructor2()
console.log( obj )           //=> { foo: 'bar' } (not 10!)

This is subtly different though. Here we are returning an object not a literal, so it is returned instead.

function MyConstructor3() {
  this.foo = 'bar';

  return new String("A string object");
}

var obj = new MyConstructor3();
console.log( obj )          //=> > String("A string object")

Prototypes

console.log('Prototypes');

When we call a method or look for a property that doesn't exist on an object, JavaScript will also look at the object's [[prototype]], to see the method or property exists there, and so on up the prototype chain.

In general we cannot access the prototype of an object. But we can modify the prototype of a constructor. (Confusing huh?!).

When we construct an object (with the new keyword) the objects [[prototype]] is set to the constructor function's prototype. We cannot access/modify/set an object's [[prototype]] directly after it has been constructed. "Phew!"

Basic prototype example

console.log('Basic prototype example');

Create a constructor function

function Animal() {}

Add some, properties/methods to the constructor function's prototype

Animal.prototype.name = "animal"
Animal.prototype.talk = function() {
  console.log("I am ", this.name);
}

Construct a new animal

dog = new Animal()

We have an apparently empty object

console.log(dog);     //=> {}

Set the name property on the object

dog.name = "Rover"    
console.log(dog)      //=> { name: "Rover" }

Our dog apparently doesn't have a talk() method, but if we call it it still works. This is because the talk() method has been found on the dog's prototype.

dog.talk()            //=> I am Rover

In some implementations of JS (node/chrome/firefox/opera) we can lookup/modify an objects prototype directly with the __proto__ attribute. But the double underscores, and the lack of cross browser support means that in general we shouldn't.

console.log( dog )           //=> { name: "Rover" }
console.log( dog.__proto__ ) //=> { name: "animal", talk: [Function] }

The new keyword

console.log('The `new` keyword\n\n')

We have now seen everything we need to, to know how the new keyword works. It:

  1. Creates a new object
  2. Sets the [[prototype]] of the object to the constrctor functions prototype
  3. Executes the constructor function with this set to the new object.

This means we could define our own function to replicate the behaviour of new, (note this will only work where __proto__ is supported (not i.e)

function myNew(constructorFunction) {
  var newObject = {};
  newObject.__proto__ = constructorFunction.prototype;
  
  var constructorReturned = constructorFunction.apply(newObject);
  
  if ( typeof constructorReturned === 'object' ) {
    return constructorReturned;
  } else {
    return newObject;
  }
}

function Animal() {}

Animal.prototype.name = "an animal";
Animal.prototype.greet = function() { 
  return "Hi I am " + this.name;
}

With the normal new

dog = new Animal();
dog.name = "Rover"
console.log( dog );         //=> { name: "Rover" }
console.log( dog.greet() ); //=> "Hi I am Rover"

With my new (won't work on IE)

dog = myNew( Animal );
dog.name = "Rover"
console.log( dog );         //=> { name: "Rover" }
console.log( dog.greet() ); //=> "Hi I am Rover"

So when/where/why prototypes?

console.log('So when/where/why prototypes?\n\n');

Prototypes let us mimic inheritance chains like in a regular OO language.

console.log('Prototypes let us mimic inheritance chains like in a regular OO language.\n\n');

function Animal() {}
Animal.prototype.legs = null
Animal.prototype.greet = function() { console.log("Hi I have ", this.legs, "legs") };

Note here, that we are setting the prototype to an instance of an animal. Prototypes are just objects. Nothing more, nothing less.

function Dog() {}
Dog.prototype = new Animal();
Dog.prototype.legs = 4
Dog.prototype.breed = null

function Retriever() {}
Retriever.prototype = new Dog();
Retriever.prototype.breed = "Retriever"

myDog = new Retriever();
console.log( myDog.breed );   //=> "Retriever"
myDog.greet()                 //=> logs "Hi I have 4 legs"