One of the most powerful and therefore potentially dangerous features of JavaScript is its ability to enhance global objects with custom methods and attributes.
JavaScript offers direct access to global objects like Array, String, Function, and even Object itself
through the prototype property. If you want to
add a contains method to the native Array, you
simply define the contains function on Array.prototype, as shown in Listing 1.
Listing 1. Enhancing Array with a contains method
Array.prototype.contains = function(q){
for ( var i = 0, len = this.length; i < len; i++) {
if ( this[ i ] === q ) {
return true;
}
}
return false;
}
console.log([1,2,3,4,5].contains(4));
>>>true
console.log([1,2,3,4,5].contains('hello'));
>>>false
|
As its name implies, the Prototype JavaScript library leverages this technique heavily to enhance core JavaScript functionality.
In theory, this is great. Being able to enhance natives allows for elegant,
almost transparent manipulation of core objects. Listing
2 shows the difference in usage between the contains method added to Array in
Listing 1, a similar function created without
using prototypal inheritance, and an example of a similar function from
the jQuery JavaScript library.
Listing 2. Array.contains, contains() and $.inArray()
//This implementation is simpler, as it operates directly on the Array //It takes a single argument [1,2,3,4,5].contains(5); >>>true //The function version takes an extra argument and doesn't operate directly on the Array contains([1,2,3,4,5],5) >>> true //jquery inArray. The query is first, the array is second. //It returns the index of the query in the Array $.inArray(4,[1,2,3,4,5]); >>>3 |
The difference between the example using prototypal inheritance and the method-based implementations is small, but as this article explains, it's one that many of the foremost JavaScript developers find intriguing.
Prototypal enhancement on native JavaScript objects is an appealing, powerful idea. In practice, though, some serious concerns with modifying native global objects have arisen. Over the past several years, as JavaScript has grown into its place as a core web technology, we've discovered that some things are more complex than they appear at first glance.
Let's walk through some of the issues.
Adding methods and properties to native objects breaks for...in loops, and any broadly implemented code quickly
develops problems related to this issue. For that reason, it's often the
first cited counter-argument for modifying natives.
Until version 1.4, Prototype did just this with Object.prototype (from which all JavaScript objects inherit),
and it continues to do so with elements like Array and String. Listing 3 shows this with a simple loop through an Array enhanced by Prototype, and then through a
native, unmodified Array. The enhanced Array loop exposes 37 additional methods and
properties.
Listing 3. Broken "for...in" loops
console.log(Prototype.Version)
;
>>>1.6.1
var arr = [1,2,3,4,5],
count = 0;
for (var i in arr){
count++
}
console.log(count);
>>>42
//now without Prototype
console.log(Prototype.Version)
;
>>> undefined
var arr = [1,2,3,4,5],
count = 0;
for (var i in arr){
count++
}
console.log(count);
>>>5
|
It is possible to do a lot of coding without ever using for...in statements—in fact, I use them only a
handful of times each year—and ways to protect against
enhanced prototypes that use Object.hasOwnProperty() are well-documented, as shown in Listing 4. However, it's safer to leave them alone,
especially in environments where you can't be sure of who or what will be
interacting with your code. Modern web development works best when it has
compatible and predictable interactions. Enhancing native objects is the
essence of unpredictability.
Listing 4. Protecting against enhanced natives with Object.hasOwnProperty()
console.log(Prototype.Version);
>>>1.6.1
var arr = [1,2,3,4,5],
count = 0;
for (var prop in arr){
if (arr.hasOwnProperty(prop)){
count++
}
}
console.log(count);
>>>5
|
Inconsistency with other libraries or native implementations
The modern web can be a complex place. This complexity can, in turn, uncover issues with native enhancement in several different ways.
On one hand, many sites use two or more core libraries on the same page.
This isn't an optimal practice, since two libraries can collide in a
number of ways, but it's one that is common enough to warrant a noConflict() method in jQuery.
On the other hand, we have a new and generally positive browser war on our hands. All the major browser developers—including Microsoft, as of Windows® Internet Explorer® version 9—are racing to implement new and emerging standards (such as the Canvas 2D API, geolocation) and to innovate with new features. We have learned that implementing "missing" JavaScript features on native objects can be dangerous in subtle ways. It can create incompatibilities with other libraries on the page or with implementations of missing features in the browser.
Prototype presents an example of the latter with its implementation of
Array.some() (as of Prototype version 1.6).
The some() method is a new ECMAScript Revision
5 Array method that iterates through members of
an Array (or an Array-like object) until some test condition is met. This is
shown in Listing 5.
Listing 5. An example of Array.some()
//the test that must be satisfied
function isFive(element, index, array) {
return (element === 5);
}
[2,4,6,7,8,9].some(isFive);
>>>false
[2, 5, 8, 1, 4].some(isFive);
>>> true.
//some() exits after second element
|
The Prototype library implements its own version of some() on Array. The implementation
assumes that the object being iterated on is an Array and therefore expects an Array.each method (which it also provides as part of the
library). The specification states that the this value of the method must be generic, meaning that it
throws an error when Prototype is paired with YUI Test. YUI Test leverages
Y.Array, which uses the call() method to borrow some array-like objects (in this case,
an HTMLCollection). Because Prototype paves
over Array.some() without checking if it
already exists, this throws an error in YUI Test. The Yahoo! Array.some() method is shown in Listing 6.
Listing 6. Y.Array.some()
/*
Copyright (c) 2010, Yahoo! Inc. All rights reserved.
*/
YArray.some = (Native.some) ?
function(a, f, o) {
return Native.some.call(a, f, o);
} :
function(a, f, o) {
var l = a.length, i;
for (i = 0; i < l; i = i + 1) {
if (f.call(o, a[i], i, a)) {
return true;
}
}
return false;
};
|
Nicholas Zakas from Yahoo! sums up the problem succinctly: "Don't Modify Objects You Don't Own."
Despite the pitfalls, the idea of extending and enhancing core JavaScript objects is an appealing one. A certain elegance surrounds it, especially for people who are comfortable with JavaScript as a language (as opposed to developers, who rely entirely on one or more library APIs).
Thankfully, sandboxed natives—which are native objects enhanced in a way that separates them from the global space—offer a safe alternative.
Sandboxed natives: the long journey to (prototypal) salvation
The concept of sandboxed natives can be traced back to two 2006 posts by
Dean Edwards called "Sandboxing JavaScript Using <iframe>" and "How to Subclass the JavaScript Array
Object." He offered a clever solution to the problem of enhancing natives
and proposed using an iframe to copy and enhance Array without polluting the global space.
The proof of concept is quite simple, and is shown in Listing 7.
Listing 7. Dean Edwards subclasses Array
//code copyright Dean Edwards
//http://dean.edwards.name/weblog/2006/11/hooray/
// create an <iframe>
var iframe = document.createElement("iframe");
iframe.style.display = "none";document.body.appendChild(iframe);
// write a script into the <iframe> and steal its Array object
frames[frames.length - 1].document.write(
"<script>parent.Array2 = Array;<\/script>");
|
This was an exciting post for the many developers that immediately recognized the technique's vast potential. Unfortunately, several problems surfaced with the approach:
- It doesn't work in Apple Safari.
- It pollutes the
window.framescollection. - It causes mixed-protocol security issues in Internet Explorer.
Though it was an imperfect solution, Edwards' approach inspired others to explore the issue, and several solutions have been proposed over the years.
For one notable solution, Hedger Wang used window.createPopup to fix several of the issues present in
Internet Explorer. His attempt is shown inListing 8.
Listing 8. Hedger Wang subclasses Array
;(function(){
if(!window.createPopup){return};
var fs =
function(){
/==/
var Array2 = parent.Array2 ;
var p1 = Array.prototype ;
var p2 = Array2.prototype;
for(i in p2 ){
p1[i] = p2[i];
};
parent.Array2 = Array;
parent.document.title = 'Array2 is ready';/*debug msg*/
/==/
};
document.title = 'Prepare Array2';/*debug msg*/
fs = (fs + '').split('/==/')[1];
window.createPopup().document.body.innerHTML =
'<img src="null" onerror="' + fs + '" />';
})();
|
His solution was taken up by the Dojo team and included in the library. Several issues were eventually discovered in connection with the technique—including a memory leak in Internet Explorer—and the code was subsequently dropped from the library.
With the creation of FuseJS, a new JavaScript framework, John-David Dalton
decided to use the sandboxed natives concept as the core for his new
project. The goal was to create a stable, cross-browser sandbox system as
the foundation for the framework and to extend the sandbox concept beyond
Array (the focus of previous attempts) to
other natives.
In September 2010, he split this core code out as a separate, open source project called Fusebox and released a series of screencasts to introduce the project. In the first of these screencasts, he outlined the technical requirements for this new version of sandboxed natives:
- That they be loaded before the DOM as to be ready when the key
DOMContentLoadedevent (or the library equivalent) fires. This is vital, since much activity depends on that particular event. - That they not pollute the
window.framescollection. - That they be "real" natives. For example, the extensible
Fusebox.Arrayshould have alengthproperty that operates in the same way aswindow.Array.length. - That the objects and methods be "chainable." Chaining is fun and convenient.
- That they not rely on browser sniffing. Browser sniffing brings its own unpredictability, so it's best to avoid it.
Three separate techniques are used to create the sandboxes and reach those goals:
ActiveXObject('htmlfile')is used in Internet Explorer.Object['__proto__']is leveraged for Gecko and WebKit browsers.- Iframes are used for Opera.
The screencast series goes into technical detail about the creation of the
sandbox environment. This article focuses on the result. As Listing 9 shows, the instantiation of Fusebox lets
you manipulate Fusebox.Array as desired without
touching the global Array.
Listing 9. Fuseboxed arrays
//Fusebox can be called with or without new
var fb = Fusebox();
fb.Array.prototype.contains = function(q){
for ( var i = 0, len = this.length; i < len; i++ ) {
if ( this[i] === q ) {
return true;
}
}
return false;
}
console.log(fb.Array(1,2,3,4,5).contains(4));
>>>true
console.log(typeof Array.prototype.contains);
>>>false
|
As Listing 10 shows, Fusebox.Array behaves like a real array with a proper length property.
Listing 10. Fusebox.Array.length
var fb = new Fusebox(); console.log(fb.Array(1,2,3,4,5).length); >>>5 |
One interesting example of the power of sandboxing is its ability to create multiple sandboxes. This offers a clear path to clean code organization and logical inheritance patterns; it is shown in Listing 11.
Listing 11. Multiple subclassed arrays
var widgetCode = Fusebox();
var applicationCode = Fusebox();
widgetCode.Array.prototype.widget = function(){
return true;
}
applicationCode.Array.prototype.app= function(){
return true;
}
console.log(widgetCode.Array().widget());
>>>true
console.log(applicationCode.Array().app());
>>>true
widgetCode.Array().app();
>>>TypeError: widgetCode.Array().app() is not a function
applicationCode.Array().widget();
>>>TypeError: applicationCode.Array().widget() is not a function
//check the native
[1,2,3,4,5].widget();
>>>TypeError: [1, 2, 3, 4, 5].widget is not a function
|
Beyond helper functions and implementation of missing features, application
code can be implemented on FuseBox.Array.prototype. Listing 12
shows how to create an Array method that would
process a series of latitude and longitude pairs into Google Maps LatLng objects.
Listing 12. Adding application-specific google.maps functionality to Array
var myMap = fusebox();
myMap.Array.prototype.mapify=function(){
return this.map(function (data) {
return new google.maps.LatLng(data.lat,data.lng);
}
);
}
var myGMCoords = myMap.Array(
{"lat":"-34.397",
"lng" : "150.644"},
{"lat":"-64.397",
"lng" : "100.644"},
{"lat":"-94.397",
"lng" : "50.644"}
).mapify(); |
One thing to notice about the previous code is that this.map() inside the body of the function works even in
browsers that don't have a native Array.map().
This is because the this value inside the
function refers to the enhanced Array you've
created in your sandbox. Listing 13 adds a new
property to fb.Array.prototype, and then later
accesses it directly off of this.
Listing 13. An illustration of the this keyword in Fusebox
fb.Array.prototype.isFuseBoxed = "yes, this is a Fuseboxed Array";
fb.Array.prototype.whatsThis=function(){
console.log(this.isFuseBoxed);
}
fb.Array(1,2,3,4,5).whatsThis();
>>>"yes, this is a Fuseboxed Array"
|
This code contrasts with jQuery, where this is
commonly passed into $() to access the
library's methods. Direct access to enhanced functionality on this with Fuse seems like a natural solution.
Fusebox is meant to be the foundation of a new framework, FuseJS. Built on top of Fusebox, FuseJS is currently in alpha testing. The project isn't ready for prime time, but since the framework's architecture, guiding design principles, and future road map are very exciting, it's worth examining even in its nascent state.
The project is hosted on GitHub (see Resources), where you can clone the project and build a copy using the commands provided in Listing 14. (You will need both Git and Ruby.)
Listing 14. Cloning FuseJS
git clone git://github.com/jdalton/fusejs.git cd fusejs git submodule update --init ruby Build.rb |
FuseJS will offer several nice features beyond the basic tools for DOM manipulation, CSS selectors, and event registrations common to all general-purpose libraries (and the aforementioned sandboxed natives core). One of the most interesting is the ability to build a customized version of the library. This functionality will manifest itself in the choice of several different CSS selector engines and in the concept of library emulation.
The ability to choose CSS selector engines allows the library as a whole to keep up with developments in the selector engine space and, at a more granular level, a development team to choose an engine that is optimized for their tasks or coding style.
The currently supported CSS selector engines are:
- NWMatcher
- Acme (Dojo)
- DOMAssistant
- DomQuery (ExtJS)
- Sizzle (jQuery)
- Peppy
- Slick (MooTools)
- Sly
The default is NWMatcher, which was chosen for its adherence to the CSS3 specification. It is fast, predictable, and safe; adhering to the specification as a guiding principle will help FuseJS avoid incompatibilities moving forward.
FuseJS will feature framework emulation. This means you can create a custom build with, for example, Prototype emulation; then swap FuseJS core plus a Prototype emulation layer for the core Prototype code without having to refactor application code. This functionality includes performance improvements and bug fixes, and allows a relatively painless path off of legacy systems for developers and organizations looking to upgrade their core code. They can increase performance and flexibility without the occasionally massive expense of rewriting the application layer. It also lets plug-ins and libraries built on top of existing libraries be leveraged in a Fuse-based environment. This gives the library a running start in terms of hurdles to adoption, since off-the-shelf components and plug-ins are a large consideration when choosing a library.
The plan is for Prototype emulation to be included in the project when it reaches beta.
With a powerful core based on sandboxed natives, framework design lessons learned from other popular libraries, and the flexibility of custom builds, FuseJS is poised to be a reinvigorating entry into the JavaScript library space. It is a new, creative entry into the field that has several major libraries firmly established and set in the features/bug-fix pattern of mature code. If JavaScript is to continue to drive the future of the web, it needs a stream of fresh thought to ensure that the community doesn't stagnate. FuseJS looks like it will fulfill most expectations when it emerges from alpha and is put into the hands of developers.
Learn
- Learn more about FuseJS.
- Check out John-David Dalton's series of Fusebox
screencasts.
- Read Dean Edwards' blogs "Sandboxing
JavaScript Using
<iframe>" and "How to Subclass the JavaScript Array Object." - Nicholas C. Zakas shares his experiences
with "Maintainable JavaScript: Don’t modify objects you don't
own."
- Learn more about Prototype.
- Follow developerWorks on
Twitter.
- Discover what's new in CSS3.
- The Web development zone
specializes in articles covering various web-based solutions.
- Stay current with developerWorks' technical events and webcasts.
- Watch developerWorks on-demand demos ranging from product installation
and setup for beginners to advanced functionality for experienced
developers.
Get products and technologies
- Download Fusebox on GitHub.
- Download the
current code for FuseJS on
GitHub.
- Fusebox supports
several CSS selector engines, including NWMatcher, DOMAssistant, Sizzle, Peppy, Slick, and Sly.
- Check out Query from Dojo.
- Download IBM
product evaluation versions or explore
the online trials in the IBM SOA Sandbox and get your hands on
application development tools and middleware products from
DB2®, Lotus®, Rational®,
Tivoli®, and WebSphere®.
Discuss
- Create your developerWorks profile today and set up a watch list on JavaScript. Get connected and stay
connected with developerWorks community.
- Find other developerWorks members interested in web development.
- Share what you know: Join one of our developerWorks groups focused on web
topics.
- Roland Barcia talks about Web 2.0 and middleware in his blog.
- Follow developerWorks' members' shared bookmarks on web topics.
- Get answers quickly: Visit the Web 2.0 Apps forum.
- Get answers quickly: Visit the Ajax forum.

Rob Larsen has more than 11 years of experience building and designing websites and web applications. Currently, he's an interface architect at Isobar, working with HTML5, CSS3, and other emerging technologies for some of the world's largest brands. Rob writes about the web and web technologies at his blog, HTML + CSS + JavaScript, and you can reach him at rob@htmlcssjavascript.com.




