|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionJust so you know, you can use the Intrinsic JavaScript objects exposing the 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. PrototypesThe 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 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 // 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, But before going on, lets look at the prototyping syntax: we extend intrinsic objects through the // 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 var s = eval(String.trim);
// s == 'function() { return this.split(/\s/).join(' '); }'
If the prototype declaration names the function like shown before: 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: 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: // 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 (
OK, so once we're done trimming, here's a VB-like string replicator: // 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
I could've named the function 'string' just like in VB, but it may lead to confusion, being the // 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 // 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 So what about a zero-trailing function? Well it's just as easy as inverting the concatenation arguments: // 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 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 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 // 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 There's a couple of // 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: // 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 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 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 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 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: // 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 Once we have the reverse of the string, all that's left to do is to insert the thousands separators: // 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 So our old format function can evolve into the new and improved // 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 // 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 ( 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: // 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: // 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 And so much for number formatting. These prototypes fully serve my display needs, and you can build on them towards your award-winning JavaScript Date Object PrototypesLet's turn our attention towards the // 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
The previous // 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: // 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 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: // 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: // 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 PrototypesTo close the topic, I'll show you how to prototype some Math object functions. The // 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 For the sample functions, ConclusionFew 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!
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||