Tuesday, January 01, 2013

JavaScript - The Good Parts - Part 2

Introduction

This is the second post based on the book JavaScript - The Good Parts by Douglas Crockford. In the first post we saw the basics of JavaScript with regards to variable, scope, type of variables etc. In that we saw that the type of a variable could be a Function or an Object. In this post we will see details of how Objects are defined and used in JavaScript

Functions in JavaScript

Before we get into Classes and Objects in JavaScripts it is important to understand some basics of functions in JavaScript
The power of functions in JavaScript is something that an Object Oriented Programmer never realizes. Functions are "First Class Citizens" of the language. They can be declared as variables, they can be passed across as parameters to other functions. These are features from a Functional Programming Paradigm and these give JavaScript a tremendous flexibility.
A function can be created using one of the following methods:
function foo () {
 //do something
}
or
foo = function () {
  //do something
}
or
var foo = function () {
  //do something
}
or
Function.prototype.foo = function () {
  //do something
}
Functions are by default created in the global scope and so can be invoked from anywhere.
Functions are treated as variables and can be passed around.
function foo () {
  alert('a');
}
function bar (x) {
  x(); //Invoking a function passed as a parameter.
  alert('b');
}
bar(foo);
This is a principle that has been adopted from "Functional Programming" paradigm.

Objects in JavaScript?

An object is JavaScript is a container of properties which can be attributes or functions. It is a dictionary where each property can be accessed by their name. A property name can be any string including a blank string.
Note that there is nothing specific called a class. JavaScript has only objects.
Before getting into further details of Objects, let us first see how inheritance is possible in JavaScript. This is importance as it is quite different from what one would have seen and experienced in Object Oriented Programming.

Inheritance in JavaScript

One has two types of Inheritance in Software
  1. Classical: Like the types present in C++ and Java. In this one explicitly extends another object and gets the attributes and methods of the parent object.
  2. Prototype: This is the mechanism used by JavaScript to provide inheritance.

Inheritance by Prototype

In JavaScript every object and every function has a prototype. There is a global Object Prototype and a Global Function Prototype. These are the prototypes that are associated to an Object or a Function as the case may be, by default.
One can attach attributes and functions to the prototypes of the object. Once an attribute or a function is attached to the prototype of an object it is available to all instances of that object. For example if one wishes to introduce a trim function to the String object in JavaScript, one can achieve this as follows
if(!String.prototype.trim) {
  String.prototype.trim = function () {
    return this.replace(/^\s+|\s+$/g,'');
  };
}
In line 1 we are checking if the function trim has already been defined in the prototype of the String object. If it has not been defined (note the usage of trim being "undefined" being treated as falsy) then define the trim function and assign it to the prototype of the String object.
The reason to check if the prototype already has a trim is to ensure that if in future JavaScript standards define the trim function in the String object prototype then one should use that implementation.
One can use the trim function on any string object.

function testTrim() {
    var a = "  abc ";
    var b = a.trim(); //b should have a value "abc".
}
When an attempt is made to access an attribute in an object or when an attempt is made to execute a function of an object, the JavaScript runtime first looks for the same in the object; if found it uses this; if it is not found then it looks for the same in the prototype of the object; if found it uses this from the prototype; if not it looks for a prototype of the prototype and if found it looks for this in this prototype; this goes on till there are no more prototypes available.
It is in this manner that hierarchy is implemented in JavaScript.
It is possible to attach an object "x" as a prototype to another object "y". By doing this the object inherits all the attributes and the functions and the prototype of the other object leading to inheritance. Note that inheritance in that JavaScript world becomes dynamic rather than the static inheritance that one sees in the Object Oriented world.
We can write a function "beget" as follows to attach one object as prototype of the other:
If (typeof Object.beget !== 'function')  {
  Object.beget = function (o) {
  var F = function() {};

F.prototype = o;
return new F();
};
} var another_stooge = Object.beget(stooge);
Note that we are attaching the function beget to the prototype of the "Global Object", thus making it available to all objects.
In the above example the object "stooge" becomes the prototype of object "another_stooge" thus bestowing on "another_stooge" all properties of object "stooge" and its prototype.

Object and Function prototypes

JavaScript has two global level prototypes, one is the prototype for all Objects and another is for all Functions. The prototype of the Function.prototype is the Object.prototype. Any attribute or function attached to this becomes available to all the Functions or all the Objects respectively.

Creating Objects

Objects in JavaScript can be created using one of the following methods:
  1. new
  2. Object Literal
  3. Object.create

Using new

First create function which has the required attributes and functions.
function Person() {
   this.name;
   this.age;
   this.isSeniorCitizen = function () {
      return this.age > 60;
   }
}
var johnDoe = new Person();
johnDoe.name = ‘John Doe’;
johnDoe.age = 50;
To use new to create an Object, first one needs to define a Function. Then one can instantiate an object which is represented by the Function. In the above example we have defined a function called Person. It can have two attributes "name" and "age". It also has a function "isSeniorCitizen".
To create an new instance of this object one can use the keyword new. This is the pattern that may Object Oriented Programmers will be familiar with. The other two mechanisms are something that is not encountered in the Object Oriented world.

