JavaScript defines several objects that are part of its core: Array, Boolean, Date, Function, Math, Number, RegExp, and String. Each object extends Object, inheriting and defining its own properties and methods. I’ve occasionally needed to augment these core objects with new properties and methods and have created a library with these enhancements. In this article, I present various enhancements that I’ve introduced to the Array, Boolean, Date, Math, Number, and String objects.
I add new properties directly to the core object. For example, if I needed a Math constant for the square root of 3, I’d specify Math.SQRT3 = 1.732050807;. To add a new method, I first determine whether the method associates with a core object (object method) or with object instances (instance method). If it associates with an object, I add it directly to the object (e.g., Math.factorial = function(n) { ... }). If it associates with object instances, I add it to the object’s prototype (e.g., Number.prototype.abs = function() { ... }).
Methods and Keyword this
Within an object method, this refers to the object itself. Within an instance method, this refers to the object instance. For example, in " remove leading and trailing whitespace ".trim(), this refers to the " remove leading and trailing whitespace " instance of the String object in String‘s trim() method.
Name Collisions
You should be cautious with augmentation because of the possibility for name collisions. For example, suppose a factorial() method whose implementation differs from (and is possibly more performant than) your factorial() method is added to Math in the future. You probably wouldn’t want to clobber the new factorial() method. The solution to this problem is to always test a core object for the existence of a same-named method before adding the method. The following code fragment presents a demonstration:
Of course, this solution isn’t foolproof. A method could be added whose parameter list differs from your method’s parameter list. To be absolutely sure that you won’t run into any problems, add a unique prefix to your method name. For example, you could specify your reversed Internet domain name. Because my domain name is tutortutor.ca, I would specify Math.ca_tutortutor_factorial. Although this is a cumbersome solution, it should give some peace of mind to those who are worried about name conflicts.
Augmenting Array
The Array object makes it possible to create and manipulate arrays. Two methods that would make this object more useful are equals(), which compares two arrays for equality, and fill(), which initializes each array element to a specified value.
Implementing and Testing equals()
The following code fragment presents the implemention of an equals() method, which shallowly compares two arrays — it doesn’t handle the case of nested arrays:
equals() is called with an array argument. If the current array and array refer to the same array (=== avoids type conversion; the types must be the same to be equal), this method returns true.
equals() next checks array for null or undefined. When either value is passed, this method returns false. Assuming that array contains neither value, equals() ensures that it’s dealing with an array by concatenating array to an empty array.
equals() compares the array lengths, returning false when these lengths differ. It then compares each array element via !== (to avoid type conversion), returning false when there’s a mismatch. At this point, the arrays are considered equal and true returns.
As always, it’s essential to test code. The following test cases exercise the equals() method, testing the various possibilities:
When you run these test cases, you should observe the following output (via alert dialog boxes):
Implementing and Testing fill()
The following code fragment presents the implementation of a fill() method, which fills all elements of the array on which this method is called with the same value:
fill() is called with an item argument. If null or undefined is passed, this method throws an exception that identifies either value. (You might prefer to fill the array with null or undefined.) Otherwise, it populates the entire array with item and returns the array.
I’ve created the following test cases to test this method:
When you run these test cases, you should observe the following output:
Augmenting Boolean
The Boolean object is an object wrapper for Boolean true/false values. I’ve added a parse() method to this object to facilitate parsing strings into true/false values. The following code fragment presents this method:
This method returns false for any argument that is not a string, for the empty string, and for any value other than "true" (case doesn’t matter) or "yes" (case doesn’t matter). It returns true for these two possibilities.
The following test cases exercise this method:
When you run these test cases, you should observe the following output:
Augmenting Date
The Date object describes a single moment in time based on a time value that’s the number of milliseconds since January 1, 1970 UTC. I’ve added object and instance isLeap() methods to this object that determine if a specific date occurs in a leap year.
Implementing and Testing an isLeap() Object Method
The following code fragment presents the implementation of an isLeap() object method, which determines if its date argument represents a leap year:
Instead of using a date instanceof Date expression to determine if the date argument is of type Date, this method employs the more reliable Object.prototype.toString.call(date) != '[object Date]' expression to check the type — date instanceof Date would return false when date originated from another window. When a non-Date argument is detected, an exception is thrown that identifies the argument.
After invoking Date‘s getFullYear() method to extract the four-digit year from the date, isLeap() determines if this year is a leap year or not, returning true for a leap year. A year is a leap year when it’s divisible by 400 or is divisible by 4 but not divisible by 100.
The following test cases exercise this method:
When you run these test cases, you should observe output that’s similar to the following:
Implementing and Testing an isLeap() Instance Method
The following code fragment presents the implemention of an isLeap() instance method, which determines if the current Date instance represents a leap year:
This version of the isLeap() method is similar to its predecessor but doesn’t take a date argument. Instead, it operates on the current Date instance, which is represented by this.
The following test cases exercise this method:
When you run these test cases, you should observe output that’s similar to the following:
Augmenting Math
The Math object declares math-oriented object properties and methods and cannot be instantiated. I’ve added a GOLDEN_RATIO object property and rnd(), toDegrees(), toRadians(), and trunc() object methods to Math.
About the Golden Ratio
The Golden Ratio is a math constant that frequently appears in geometry. Two quantities are in the golden ratio when their ratio equals the ratio of their sum to the larger of the two quantities. In other words, for a greater than b, a/b = (a+b)/a.
Implementing and Testing GOLDEN_RATIO and rnd()
The following code fragment presents the implemention of the GOLDEN_RATIO constant and the rnd()
method:
After defining the GOLDEN_RATIO object property, this code fragment defines the rnd() object method, which takes a limit argument. This argument must be numeric; if not, an exception is thrown.
Math.random() returns a fractional value from 0.0 through (almost) 1.0. After being multiplied by limit, a fraction remains. This fraction is removed through truncation and truncation is performed by bitwise ORing 0 with the result.
Bitwise OR uses a ToInt32 internal function to convert its numeric operands to 32-bit signed integers. This operation eliminates the fractional part of the number and is more performant than using Math.floor() because a method call isn’t required.
The following test cases exercise these items:
When you run these test cases, you should observe output that’s similar to the following:
Implementing and Testing toDegrees(), toRadians(), and trunc()
The following code fragment presents the implementation of the toDegrees(), toRadians(), and trunc() methods:
Each method requires a numeric argument and throws an exception when this isn’t the case. The first two methods perform simple conversions to degrees or radians and the third method truncates it argument via Math‘s floor() method.
Why introduce a trunc() method when floor() already performs truncation? When it receives a negative non-integer argument, floor() rounds this number down to the next highest negative integer. For example, floor() converts -4.1 to -5 instead of the more desirable -4.
The following test cases exercise these items:
When you run these test cases, you should observe the following output:
Augmenting Number
The Number object is an object wrapper for 64-bit double precision floating-point numbers. The following code fragment presents the implementation of a trunc() instance method that’s similar to its object method counterpart in the Math object:
The following test cases exercise this method:
The two dots in 10..trunc() prevent the JavaScript parser from assuming that trunc is the fractional part (which would be assumed when encountering 10.trunc()) and reporting an error. To be clearer, I could place 10. in round brackets, as in (10.).trunc().
When you run these test cases, you should observe the following output:
Augmenting String
The String object is an object wrapper for strings. I’ve added endsWith(), reverse(), and startsWith() methods that are similar to their Java language counterparts to this object.
Implementing and Testing endsWith() and startsWith()
The following code fragment presents the implemention of endsWith() and startsWith() methods that perform case-sensitive comparisons of a suffix or prefix with the end or start of a string, respectively:
Each of endsWith() and startsWith() is similar in that it first verifies that its argument is a string, throwing an exception when this isn’t the case. It then returns true when its argument is the empty string because empty strings always match.
Each method also uses String‘s substring() method to extract the appropriate suffix or prefix from the string before the comparison. However, they differ in their calculations of the start and end indexes that are passed to substring().
The following test cases exercise these methods:
When you run these test cases, you should observe the following output:
Implementing and Testing reverse()
The following code fragment presents the implemention of a reverse() method that reverses the characters of the string on which this method is called and returns the resulting string:
reverse() loops over the string backwards and appends each character to a temporary string variable, which is returned. Because string concatenation is expensive, you might prefer an array-oriented expression such as return this.split("").reverse().join("");.
The following test case exercises this method:
When you run this test case, you should observe the following output:
Conclusion
JavaScript makes it easy to augment its core objects with new capabilities and you can probably think of additional examples.
I find it easiest to place all of a core object’s new property and method definitions in a separate file (e.g., date.js) and include the file in a page’s header via a <script> element (e.g., <script type="text/javascript" src="date.js"><script>).
For homework, add a shuffle() method to the Array object to shuffle an array of elements (e.g., playing card objects). Use this article’s rnd() method in the implementation.
The post Augmenting JavaScript Core Objects appeared first on SitePoint.