Javascript - OOP 2

javascript-oop

How can we use classical inheritance with JavaScript?

The method method takes a method name and a function, adding them to the class as a public method:

Function.prototype.method = function (name, func) {
    this.prototype[name] = func;
    return this;
};

This adds a public 'method' method to the Function.prototype, so all functions get it. It takes a name and a function, and adds them to a function's prototype object.

Next comes the inherits method, which indicates that one class inherits from another. It should be called after both classes are defined, but before the inheriting class's methods are added. The inherits method is similar to Java's extends:

Function.method('inherits', function (parent) {
    var d = {}, p = (this.prototype = new parent());
    this.method('uber', function uber(name) {
        if (!(name in d)) {
            d[name] = 0;
        }        
        var f, r, t = d[name], v = parent.prototype;
        if (t) {
            while (t) {
                v = v.constructor.prototype;
                t -= 1;
            }
            f = v[name];
        } else {
            f = p[name];
            if (f == this[name]) {
                f = v[name];
            }
        }
        d[name] += 1;
        r = f.apply(this, Array.prototype.slice.apply(arguments, [1]));
        d[name] -= 1;
        return r;
    });
    return this;
});

We make an instance of the parent class and use it as the new prototype. We also correct the constructor field, and we add the uber method (similar to Java's super) to the prototype as well. The uber method looks for the named method in its own prototype. If we are doing Classical Inheritance, then we need to find the function in the parent's prototype. The return statement uses the function's apply method to invoke the function, explicitly setting this and passing an array of parameters. The parameters (if any) are obtained from the arguments array. Unfortunately, the arguments array is not a true array, so we have to use apply again to invoke the array slice method.

Finally, the swiss method:

Function.method('swiss', function (parent) {
    for (var i = 1; i < arguments.length; i += 1) {
        var name = arguments[i];
        this.prototype[name] = parent.prototype[name];
    }
    return this;
});

The swiss method loops through the arguments. For each name, it copies a member from the parent's prototype to the new class's prototype.

Classical Inheritance

function Parenizor(value) {
    this.setValue(value);
}

Parenizor.method('setValue', function (value) {
    this.value = value;
    return this;
});

Parenizor.method('getValue', function () {
    return this.value;
});

Parenizor.method('toString', function () {
    return '(' + this.getValue() + ')';
});

So now we can write:

myParenizor = new Parenizor(0);
myString = myParenizor.toString();

As you would expect, myString is "(0)".

Now we will make another class which will inherit from Parenizor, which is the same except that its toString method will produce "-0-" if the value is zero or empty:

function ZParenizor(value) {
    this.setValue(value);
}

ZParenizor.inherits(Parenizor);

ZParenizor.method('toString', function () {
    if (this.getValue()) {
        return this.uber('toString');
    }
    return "-0-";
});

Because objects in JavaScript are so flexible, you will want to think differently about class hierarchies. Deep hierarchies are inappropriate. Shallow hierarchies are efficient and expressive.

Custom Objects:

function Person(first,last) {
    this.first = first;
    this.last = last;
    this.fullName = function() {
         return this.first + " " + this.last;
    };
    this.fullNameReversed = function() {
         return this.last + ', ' + this.first;
    }
}
var s = new Person("Simon","Willison");

The 'new' keyword is strongly related to 'this'. The 'new' keyword creates a brand new empty object, and then calls the function specified with 'this' set to that new object. Functions that are designed to be called by 'new' are called constructor functions. Common practices is to capitalise these functions.

Every time we create a Person object, we are creating two new function objects within it - wouldn't it be better if this code was shared?

function personFullName() {
    return this.first + ' ' + this.last;
}
function personFullNameReversed() {
    return this.last + ', ' + this.first;
}
function Person(first,last) {
    this.first = first;
    this.last = last;
    this.fullName = personFullName;
    this.fullNameReversed = personFullNameReversed;
}

That's better: we are creating the method functions only once, and assigning references to them inside the constructor. Can we do better than that? The answer is yes:

function Person(first,last) {
    this.first = first;
    this.last = last;
}
Person.prototype.fullName = function() {
    return this.first + ' ' + this.last;
}
Person.prototype.fullNameReversed = function() {
    return this.last + ', ' + this.first;
}

Person.prototype is an object shared by all instances of Person. It forms part of a lookup chain: any time you attempt to access a property of Person that isn't set, javascript will check Person.prototype to see if that property exists there instead. As a result, anything assigned to Person.prototype becomes available to all instance of that constructor via this object.

This is incredibly powerful tool. Javascript lets you modify something's prototype at anytime in your program, which means you can add extra methods to existing object at run-time.

As mentioned before, the prototype forms part of a chain. The root of that chain is Object.prototype.

Remember how avg.apply() had a null first arguments? The first argument to apply() is the object that should be treated as 'this'.

The apply() function has a sister function named call, which lets you set 'this' but takes an expanded argument list as opposed to an array:

function lastNameCaps() {
   return this.last.toUpperCase();
}
var s = new Person("Simon","Willison");
lastNameCaps.call(s);
// Is the same as
s.lastNameCaps = lastNameCaps;
s.lastNameCaps();

Javascript function declarations are allowed inside other functions. An important detail of nested functions in Javascript is that they can access variables in their parent function's scope:

function betterExampleNeeded() {
    var a = 1;
    function oneMoreThanA() {
        return a + 1;
    }
    return oneMoreThanA();
}

This provides a great deal of utility for writing more maintainable code. If a function relies on one or two other functions that are not useful to any part of your code, you can nest those utility functions inside the function that will be called from elsewhere. This keeps the number of functions that are in the global scope down, which is a good thing. This is also a great counter to the lure of global variables. When writing complex code, it is often tempting to use global variables to share values between multiple functions - which leads to code that hard to maintain. Nested functions can share variables in their parent, so you can use that mechanism to couple functions together when it make sense without polluting your global namespace. This technique should be used with caution, but it's a useful ability to have.

function Pet(name) {
   this._name = name;
}
Pet.prototype._name;
Pet.prototype.getName = function () {
   return this._name;
}
var p = new Pet("Max");
alert(p.getName());

Inheritance:

function Dog(name) {
   Pet.call(this,name);
}
Dog.prototype = new Pet();
Dog.prototype.wagTail = function () {
}
person = new Object ();
person.name = "Tim";
person.height = "6ft";
person.run = function() {
}

Creating object using literal notation (more robust):
timObject = {
    property1 : "Hello",
    property2 : "MmmMmm",
    property3 : ["mmm", 2, 3, 6, "aaa"],
    method1: function() {}
}
person = new Object ();
person.name = "Tim";
person.height = "6ft";
person.run = function() {
}

Creating object using literal notation (more robust):
timObject = {
    property1 : "Hello",
    property2 : "MmmMmm",
    property3 : ["mmm", 2, 3, 6, "aaa"],
    method1: function() {}
}
// The constructor for our 'Lecture'
// This constructor takes 2 arguments, name and teacher
function Lecture( name, teacher ) {
  // Save them as local properties of the object
  this.name = name;
  this.teacher = teacher;
}

// A method of the Lecture class
Lecture.prototype.display = function() {
  return this.teacher + " is teaching " + this.name;
};

// The constructor for Schedule
// This constructor takes one argument, which is an array of lectures
function Schedule( lectures ) {
  this.lectures = lectures;
}

// Another method
Schedule.prototype.display = function() {
  var str = '';
  for (var i = 0; i < this.lectures.length; i++) {
    str += this.lectures[i].display() + " ";
  }
  return str;
};

// Example of using the Schedule class
var my_schedule = new Schedule([
  new Lecture( "Gym", "Mr. Smith" ),
  new Lecture( "Math", "Mrs. Jones" ),
  new Lecture( "English", "TBD" )
]);
// Two examples of creating a simple object and setting properties
var obj = new Object();

// Set some properties of the object to different values
obj.val = 5;
obj.click = function() { alert( "hello" ); };

// Here is some equivalent code, using the {...} shorthand
var obj = {
  val: 5,
  click: function(){ alert( "hello" ); }
};

In reality, there isn't much more than that. Where things get tricky, however, is in the creation of new objects, especially ones that inherit the properties of other objects.

Unlike most other object-oriented languages, JavaScript doesn't actually have a concept of classes. In most other languages, you would instantiate an instance of a particular class, but that is not the case in JavaScript. In JavaScript, objects can create new objects, and objects can inherit from other objects.

Fundamentally, though, there still needs to be a way to create a new object, no matter what type of object scheme JavaScript uses. JavaScript makes it so that any function can also be instantiated as an object.

// Creation and usage of simple object
// A simple function which takes a name and saves it to the current context
function User( name ) {
  this.name = name;
}

// Create a new instance of that function, with the specified name
var me = new User( "Khai" );

// We can see that its name has been set as a property of itself
alert( me.name == "Khai" );

// And that it is an instance of the User object
alert( me.constructor == User );

// Now, since User() is a function, what happens when we treat it as such?
User( "Test" );

// Since its 'this' context was not set, it defaults to the global 'window'
// object.
alert( window.name == "Test" );

The constructor property exists on every object and will always point back to the function that created it. This way, you should be able to duplicate the object, or creating a new one on the same base class but not with the same properties.

// Example of using the constructor property to create new object
function User() {}

// Create a new User object
var me = new User();

// Create a new User object from the constructor of the first object
var you = new me.constructor();

// We can see that the constructors are the same
alert( me.constructor == you.constructor );

Public Methods:__

To understand object in JavaScript, you need to learn more about a property called 'prototype', which simply contains an object that will act as a base reference for all new copies of its parent object. Essentially, any property of the prototype will be available on every instance of that object. This creation / reference process gives us a cheap version of inheritance.

On object prototype is just an object, you can attach new properties to them, just like any other object. Attaching new properties to a prototype will make them a part of every object instantiated from the original prototype, effectively making all the properties public.

// Example of an object with methods attached via the prototype object
function User( name, age ) {
  this.name = name;
  this.age = age;
}

// Add new function to object prototype
User.prototype.getName = function() {
  return this.name;
};

// Instantiate a new user object
var user = new User( "Bob", 44 );

// We can see that the method we attached are with the same
// object, with proper contexts
alert( user.getName() == "Bob" );

Private Methods:__

Private methods and variables are only accessible to other private methods, private variables, and privileged methods. This is a way to define code that will only be accessible within the object itself, and not outside of it.

// Example of private method only usable by the constructor function
function Classroom( students, teacher ) {
  // A private method used for displaying all the students in the class
  function disp() {
    alert( this.names.join(", ") );
  }

  // Store the class data as public object properties
  this.students = students;
  this.teacher = teacher;

  // Call the private method to display the student names
  disp();
}

// Create a new Classroom object
var class = new Classroom( ["John", "Bob"], "Mr. Smith" );

// Fails, because disp is not a public property
class.disp();

While simple, private methods and variables are important for keeping your code free of collisions while allowing greater control over what your users are able to see and use.

Prototypal Inheritance:

Original: Prototypal Inheritance in JavaScript by Douglas Crockford

In a prototypal system, objects inherit from objects. JavaScript, however, lacks an operator that performs that operation. Instead it has a new operator, such that

new f()

produces a an object that inherits from
f.prototype

Fortunately, it is easy to create an operator that implements true prototypal inheritance:

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

The object function untangles JavaScript's constructor pattern, achieving true prototypal inheritance. It takes an old object as a parameter and returns an empty new object that inherits from the old one. If we attempt to obtain a member from the new object, and it lacks that key, then the old object will supply the member.

So instead of creating classes, you make prototype objects, and then use the object function to make new instances. Objects are mutable in JavaScript, so we can augment the new instances, giving them new fields and methods. These can then act as prototypes for even newer objects. We don't need classes to make lots of similar objects.

For convenience, we can create functions which will call the object function for us, and provide other customizations such as augmenting the new objects with privileged functions. I sometimes call these maker functions. If we have a maker function that calls another maker function instead of calling the object function, then we have a parasitic inheritance pattern.

Here is another version of the object() function:

if (! Object.create) {
    Object.create = function (o) {
        function F() {};
        F.prototype = o;
        return new F();
    };
}
newObject = Object.create(oldObject);
Function.prototype.extends = function(o) {
    function F() {};
    F.prototype = o;
    return new F();
};

var Person = function () {};
Person.extends(Animal);

__Public, private, privileged methods and variables:

Public:

function Constructor(...) {
    this.membername = value;
}
Constructor.prototype.membername = value;

Private:

function Constructor(...) {
    var that = this;
    var membername = value;
    function membername(...) {...}
}

Privileged:

function Constructor(...) {
    this.membername = function (...) {...};
}

JavaScript is fundamentally about objects. Arrays are objects. Functions are objects. Objects are objects. Objects are collections of name-value pairs. The names are strings, and the values are strings, numbers, booleans, and objects (including arrays and functions). Objects are usually implemented as hashtables so values can be retrieved quickly.

If a value is a function, we can consider it a method. When a method of an object is invoked, the this variable is set to the object. The method can then access the instance variables through the this variable.

Objects can be produced by constructors, which are functions which initialize objects. Constructors provide the features that classes provide in other languages, including static variables and methods.

The members of an object are all public members. Any function can access, modify, or delete those members, or add new members.

Inside the constructor, the this keyword is used to add members to the object.

Private members are made by the constructor. Ordinary vars and parameters of the constructor becomes the private members:

function Container(param) {
    this.member = param;
    var secret = 3;
    var that = this;
}

This constructor makes three private instance variables: param, secret, and that. They are attached to the object, but they are not accessible to the outside, nor are they accessible to the object's own public methods. They are accessible to private methods. Private methods are inner functions of the constructor.

By convention, we make a private 'that' member. This is used to make the object available to the private methods. This is a workaround for an error in the ECMAScript Language Specification which causes this to be set incorrectly for inner functions.

Private methods cannot be called by public methods. To make private methods useful, we need to introduce a privileged method.

A privileged method is a public method that is able to access the private variables and methods. It is possible to delete or replace a privileged method, but it is not possible to alter it, or to force it to give up its secrets.

Advanced:

When a javascript function is called, it enters an execution context. If another function is called (or the same function recursively) a new execution context is created, and execution enters that context for the duration of the function call.

When an execution context is created, a number of things happen in a defined order:

  1. An "Activation" object is created.
  2. The "arguments" object is created.
  3. Execution context is assigned a scope, which consists of a list (or chain) of objects. Each function object has an internal scope property that also consists of a list (or chain) of objects. The scope that is assigned to the execution context of a function call consists of the list referred to by the scope property of the corresponding function object with the Activation object added at the front of the chain (or top of the list).
  4. The process of "variable instantiation" takes place using the Variable object. The Activation object is used as the Variable object (they are the same). Named properties of the Variable object are created for each of the function's formal parameters, and if arguments to the function call correspond with those parameters, the values of those arguments are assigned to the properties (otherwise the assigned value is undefined). Inner function definitions are used to create function objects which are assigned to properties of the Variable object with names that correspond to the function name used in the function declaration. The last stage of variable instantiation is to create named properties of Variable object that correspond with all the local variables declared within the function. The properties created on the Variable object that correspond with declared local variables are initially assigned undefined values during variable instantiation. The actual initialization of local variables does not happen until the evaluation of the corresponding assignment expressions during the execution of the function body code.
  5. A value is assigned for use with the "this" keyword. If the value assigned refers to an object then property accessors prefixed with the "this" keyword reference properties of that object. If the value assigned (internally) is null, then the "this" keyword refer to the global object.

The fact that the Activation object, with its arguments property, and the Variable object, with named properties corresponding with function local variables, are the same object, allows the identifier arguments to be treated as if it was a function local variable.

The global execution context gets some slightly different handling as it does not have arguments so it does not need a defined Activation object to refer to them. The global execution context does need a scope and its scope chains consist of exactly one object, the global object. The global execution context does go through variable instantiation. Its inner functions are the normal top level function declarations that make up the bulk of javascript code. The global object is used as the Variable object, which is why globally declared functions (as well as globally declared variables) becomes properties of the global object. The global execution context also uses a reference to the global object for the "this" object.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License