Arieh.co.il

Javascript Guide - Advanced Patterns

You can now also navigate the guide through the table of contents.

Now that we know how prototypes work, at least on the basic level, I think it's time we learn a bit about the concept of objects and inheritance.

In the programming world, we have an entire paradigm of Object Oriented programming. Although JS is not object oriented, it is Object Based, and in fact, because it is such a powerful language, almost everything that can be done with Object-Oriented languages can be mimicked with JS.

About the concept of Encapsulation

A language construct that facilitates the bundling of data with the methods operating on that data.

Wikipedia

Well, actually, in Wikipedia that part is a secondary definition, but for us it's more important. The concept of encapsulation (as we are going to handle it) means that a behavior should be attached to the object that performs it. We already encountered that concept when we used Array.push - the push action is a part of the array prototype, and that means that every array has it. It is an action an array performs on itself.

So, when we create our code, it is often useful to notice if certain part of our code interact with the same parts, or that their behavior it interconnected, and wrap them within an object. Since we still haven't manipulated our web pages yet, the examples here are going to be abstract, but stay with me on this.

Creating a Person

Lets take our Person Example from before, but extend it a little. Here's how we do it:

  1. First thing we should ask ourselves is - What properties do we want our Object to have? - in our example, it's going to be - name,age, location and energy.
  2. Now we need to decide if any of these should have a default value, and if some of these can be changed. So in our case - location and energy should start the same for all persons, but age should be changeable on construction.
  3. We now need to decide what actions it will perform - our person will walk, and talk. Both will consume energy, and so he should also eat.

With these in mind, we are ready to write our code. In this example (and the ones that will follow), I will use some patterns that are a bit more advanced than what we learned so far, so you can learn from them.

    function Person (first,last,age){
        this.name = name;
        this.last = last;
        
        this.age = age || 20;
        //if age wasn't set, it will be undefined, which is "falsy". when JS encounters this notation, 
        //it will attempt to assign the first argument, and if it is false it will use the second one.
        //this pattern allows us to set default values to our properties.
        //In this case, what I'm saying is "use provided age. if non provided set to 20".
    }
    
    Person.prototype.energy = 100;
    
    Person.prototype.location = 0;
    
   
    Person.prototype.walk = function(steps){
        //receives how many steps the person walked. advances the person's movement.
        //when a person is too young or too old he only moves half the distance, and uses twice that energy.
    
        var speed = 1
           , energy = 1
           , energy_needed = 0;
           
        if (this.age < 10 || this.age > 50){ // when our person is too young or too old
            speed = 0.5; // he will walk half speed
            energy = 2; //and consume twice the energy
        } 
        
        energy_needed = steps * energy;//how much energy is needed for this walk
        
        if (this.energy < energy_needed){
            console.log('I don\'t have enough energy for this task...');
            return;
        } 
        
        this.energy -= energy_needed;
        
        this.location += steps * speed;
    };
    
    Person.prototype.talk = function(){
        if (this.energy <= 1) {//even talking takes energy
            console.log('I don\'t have enough energy for this task...');
            return;
        }
        this.energy--;
        console.log('My Name Is '+this.first+' '+this.last+' and I am '+this.age+' Years old');
    };
    
    Person.prototype.eat = function(calories){
        this.energy += calories;//yummm
    };
    
    var bob = new Person('Bob','Alice',25);
    bob.walk(20); 
    console.log(bob.location); //will be 20, because bob is young, and moves one tile per step
    bob.talk(); //will log My Name is Bob Alice and i am 25 Years old
    
run code

This was a lot more code than what we're used to, so take the time to see you get it. It's not that complicated in fact.

Extending our Person

But, in real life, although we have many persons, many of them perform much more functions than eat, walk and talk. Lets take for example, a Warrior - he can also attack people.

Here the concept of inheritance kicks in. In object oriented programing, we can create an object (or, in most programing languages - class) that inherit another. In JS, this is done by assigning one object to another' prototype.

But problems start to kick in when we want to inherit Classes that have constructors. You see, although a Function can inherit it's parent's method, it does not inherit it's construction code. So, when we do this:

    Warrior = function(){};
    Warrior.prototype = Person.prototype;
    var war = new Warrior("Bob","Alice");
    war.talk(); // will log My Name is undefined undefined and i am undefined Years old
    
run code

Person's constructor won't run, although Warrior did inherit it's talk method. To make it run, we need to call it in a way that will make it's this point to our current warrior. There are 2 ways to do this, and both will be done through manipulation of scope.

  1. Using Scopes

    As you recall, the this keyword is very sensitive to the scope. As it is now, Person is located on the global scope, so when calling it as is (without new), it's this will point to the global scope.

    But, if we assign Person to a method of Warrior, calling that method will point to the Warrior instance:

                function Warrior(first,last){
                    this.constructor = Person;
                    this.constructor(first,last);
                };
                Warrior.prototype = Person.prototype;
                
                var war = new Warrior("Bob","Alice");
                war.talk(); //will log My Name is Bob Alice and i am 25 Years old
                
    run code

    This might seem a little hackish, but it works.

  2. Apply

    The second method is to use one of every function's method - apply. function.apply is a way for us to run a function, specifying it what scope to use. It accepts 2 arguments:

    1. A scope to use. If non is supplied (null) it will use the global scope.
    2. An array of arguments to pass to the function.

    So, if we pass Person.apply the Warrior's this and it's arguments (which already is Array-like), we can receive the previous result, without the use of a special argument:

                function Warrior(first,last,age){
                    Person.apply(this,arguments);
                };
                Warrior.prototype = Person.prototype;
                
                var war = new Warrior("Bob","Alice");
                war.talk();//will log My Name is Bob Alice and i am 25 Years old
                
    run code

    apply is actually an extremely powerful tool, for many other uses, so you should keep it in the back of your head.

I personally like the second one better, so that's the one we'll use.

Now let's profile our Warrior class. As I said, I want it to be able to attack. For it to attack, I want us to be able to set it's attack power.

The way I want the attack method to work is that it will receive a Person, and remove some of it's energy. We want to make sure that attack actually received a Person, and so we will use the instanceof operator.

instanceof has a bit weird syntax - a sentence like one. They way it works is like this: sub_class instanceof sup_class - is sub_class an instance of sup_class. What this actually does is to check whether one class has another's prototype in it's prototype-chain. This means that any object that has it's prototype point to Person will be an instance of Person. That also means that any instance of Warrior will also be an instance of Person and so forth.

Just so we can get the hang of it:

    console.log({} instanceof Object); //true
    console.log(new String('') instanceof String); //true
    console.log(1 instanceof Object); //false
    
run code

So, to go on with our code, the attack method:

    Warrior.prototype.power = 10;
    Warrior.prototype.attack = function(person){
        if (!(person instanceof Person)){
            console.log('Provided person is invalid');
            return;
        }
        person.energy -= this.power;
    }
    
    var bob = new Person("Bob","Alice")
       , john = new Warrior("John","Smith");
     
     john.attack(bob);
     
     console.log(bob.energy); //since the attack took off 10 point of energy, it will log 90 ()
    
run code

To wrap it up

We've learned quite a few tricks today, and some have them were probably a bit confusing. If that's not enough, we wrote quite a lot of code (I even used an external file this time...). So here's a quick list of the stuff we learned:

  1. We've learned how to set default but dynamic values to our properties, using this syntax: this.age = age || 20.
  2. We've learned how to use prototypes and functions to encapsulate the behavior of our objects.
  3. We've learned how to use the prototype to extend our objects into new ones.
  4. We've learned how to play with the scope to reuse other object's constructors. While we were at it, we learned of the very cool apply method.
  5. Finally, we've learned how to use the instanceof operator to check if a certain object is an instance of another.

Now that we've learned quite a lot of the bases of ECMAScript, I feel it is now safe to start playing with the DOM.

JavaScript Reference, JavaScript Guide, JavaScript API, JS API, JS Guide, JS Reference, Learn JS, JS Documentation