Information Hiding in JavaScript Objects

Peter Coxhead

Contents

 1 Creating hidden 'fields'
 2 Reuse
 3 The Constructor-based Approach
 4 Prototype Inheritance
  References/Bibliography

1 Creating hidden 'fields'

Suppose we want to represent points in JavaScript (perhaps as part of a system which can produce 'drawings' in a web page via CSS).

The method for creating 'point' objects discussed in "Introduction to JavaScript for Java Programmers", §13 results in code like that shown below.

function makePoint(x,y)
{ p = new Object();
  p.x = x;
  p.y = y;
  p.d = Math.sqrt(x*x + y*y);
  p.toString = function () { return '('+x+' , '+y+')'; }
  return p;
}
var p1 = makePoint(3,4);
var p2 = makePoint(10,30);

To avoid having copies of toString in every point object, an external function can be used:

function _ptToString()
{ return return '('+this.x+' , '+this.y+')';
}
function makePoint(x,y)
{ p = new Object();
  p.x = x;
  p.y = y;
  p.toString = _ptToString;
  return p;
}
var p1 = makePoint(3,4);
var p2 = makePoint(10,30);

The problem with this approach is that the fields x, y and d are always accessible, whereas if p1 and p2 above represent particular fixed points, their x and y coordinates should not be changeable. Further, it's possible to change fields inconsistently, e.g. by not updating d when changing x or y. These are of course the standard arguments for making an object's fields only available via accessor functions.

One solution is never to store data in fields of JavaScript objects created in this way. Instead use variables which are local to the 'make' function (including its parameters):

function makePoint(x,y)
{ var d = Math.sqrt(x*x + y*y);
  p = new Object();
  p.getX = function () { return x; };
  p.getY = function () { return y; };
  p.getD = function () { return d; };
  p.toString = function () { return '('+x+' , '+y+')'; }
  return p;
}
var p1 = makePoint(3,4);
// p1.x, p1.y and p1.d are no longer accessible.
var p2 = makePoint(10,30);
alert('p1 = '+p1.toString()+'; distance from origin = '+p1.getD());

This approach seems odd to programmers used to languages like Java in which local variables last only for the duration of the execution of a subprogram. However, JavaScript ensures that 'exported' local variables are persistent via 'closures' (see
http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Functions).

A minor disadvantage of this approach is that it's not possible to use externally defined functions to avoid repeating them in every created object, since there is no way of accessing data values as they are no longer fields of the invoking object this.

2 Reuse

Adopting the principle that composition is normally to be preferred to inheritance,[1] the code for creating a point object can be re-used to make a colour point object by including a point object inside every colour point object:

function makePoint(x,y)
{ var d = Math.sqrt(x*x + y*y);
  var p = new Object();
  p.getX = function () { return x; };
  p.getY = function () { return y; };
  p.getD = function () { return d; };
  p.toString = function () { return '('+x+', '+y+')'; }
  return p;
}
function makeColourPoint(x,y,colour)
{ var cp = new Object();
  var p = makePoint(x,y);
  cp.getX = p.getX;
  cp.getY = p.getY;
  cp.getD = p.getD;
  cp.getColour = function () { return colour; };
  cp.setColour = function (newColour) { colour = newColour; };
  cp.toString = function () { return '['+p.toString()+', '+colour+']' };
  return cp;
}
cp1 = makeColourPoint(10,30,'red');
alert('cp1 = '+cp1); // Automatically calls the toString() method;
                     // displays 'cp1 = [(10, 30), red]'.
cp1.setColour('blue');
alert('cp1 = '+cp1); // Displays 'cp1 = [(10, 30), blue]'.

This approach allows the methods of the 'child class' to use the methods of the 'parent class' in their definition: note how the function toString in makeColourPoint uses the toString from makePoint. Actually, this is rarely necessary. When it isn't, a much simpler approach is to create the 'child' as a 'parent' object and then add extra fields or methods:

function makePoint(x,y)
{ var d = Math.sqrt(x*x + y*y);
  var p = new Object();
  p.getX = function () { return x; };
  p.getY = function () { return y; };
  p.getD = function () { return d; };
  p.toString = function () { return '('+x+', '+y+')'; }
  return p;
}
function makeColourPoint(x,y,colour)
{ var cp = makePoint(x,y);
  cp.getColour = function () { return colour; };
  cp.setColour = function (newColour) { colour = newColour; };
  cp.toString = function () { return '('+x+', '+y+', '+colour+')' };
  return cp;
}
var p1 = makePoint(10,33);
alert('p1 = '+p1); // automatically calls the toString() method
var cp1 = makeColourPoint(10,30,'red');
alert('cp1 = '+cp1+'; d of cp1 = '+cp1.getD());
cp1.setColour('blue');

