Skip to content

Instantly share code, notes, and snippets.

@jaredatron
Created March 19, 2013 22:04
Show Gist options
  • Save jaredatron/5200561 to your computer and use it in GitHub Desktop.
Save jaredatron/5200561 to your computer and use it in GitHub Desktop.

OOJS - Object Oriented JavaScript

JavaScript 1.8.5 does not have classes but it does have "Prototypal Inheritance".

Prototypal Inheritance

Prototypal inheritance might be better named as simple object inheritance. It basically means you have one object that inherits from a single other object.

What does that inheritance mean?

It simply means that:

when you ask the child object for an attribute `person.name`
if the child object has a name attribute
  it returns it
else
  it send that call to it parent object (its prototype)
end

A simple example

var car = {make: 'Ford', model: 'Torus'};

var broken_car = {broken: true};
broken_car.__proto__ = car;

car.make;          //= 'Ford'
car.model;         //= 'Torus'
car.broken;        //= undefined
broken_car.make;   //= 'Ford'
broken_car.model;  //= 'Torus'
broken_car.broken; //= true

car.make = 'GM';

car.make;          //= 'GM'
car.model;         //= 'Torus'
car.broken;        //= undefined
broken_car.make;   //= 'GM'
broken_car.model;  //= 'Torus'
broken_car.broken; //= true

broken_car.make = 'Honda';

car.make;          //= 'GM'
car.model;         //= 'Torus'
car.broken;        //= undefined
broken_car.make;   //= 'Honda'
broken_car.model;  //= 'Torus'
broken_car.broken; //= true

What is that __proto__ thing?

__proto__ is an easy way to get and set the protoype of an object in JavaScript.

However: __proto__ does not work in all browsers.

...so we have to use constructors.

Prototypal Inheritance Using Constructors

In order to understand Constructors you need to understand how Functions are executed in a specific scope.

Function scopes

When a JavaScript function executes it is always executed on an object. That object is what this refers to inside that function. this is often referred to as 'the scope' at which a function is applied.

The default scope is the global (AKA top-level) object (AKA the window).

The Global Object (window)

The global object is what this references in the top level namespace. In the browser this and window are the same object.

this;            // Window {window: Window, …}
window;          // Window {window: Window, …}
this === window; // true

Calling a function on it's own

Lets say we have the following function

var print_my_scope = function(){
  console.log('this ->', this);
}

When we call it on its own we can see that it is applied to the global object window

print_my_scope();
// this -> Window {window: Window, …}

Calling a function on an object

Lets see what happens when we execute that same function from another object.

var poodle = {
  im_a_poodle: true,
  print_my_scope: print_my_scope
};

poodle.print_my_scope();
// this -> Object {im_a_poodle: true, print_my_scope: function}

As you can see when you call that same function print_my_scope from the poodle object it changes the value of this for that one execution.

Note that the print_my_scope function is not changed in anyway. Executing print_my_scope on it's own still acts the same.

This should illustrate all the examples we've covered so far:

print_my_scope();
// this -> Window {window: Window, …}

this.print_my_scope();
// this -> Window {window: Window, …}

window.print_my_scope();
// this -> Window {window: Window, …}

poodle.print_my_scope();
// this ->Object {im_a_poodle: true, print_my_scope: function}

Calling a function using apply

Another way to execute a function on an object is to you use the apply method on the function it's self.

print_my_scope.apply(poodle);
// this -> Object {im_a_poodle: true, print_my_scope: function}

You can of course also do:

print_my_scope.apply(window);
// this -> Window {window: Window, …}

print_my_scope.apply(this);
// this -> Window {window: Window, …}

Or even:

print_my_scope.apply([1,2,3]);
// this -> [1, 2, 3]

Constructors

A constructor creates a new object and then executes its self on that object. The object it creates also "inherits" or delegates down to that function's prototype object.

A Constructor is just a function that is called with the new keyword. So:

function returnThis(){ return this; }

returnThis;       // returnThis
returnThis();     // window
new returnThis;   // returnThis {}
new returnThis(); // returnThis {}

As you can see all JavaScript functions are constructors when you use the new keyword to call them. When a function is called with the new keyword it changes the value of this within that function to a new object that extends from that functions prototype object.

Instantiation

Here is an example of a method that can be used to create a new object, assign the given name to that object and then return it.

function Dog(name){
  this.name = name;
}

var spot   = new Dog('Spot');
var sparky = new Dog('Sparky');

spot.name;   // 'Spot'
sparky.name; // 'Sparky'

If you forget to use the new keyword then this will refer to the global object (window) and you'll end up with:

var cuddles = Dog('Cuddles');

cuddles;     // undefined
window.name; // 'Cuddles'
name;        // 'Cuddles'

So be careful =)

You can think about the object construction process like this:

// create a new object that delegates down to `Dog.prototype`
var spot = Object.create(Dog.prototype);

// run the Dog function on that new object with the arguments ['Spot'];
Dog.apply(spot, ['Spot']);

Object.create will return an object that delegates to the given object

Prototypical Inheritance

In the dog example above both spot and sparky are objects that delegate down to Dog.prototype. This means whatever changes that are made to Dog.prototype are reflected in both spot and sparky.

This is clearly a problem for any dog worth its salt:

sparky.bark()
// TypeError: Object #<Dog> has no method 'bark'
// Dogs should be able to bark right?

We can give all dogs the ability to bark by defining a bark method on Dog.prototype:

Dog.prototype.bark = function(){
  console.log('BARK! BARK!');
};

Now both sparky and spot can bark:

sparky.bark();
// BARK! BARK!

spot.bark();
// BARK! BARK!

This is possible because both sparky and spot are objects that point to Dog.prototype so their attributes calls can fall through.

You can visually inspect these relationships in the console:

dir(spot)
▼ Dog
    name: "Spot"
  ▼ __proto__: Dog
    ► bark: function (){
    ► constructor: function Dog(name){
    ► __proto__: Object

A Complex Example

function Animal(){
  this.alive = true;
}

Animal.prototype.isAlive = function(){
  return this.alive;
}

Animal.prototype.die = function(){
  this.alive = false;
}

var pet = new Animal;

pet.isAlive; // true
pet.die();
pet.isAlive; // false

// "subclassing"

function Dog(name){
  Animal.apply(this);
  this.name = name;
}

Dog.prototype = Object.create(Animal.prototype);

Dog.prototype.speak = function(){
  console.log('BARK!', this.name, 'BARK!');
}

var sparky = new Dog('Sparky');

sparky.isAlive; // true
sparky.die();
sparky.isAlive; // false

sparky.speak() // 'BARK! Sparky BARK!'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment