Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Extending JavaScript Intrinsic Objects with Prototypes

4.90/5 (13 votes)
17 Oct 2006CPOL18 min read 1   297  
Distillate your own JavaScript flavor with prototypes.

Introduction

Just so you know, you can use the prototype property of some JavaScript intrinsic objects to extend their functionality. New instances of the specific object class inherit the behavior assigned to the class. If you know C++, think of extended objects as derived classes with new proprietary member functions.

Intrinsic JavaScript objects exposing the prototype property are the Array, Boolean, Date, Function, Number, Object, RegExp and String objects. Notice that the Global and Math objects are excluded.

So lets put this to good use. Keep in mind that extending an intrinsic object should generally lead to code that is easier to read and write, ultimately saving you some typing (and revising) time; of course, an extension function should also perform some operation involving the intrinsic object that results in something meaningful. It should also be fast and efficient, but then again, every piece of code should be fast and efficient.

Prototypes

The numeric objects are the most interesting to deal with. There's a lot of things you can do with strings too, but generally most string operations submit to concatenations or substring extractions, which are already provided for by the String object intrinsically. Yet, there are some things that might just be what you need.

Trimming a string literal is one of the most typical issues in string manipulation, specially when dealing with user input; there's no intrinsic method of the String object that can turn ' hello world. ' into 'hello world.'. You cannot simply replace every blank, or you'll end up with 'helloworld'. So here's one way to do it:

JavaScript
// trimming with array ops
String.prototype.trim = function() { return this.split(/\s/).join(' '); }

As if magic, this gets rid of leading, trailing and middle spaces, and the resulting string (returned) separates words with one and only one space. The work is done by split, an intrinsic string function that turns the string into an array, using any 'white space' occurrence as the split delimiter. Joining the array back into a string object, using a (literal) blank space as the 'joint' character cleans any leading or trailing white space, as well as middle white space, the one between words. Incidentally, \s is a regular expression that includes tabs, line feeds, vertical tabs or white space in general, so it will work for entire paragraphs.

But before going on, lets look at the prototyping syntax: we extend intrinsic objects through the prototype property, with a unique string literal (preferably a meaningful name), to which we assign an anonymous function; the function may have zero or any number of arguments, and the function body follows the prototype declaration. Except in very rare cases with nested object scopes, the this keyword refers to the instance of the object class invoking the function, i.e., to the actual string. The function doesn't need to be anonymous, but giving it a name m ay be somewhat redundant or lead to confusion:

JavaScript
// redundant prototype function definition
String.prototype.trim = 
  function trim() { return this.split(/\s/).join(' '); };
...
// confusing prototype function definition
String.prototype.trim = 
 function trimblanks() { return this.split(/\s/).join(' '); };

Still, naming the function has a use, if you ever need to retrieve an object's method (or a function) as a string; you can do this with the eval function:

JavaScript
var s = eval(String.trim);
// s == 'function() { return this.split(/\s/).join(' '); }'

If the prototype declaration names the function like shown before:

JavaScript
var s = eval(String.trim);
// s == 'function trim() { return this.split(/\s/).join(' '); }'

Again, the prototype name is required by the syntax, but the function name is optional. In case you're wondering, this is what you'll get if you want to decrypt an intrinsic function:

JavaScript
var s = eval(String.substr);
// s == 'function substr() { [native code] }'

So much for reverse-engineering the JavaScript engine.

Of course there are other ways to implement the trim function; programmers above all, know that there's usually more than one solution to a specific problem. OK, maybe mathematicians above all, with engineers right behind. Consider splitting the string object using the string replace method and a more elaborate regular expression:

JavaScript
// trimming with replace
String.prototype.trim2 = 
  function() { return this.replace(/^\s*(\S*(\s+\S+)*)\s*$/, '$1'); }

It's not pretty, specially if you don't know about regular expression syntax. We're replacing any leading spaces (\s, with * being the zero or more wildcard), those after the beginning of the line (^), and any trailing spaces, those before the end of the line ($). Between the parenthesis of the regular expression is everything that begins with a non-space character (\S*), but is allowed to have spaces in between (the second parenthesis). The result ($1) is exactly the contents of the string that evaluates to the outer parenthesis, and the replacement is returned by the function prototype. Notice that word spacing is preserved with this method.