3 The Constructor-based Approach

JavaScript does have 'constructors' which look superficially like Java constructors. However, they effectively combine the functionality of both a Java class and a constructor. This means that in JavaScript it's not possible to have multiple constructors taking different numbers of arguments.

The example above can be written using constructors as follows.

function Point(x,y)
{ var d = Math.sqrt(x*x + y*y);
  this.getX = function () { return x; };
  this.getY = function () { return y; };
  this.getD = function () { return d; };
  this.toString = function () { return '('+x+' , '+y+')'; }
}
function ColourPoint(x,y,colour)
{ Point.call(this,x,y);  // See below
  this.getColour = function () { return colour; };
  this.setColour = function (newColour) { colour = newColour; };
  this.toString = function () { return '('+x+' , '+y+', '+colour+')' };
}

Allowing for the different way of defining methods in JavaScript, most of the above code should be clear to a Java programmer, remembering that a function like Point is effectively defines both a class and a constructor.

In Java, ColourPoint would inherit from Point via an extends statement. Then within ColourPoint, its superclass Point's constructor would be called via super(). In JavaScript we need to call Point's constructor a different way. The special syntax fun.call(obj, ...) calls the function fun as if it were called as obj.fun(...) even if obj does not have this method:

var o1 = new Object();
o1.val = 3;
o1.sq = function (x) { return this.val*this.val; };
alert( o1.sq() ); // Displays 9.
var o2 = new Object();
o2.val = 4;
alert( o1.sq.call(o2) ); // Displays 16.

In the last statement, the function o1.sq is called as though o2.sq, so that within it this.val is 4 not 3.

In ColourPoint above, we call Point(x,y) as if called as this.Point(x,y), which is what we need to set up Point's methods within ColourPoint.

Once the constructors are defined in this way, then can be used to create objects:

var p1 = new Point(10,33);
alert('p1 = '+p1); // automatically calls the toString() method
var cp1 = new ColourPoint(10,30,'red');
alert('cp1 = '+cp1+'; d of cp1 = '+cp1.getD());
cp1.setColour('blue');

I can only repeat that I can see no advantage of this approach over the 'make function' approach. The similarity of syntax to Java is superficially appealing, but conceals the fact that the underlying language is very different. It's still possible to dynamically alter objects. For example:

var cp1 = new ColourPoint(3,4,'red');
cp1.size = 12;
cp1.getD = function () { return this.getX() + this.getY(); };

The object cp1 now has a new field, size, and its getD function is different:

alert( cp1.getD() ); // Displays 7 instead of 5.

Needless to say, adding extra fields and changing methods in this way is generally highly undesirable, making code difficult to understand and debug.

4 Prototype Inheritance

JavaScript has a form of inheritance via the prototype field of functions. There's a reasonably clear explanation of how this works at
http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Guide:Class-Based_vs._Prototype-Based_Languages.

The power of prototypes is that they allow dynamic changes to existing 'classes' of object. With the function Point defined as above, create p1:

var p1 = new Point(100,30);

Suppose we want an extra method toSimpleString to be added to all Point objects. This can be done via the special prototype 'field' which all functions have:

Point.prototype.toSimpleString = function ()
  { return this.getX()+','+this.getY();
  };

After this statement has been executed, all objects created by new Point(), even if created earlier like p1, will have a toSimpleString method:

alert( p1.toSimpleString() );  // Displays '100,30'.

(To allow ColourPoint objects to inherit this dynamically added method, we need to add the statement:

ColourPoint.prototype = new Point();

immediately after the definition of ColourPoint, i.e. before any ColourPoint objects are created.)

I can see absolutely no point in dynamically changing all members of a 'class' of objects that you have already defined. Why add extra fields and methods outside the functions which construct such objects? It's simply confusing.[2]

However, the prototype field can be of value in extending objects obtained from a source which you cannot access and hence cannot change. For example, an extra method can be added to all strings by adding to the String.prototype. You might like the function substr to be called mid (as in Excel), to avoid confusion with the function substring:

String.prototype.mid = function (start,len)
  { return this.substr(start,len);
  };
alert( 'Hello'.mid(1,3) ); // Displays 'ell'.

Needless to say this is a very dangerous feature of JavaScript, since it can be used to alter built-in methods. For example:

alert( 'hello'.substr(1,3) );  // Dsplays 'ell'.
. . . . . . .
String.prototype.substr = function (i,j) { return this+i+j; };
. . . . . . .
alert( 'hello'.substr(1,3) );  // Now displays 'hello13'.

References/Bibliography

See the other handouts for this module, which are available online.



Footnotes

[1] See Design Principles: Reusability.

[2] One possible valid use might be in 'self-modifying' programs, e.g. in a machine learning application.

GoHome Page for "Information and the Web"