JavaScript 1.8.5 does not have classes but it does have "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.
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
__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.
In order to understand Constructors you need to understand how Functions are executed in a specific scope.
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 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
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, …}
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}
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]
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.
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
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
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!'