trim2 was actually my first approach to the trimming problem; it has no type conversions, and it is just a wrapper for an intrinsic method. But when put to the test with a 100,000 character string (a somewhat big plain-text file), the replace method turned out to be 160 times slower in average than the split-join operation! for larger strings, replace simply locks up. trim2 may still be of use if you want to preserve word spacing, but you will have to check for length and code some logic to break it down into replace-chewable pieces, turning it uglier than it already is. In any case, you'll rarely need to keep word spacing, since displaying justified text can be easily achieved with an HTML attribute, so trim wins as the preferred method. Ultimately, trim is much more elegant and digestible than trim2.

OK, so once we're done trimming, here's a VB-like string replicator:

JavaScript
// VB-like string replicator 
String.prototype.times = function(n)
{
 var s = '';
 for (var i = 0; i < n; i++)
  s += this;

 return s;
}
... 
// using the prototype
var r = 'hey', q; 
r = r.times(5);  // r == 'heyheyheyheyhey'
q = '0'.times(4) // q == '0000'

The operation is pretty obvious, but for what purpose would anyone replicate a string n times? Well, there aren't many uses for it, but VB, VBA, and VBScript sponsor the String() function, and JavaScript doesn't. Apart from that, I do have a good use for it, when formatting numbers, for zero-padding; but before we get to that I just want to highlight a few pointers.

  • The function constructs a new return value by concatenating n copies of this; it is good practice to leave the input arguments untouched (e.g., never assigning to this); you can always use a reassignment like in the sample;
  • The function requires two variables, s, the return value and i the loop counter; the code initializes s to an empty string (it will fail unless it does);
  • JavaScript is loosely typed, so the var keyword is totally optional;
  • The function body can be as complex as required by the operation, with as many local variables and with full access to the engine and the other intrinsic objects;
  • The function operates on both variables of the type and on constants (literals) of the type, as shown in the last line; this is an exclusive JavaScript treat, when compared to most scripting and programming languages.

I could've named the function 'string' just like in VB, but it may lead to confusion, being the string method of the String object; '@ times 5' is pretty self-explanatory when read by anyone else. So that brings us to the zero-padding function:

JavaScript
// Zero-Padding
String.prototype.zp = 
  function(n) { return '0'.times(n - this.length) + this; }
...
// zp usage
var a = '5'.zp(5); // a == '00005'

Maybe you can come up with a better name for the function, but I'm fine with just zp. The fun part comes when we extend the prototype to the Number object:

JavaScript
// string functions that we want to apply directly to numbers...
Number.prototype.zp = function(n) { return this.toString().zp(n); }
...
// ... so that we can use it on numbers!
var b = (5 * 3).zp(5);  // b == '00015'
var c = (b * 10).zp(2); // c == '150'

And there you have it. We declared a Number prototype function that wraps a String prototype function based on another String prototype function; we are also turning a number into a string, for the purpose of format, so at this point you can tell there are no return value restrictions, no argument type checking (not even argument count enforcement) and basically no limits to what you can do! Incidentally, even though b in the sample is a string, multiplying it by 10 turns it into a number, and since the string length of 150 is larger than 2, the String times function returns an empty string, which is just perfect for our purposes.

So what about a zero-trailing function? Well it's just as easy as inverting the concatenation arguments:

JavaScript
// Zero-Trailing
String.prototype.zt = 
  function(n) { return this + '0'.times(n - this.length); } 
... 
// but be carefull about the results!
var b = (5 * 3).zt(5); // b == '15000'

Of course that's not the point of a zero-trail; we could've simply multiplied by a thousand to get the same result. Trailing zeroes are most useful when formatting real (floating-point) numbers. The C printf family of functions and maybe Microsoft Excel are probably the best at it, but I've yet to see a native scripting language implementation of such a function. Incidentally, the VB Format function is not even close to printf, but to be fair, printf is an intricate piece of code, and it handles a variable number of arguments with a single format string.

You can Google around for 'JavaScript printf' to get a myriad of implementations; I also did my own once, which replicated the character state machine found in the C printf function; ok, I simply translated it to JavaScript.

Then I used my head. Formatted tabular output is only useful if the actual output is a plain text file. That was great when displays were character based, but we're not in Kansas anymore. There might still be some uses for it, if you still need to go through a text file for debugging or analysis; that's usually the case for me when tweaking Direct3D .x files, or other plain-text 3D files. But in general, if the formatted output is tabular numeric data, you're better off with comma-separated values (.csv) files or tab delimited files: Excel will import them and format them. In particular, JavaScript output is probably going to be displayed in some web page, therefore we can use HTML to help with the formatting, at least with indenting issues.

So to take the air out of the tires of this, we won't place our aim into a full-fledged JavaScript printf; unless you really need it, the following approach will handle your floating-point number formatting needs somewhat gracefully. Let's start with truncation to a specified number of decimal digits:

JavaScript
// decimal digits truncation
Number.prototype.truncate = function(n)
{
return Math.round(this * Math.pow(10, n)) / Math.pow(10, n);
} 
... 
var a = 78.53981633974483;
var b = a.truncate(4);       // b = 78.5398
var c = (5 / 2).truncate(4); // c = 2.5
var d = (199).truncate(4);   // d = 199

It's a simple shift of the decimal digits, back and forth, with intrinsic truncation via Math rounding. It works just fine, except that for our formatting purposes, we are missing the trailing zeroes for 2.5 or 199 in c and d above.

There's a couple of Global methods to parse objects into integers and floats, but there's no method to extract the fractional part of a real number; that sounds like a good Number prototype candidate:

JavaScript
// fractional part of a number
Number.prototype.fractional = 
  function() { return parseFloat(this) - parseInt(this); } 
... 
var f = a.fractional(); // f == 0.53981633974483

So now we're ready to prototype our number formatting function:

JavaScript
// format a number with n decimal digits
Number.prototype.format = function(n)
{
 // round the fractional part to n digits, skip the '0.' and zero trail
 var f = this.fractional().truncate(n).toString().substr(2).zt(n);

 // integer part + dot + fractional part, skipping the '0.'
 return parseInt(this) + '.' + f;
}

The fortunate circumstances that make this work is that the '+' operator will do the proper type conversions, so we can 'add' (concatenate) an integer and a string. The substr function does require a String object, therefore the conversion is required, but then there's nothing preventing us from defining substr as a Number prototype and calling the String version, or is there?

JavaScript
// substr for numbers!
Number.prototype.substr = 
  function(n) { return this.toString().substr(n); }

OK, you may say that's overdoing it, but then again it might save you from typing .toString() here and there!

As stated before, HTML tags can handle indenting; just align table cells to the right to make your tabular output more readable. Still, there are other format elements that our prototype lacks. Consider a currency format, with thousand separators, like in the VBScript FormatNumber and FormatCurrency functions.

In this case, we need to insert the digit group separator into the integer part, every three digits. It's a string operation instead of a number operation. We will use a comma (,) as the thousands separator, since JavaScript always expects the dot (.) to be the decimal separator. There are ways around it for other locales than US English; most implementations I've seen involve mixing JavaScript and VBScript, but then VBScript only works under the IE browser. In any case, exchanging separators should be done just before displaying the output, and not in between arithmetic operations.

Also keep in mind that the decimal separator is essential to a number, but the thousands separator only improves the readability of a number, i.e., it is merely decorative. I mention this to stress the importance of formatting after operating on, or with numbers. Eventually, we may need a string prototype that can get rid of number formatting (namely thousand separators) to get an actual Number object we can use in our calculations.

As to why the rest of the world uses the comma as the decimal separator, the International Standards Organization argues that it is harder to mistype a comma in handwriting, and that a dot can be a smudge in a photocopy, noise in a fax transmission or (can you believe it?) a fly in a banner! But we're done with general ed, lets get to the code stuff.