Using Object Literals

One can create objects in JavaScript using simple Strings.
var johnDoe = {name: "John Doe", age: 50,
  isSeniorCitizen: function () {return this.age > 60;}};
A similar syntax can be used to create an array using object. This is shown in the following example:
var listOfLifeStates = ["infant", "kid", "teenager", "adult", "middle age", "senior citizen"];
Note that it is possible define functions along with the attributes of the object, using object literals.
If this reminds one of JSON then one is not mistaken. JSON is a subset of Object Literals.

Using Object.create

JavaScript 5 introductions a new method called ‘create’ in the Object prototype. This allows instantiating objects which can inherit from other objects.
function Foo() {
   this.name = "foo";
}
Foo.prototype.sayHello = function() {
  alert("hello from " + this.name);
};
var myFoo = new Foo();
myFoo.sayHello();
If one wishes to create another object Bar which inherits from Foo we can do the following:
function Bar() {
}
Bar.prototype = new Foo();
Bar.prototype.sayGoodbye = function() {
  alert("goodbye from " + this.name);
}
var myBar = new Bar();
myBar.sayHello();
myBar.sayGoodbye();
But the issue is that the property "prototype" is called also called "__proto__" in some environments. To avoid this confusion a new Object.create method has been introduced. This can be used as follows.
var Foo = {
  name: "foo",
sayHello: function() { alert("hello from " + this.name); } }; Foo.sayHello();
Now use Object.create to create bar with a prototype of Foo.
var bar = Object.create(Foo, { sayGoodbye:  function() {alert("goodbye from " + this.name); }});
bar.sayHello();
bar.sayGoodbye();
This is much cleaner than using the prototype directly.
But some environments still do not have the implementation of Object.create. To overcome this problem one can define one’s own create function in the Global Object prototype as below:
if(typeof Object.create !== "function") {
   Object.create = function (o, props) {
      function F() {}
      F.prototype = o;
      extend(F, prop);
      return new F();
      function extend(obj, props) {
         for(prop in props) {
            if(props.hasOwnProperty(prop)) {
               obj[prop] = props[prop];
            }
         }
      }
   };
}
Now one can use Object.create fearlessly irrespective of the environment in which one's scripts run.

Adding functions to prototypes

The normal way to add a function to a prototype is as
Object.prototype. = function () {
}
To make this simpler we can add a method called "method" to the global function prototype as follows:
Function.prototype.method = function (name, func) {
   this.prototype[name] = func;
   return this;
}
Now one need not be worried about prototype. To add a method to a function all one needs to do is as described in the following example.
To add a method to the Number object to convert numbers to integer we can do the following:
Number.method ('integer', function () {
  return Math[this < 0 ? 'ceiling': 'floor'](this);
});
Note the ability to select the function to execute using the ternary operator
To add a trim method to all Strings
String.method('trim', function() {
   return this.replace(/^s+|s+$g, '');
}
To avoid clashes with other libraries we should set the function if and only if it does not exist. So change the 'method' function as follows:
Function.prototype.method = function (name, func) {
   if (!this.prototype[name]) {
this.prototype[name] = func; } return this; }
This will ensure that the method will be created in the object only if one does not already exist. Note the use of [] to access the attribute/function in an object. Not only can one use the dot notation to access the function or the attribute but one can also use [] to access the function or attribute.

Attributes in an Object and hasOwnProperty

To go through the attributes in a JavaScript object the following loop can be used.
for (myvar in object) {
   if (obj.hasOwnProperty(myvar) {
      alert("Value of attribute " + myvar + " in object is " + obj[myvar]);
   }
}
The method hasOwnProperty indicates if the attribute or function belongs to the object or if it is has been inherited through the prototype. The above method will display only those attributes which are attached to the object directly. It will not display the methods it has inheried through its prototype. If one removes the hasOwnProperty function then one will get a list of all the attributes and functions included in the prototype hierarchy of the object.

Delete operator

JavaScript has a delete method using which one can delete methods and attributes in a JavaScript object.
Person = function (pName, pAge, pOccupation) {
   this.name = pName;

this.age = pAge; this.occupation = pOccupation; } var johnDoe = new Person("John Doe", 35, "vagabond"); //This will delete the attribute occupation from object johnDoe. delete johnDoe.occupation;
Note that the function delete will only delete attributes and functions directly associated with the object and not the ones in the prototype hierarchy.

Instanceof in JavaScript

The method "instanceOf" can be used to check the type of object. This walks up the prototype and checks with all the prototypes in the hierarchy. If one wishes to restrict avoid the prototype hierarchy then we should check object.constructor === "<object type>". Care needs to be taken to ensure that the object is not null if one wishes to check with constructor.
instanceOf does not work with objects instantiated across windows. E.g. between the parent window and the IFrame or parent window and window opened.
Note that Array is a special object that has certain specific properties like length. But an Array is not really an Array (as in other languages), but it is an object where the names of the properties are numbers. These numbers cannot be negative and the length of the Array is equal to the highest number + 1 and not equal the number of elements in the Array.

No comments: