Skip to main content

By clicking Submit, you agree to the developerWorks terms of use.

The first time you sign into developerWorks, a profile is created for you. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

All information submitted is secure.

  • Close [x]

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerworks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

By clicking Submit, you agree to the developerWorks terms of use.

All information submitted is secure.

  • Close [x]

Review sandboxed natives, Fusebox, and FuseJS

A short history of safely enhanced JavaScript natives

Rob Larsen (rob@htmlcssjavascript.com), Interface Architect, Isobar
Rob Larsen photo
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.

Summary:  The concept of sandboxed natives, which are native JavaScript objects safely enhanced outside of the global namespace, has been circulating for several years. With Fusebox, we have a new, elegant approach to sandboxed natives—one that serves as the foundation for a new library, FuseJS.

Date:  08 Mar 2011
Level:  Intermediate PDF:  A4 and Letter (57KB | 13 pages)Get Adobe® Reader®
Also available in:   Chinese  Japanese

Activity:  8166 views
Comments:  

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.

Practice makes imperfect

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.

Broken "for...in"  loops

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."


Still, the temptation remains

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.frames collection.
  • 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.


Enter Fusebox

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 DOMContentLoaded event (or the library equivalent) fires. This is vital, since much activity depends on that particular event.
  • That they not pollute the window.frames collection.
  • That they be "real" natives. For example, the extensible Fusebox.Array should have a length property that operates in the same way as window.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();


this is this

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.


FuseJS

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

Features and road map

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.

Framework emulation

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.


The way forward

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.


Resources

Learn

Get products and technologies

Discuss

About the author

Rob Larsen photo

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.

Report abuse help

Report abuse

Thank you. This entry has been flagged for moderator attention.


Report abuse help

Report abuse

Report abuse submission failed. Please try again later.


developerWorks: Sign in


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

Choose your display name

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


Rate this article

Comments

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development
ArticleID=629941
ArticleTitle=Review sandboxed natives, Fusebox, and FuseJS
publish-date=03082011
author1-email=rob@htmlcssjavascript.com
author1-email-cc=bwetmore@us.ibm.com

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

For articles in technology zones (such as Java technology, Linux, Open source, XML), Popular tags shows the top tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), Popular tags shows the top tags for just that product zone.

For articles in technology zones (such as Java technology, Linux, Open source, XML), My tags shows your tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), My tags shows your tags for just that product zone.

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Try IBM PureSystems. No charge.

Special offers