We'll start with a thousand separator function that discards the fractional part. The easiest way to insert the separators is to traverse the string representation of the number from right to left, i.e., in reverse. Strangely enough, there is no intrinsic string reversal method in JavaScript, so we'll have to prototype our own:

JavaScript
// String reverse
String.prototype.reverse = 
  function() { return this.split('').reverse().join(''); }

It's pretty straightforward: split the string with a zero-length separator, which luckily for us yields an array object with one character per element. The Array object does have an intrinsic reverse method, so we just glue its output back together with a zero-length separator again, turning 'Hello' into 'olleH'. Slick.

Once we have the reverse of the string, all that's left to do is to insert the thousands separators:

JavaScript
// integer thousand separators
Number.prototype.group = function()
{
 var s = parseInt(this).toString().reverse(), r = ''; 
 for (var i = 0; i < s.length; i++)
  r += (i > 0 && i % 3 == 0 ? ',' : '') + s.charAt(i);
 
 return r.reverse();
}

We start with the string representation of the integer part and reverse it; then we traverse it and accumulate digits in the return variable r at a time, and an optional ',' prefix, every three occurrences. Check the modulo (remainder) % operator documentation (and your math expertise) if you don't know what is going on. Reversing the result once more finishes up the job.

So our old format function can evolve into the new and improved format2 function:

JavaScript
// format a number with n decimal digits and thousands separator
Number.prototype.format2 = function(n)
{
 // truncate and zero-trail the fractional part
 var f = this.fractional().truncate(n).substr(2).zt(n);

 // grouped integer part + dot + fractional part
 return this.group() + '.' + f;
}

From here on is up to your imagination; you could use a second argument to turn grouping on or off, you could extend the group function to cover for real numbers, you could make the decimal part optional, etc. I think I've given you a good base to start developing your own number formatters. I've seen from recursive regular expression formatters to currency symbol handlers, not to mention number to text converters, like the ones used for automatic check writing. Remember, these code bits are just one way to do it!

As mentioned before, here's the function that gets rid of formatted input and returns an actual floating-point number. It is of course a String method; formatted input is more likely to be a string:

JavaScript
// clear format from a string representation of a number
String.prototype.clean = 
  function() { return parseFloat(this.replace(/,/g, '')); }

... 
var a = 7853981.633974483;
var b = a.format(4); // b == '7,853,981.6340'
var c = b.clean();   // c == 7853981.634

Notice that the returned number has no trailing zeroes, for they are meaningless, in numeric terms. The sample may seem somewhat lame; we could use a in any calculation in the first place, but consider that the input (b) comes straight from a formatted text file, or from an input box: unless you clean it up, parseFloat(b) is 7, instead of 7 million something; try blaming an investor for such a typo! Also, we're just looking for commas, and nothing else; the correct regular expression should cover for non-digit characters, the decimal separator (the dot) and the minus sign:

JavaScript
String.prototype.clean = 
  function() { return parseFloat(this.replace(/[^0-9|.|-]/g, '')); }

Incidentally, it also gets rid of currency symbols, if any. Notice that an input string with mixed text and numbers may yield unexpected results; in other words, the function expects something resembling a formatted number, not a text line or a paragraph.

But oops, there's still a fatal flaw in our functions, it fails miserably for negative numbers. The code must remember the input sign, and what do you know, that's another good prototype candidate:

JavaScript
// number sign 'bit' (boolean)
Number.prototype.sign = function() { return this < 0; }

The function clearly answers 'is n negative?', so it works just like the sign bit. Duh. That's not the point; the point is that we can use it as an index to a string, to help solve the problem:

JavaScript
// format a number with n decimal digits thousands sep and sign
Number.prototype.format3 = function(n)
{
 // remember the input sign and cancel it
 var a = Math.abs(this); 
 
 // truncate and zero-trail the fractional part
 var f = a.fractional().truncate(n).substr(2).zt(n); 
 
 // sign + grouped integer part + dot + fractional part
 return '+-'.substr(this.sign(), 1) + a.group() + '.' + f;
}

The sign bit selects + or -, and the rest is the same, once we get rid of the sign. If you don't want the + sign, you can substitute it with a blank space and optionally trim the return value.

In comparison, VBScript's FormatCurrency and FormatNumber can use parenthesis for negative numbers, but they cannot force the plus '+' sign. Once again, parenthesis are just decorators, and particularly old-fashioned ones for that matter, since colors are much better to differentiate positives from negatives.

And so much for number formatting. These prototypes fully serve my display needs, and you can build on them towards your award-winning JavaScript printf code. They might not be awe inspiring, but they get the job done, they involve very simple manipulations in a very simple but nonetheless very powerful language, and they extend the functionality of intrinsic objects letting me create my own JavaScript dialect, which in itself, is more than anyone can say about any of the competing scripting languages.

Date Object Prototypes

Let's turn our attention towards the Date object. It has intrinsic methods for each date part, just like VBScript's DatePart function, but it lacks the DateDiff and DateAdd counterparts, so here they are:

JavaScript
// date diff in days
Date.prototype.dateDiff = function(d)
{ return Math.round((d.valueOf() - this.valueOf()) / 86400000); }

// date adds
Date.prototype.add = function(n)
{
 var d = new Date(this);
 d.setDate(d.getDate() + n);
 return d;
}
Date.prototype.addMonth = function(n)
{
 var d = new Date(this);
 d.setMonth(d.getMonth() + n);
 return d;
}

Date.prototype.addYear = function(n)
{
 var d = new Date(this);
 d.setFullYear(d.getFullYear() + n);
 return d;
}
... 
var today = new Date();
var tomorrow = today.add(1);
var d = today.dateDiff(tomorrow); // d == 1

dateDiffuses the valueOf intrinsic method to turn dates into milliseconds elapsed from a common fixed base and divide by the number of milliseconds in a day, then rounds it up to the closest integer for a result in days. Positive results indicate the argument is ahead in time; this helps the calling syntax a little, but just a little; calendar operations like 'how many days are left until Easter?' can become quite a nuisance if you subtract the arguments in the wrong order.

Date additions construct a new return date and alter it with Date object 'set' intrinsic methods. Notice that in Visual Basic you can add dates and numbers, and numbers are interpreted as days. In JavaScript though, you're probably adding milliseconds, so adding whole days, months or years takes adding the equivalent number of milliseconds for each case. Yet, that doesn't cover for the fact that different months have different number of days, so we're better off simply shifting the date part with the desired amount.

The previous Date prototypes helped me build a couple of others that find out the first and last date of the month:

JavaScript
// first and last date of month
Date.prototype.getFirstDate =
  function() { var d = new Date(this); 
               d.setDate(1); return d; }
Date.prototype.getLastDate =
  function() { var d = this.addMonth(1); 
               d.setDate(1); return d.add(-1); }

I know you'll find them invaluable, specially if you ever have to build or program web calendars. The first date is pretty easy, just set the day index part to 1. The last date is more elaborate, shifting to the first of the next month and subtracting a day. You may also find this in-range checker useful:

JavaScript
// date between [d1, d2]
Date.prototype.between =
function(d1, d2) { return 0 <= d1.dateDiff(this) && 
                   d2.dateDiff(this) <= 0; } 
... 
var b = today.between(today.getFirstDate(), 
        today.getLastDate()); // b == true

Of course, there's no argument checking, so be careful to pass dates, and make sure that d1 < d2.

In the same calendar-programming mode, I was once confronted with a holiday highlighter. Sundays and fixed-date holidays turned out to be pretty easy, but moveable, Easter-based holidays were not so. To start with, Easter is itself a moveable date, so I needed a (Gregorian calendar) Easter calculator:

JavaScript
// easter calculator
Date.prototype.getEaster = function(y)
{
 if (!y)
  y = this.getFullYear();
 
 var c, n, k, i, j, l, m, d; 
    c = parseInt(y / 100);
    n = y - 19 * parseInt(y / 19);
    k = parseInt((c - 17) / 25);
    i = c - parseInt(c / 4) - parseInt((c - k) / 3) + 19 * n + 15;
    i = i - 30 * parseInt(i / 30);
    i = i - parseInt(i / 28) *
        (1 - parseInt(i / 28) * parseInt(29 / (i + 1)) *
        parseInt((21 - n) / 11));
    j = y + parseInt(y / 4) + i + 2 - c + parseInt(c / 4);
    j = j - 7 * parseInt(j / 7);
    l = i - j;
    m = 3 + parseInt((l + 40) / 44);
    d = l + 28 - 31 * parseInt(m / 4); 
    return new Date(y, m - 1, d);
}

The input is a full four digit year, or the date instance's year, if no argument is provided. You can check the math if you want to, attributed to J. M. Oudin (1940); I found it in the US navy site. The rule is that Easter is the first Sunday after the first ecclesiastical full moon that occurs on or after March 21. Sounds somewhat retro, but hey, that's tradition for you.

Once I've got an Easter date, the moveable holidays are relative to it, so I just need to check date coincidences:

JavaScript
// locale holiday test (Venezuelan)
Date.prototype.isHoliday = function()
{
    // sundays
    if (this.getDay() == 0)
     return true;
        
    var y = this.getFullYear();
    var m = this.getMonth();
    var d = this.getDate();
    var r;
    
    // fixed holidays
    switch(m)
    {
    case 0: case 4: r = d ==  1;   break;
    case 3:   r = d == 19;   break;
    case 5:   r = d == 24;   break;
    case 6:   r = d == 24 || d == 5; break;
    case 9:   r = d == 12;   break;
    case 11:  r = d == 25 || d == 31; break;
    default:
    }
    
    // moveable easter-based holidays
 if (!r)
 { 
  // integer date diff decision 
     switch (this.dateDiff(this.getEaster(y)))
     {
     case 2: case 3: case 48: case 47: r = true;
     default:
     }
    }
    
    return r;
}

So you can easily modify the locale holidays by changing months and dates for fixed holidays, and shift values for Easter-based holidays. The example covers Venezuelan holidays.

And so much for calendars. To finish the date prototypes, I'll invite you to check my date format prototype article, published a while ago; it uses a format string to display the date in the locale of your choice. You will also find it in the date.js file included in this article.

Math Object Prototypes

To close the topic, I'll show you how to prototype some Math object functions.

The Math object lacks a set of trigonometric functions that might make your trigonometry nightmares somewhat lighter. Still, as mentioned at the very beginning of the article, the Math object has no prototype property; this is because of its actual definition: the Math object is a LITERAL, meaning it has no constructor, so it is initialized by the JavaScript engine, rather than constructed and then instantiated. As a consequence, it has no prototype property. Fortunately for us, we can still extend it by simply skipping the prototype keyword! (thanks to schephais for pointing that out at the web developer archives). Here's how:

JavaScript
// math cosecant, secant and cotangent
Math.csc = function(x) { return 1 / Math.sin(x); }
Math.sec = function(x) { return 1 / Math.cos(x); }
Math.cot = function(x) { return 1 / Math.tan(x); } 
... 
// using the new trigonometric predicates
var a = 45 * Math.PI / 180;
var b = Math.csc(a); // b == 1.4142135623730951

I'll leave you to define the 'arc' counterparts. We just extended the intrinsic Math object with some fairly common trigonometric functions; the kicker is that they just saved us from ever having to type '1 / Math.aaa(x)' again, and made the code a little easier to read, given you are a trigonometry buff.

For the sample functions, x is expected to be a number; you'll get NaN otherwise (just as you would for Math.sin(x), or any Math method, for that case).

Conclusion

Few scripting languages will let you build upon their intrinsic objects, like full-fledged programming languages do, via sub-classing. JavaScript is one of them; it allows web programmers to distillate their own JavaScript flavors, and hopefully, to stir up their imagination and help them in writing better code.

So check your code again and start prototyping your mocha version of JavaScript!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)