Fail proof access to JavaScript’s built-in types…
Because I'm paranoid...
JavaScript is a beautiful programming language, the de facto client side scripting language of the web until something new comes along, which I don’t think will ever happen. It’s extremely elastic in that one can create user defined methods to use with built in objects like Functions, Arrays and even generic objects. It exposes DOM and HTML interfaces that one can extend as per the requirements of the web app they’re building.
It is so flexible that features not available in certain browsers, due to poor implementations of W3 standards or a complete disregard of their recommendations, could be added dynamically by writing JavaScript code that fills in those gaps, usually referred to as shims or polyfills. However, with this kind of malleability, it is also possible to overwrite built-in objects references.
Messing up built-in objects
Take for instance the following snippet.
String.prototype.reverse = function () { var out = []; for ( var i = this.length - 1; i >= 0; i-- ) { out.push( this.charAt( i ) ); } return out.join( '' ); }
After writing that piece, I can very easily reverse a string just by using the ‘reverse’ method on a string primitive or a String
object. This is because the built in object is available for access in the global namespace via ‘String
‘. The problem is I can redefine String
to anything I want.
String = 'LOL'
If I write this piece of code before I define the reverse method, a TypeError will be thrown because String
is now simply a variable that happens to hold the string ‘LOL’ instead of a reference to the String
object. It’s still there somewhere, mind you, but it cannot be accessed because the only link to it has been severed. In fact, I didn’t really need to overwrite the String
variable. I could just as easily do the following.
delete window.String
That would basically delete the global variable String
completely.
This is kind of a problem for those who build open source JavaScript libraries as it’s now at the mercy of the user. A script call to one that simply executes a rewrite or deletion of any or all those variables – Function
, Number
, String
, Array
, Object
– prior to your script will ensure your work is rendered useless. Luckily for you, I exist.
Good?! So what to do?
Well, we’ll first need to understand that JavaScript lets data of certain types exist in two different forms – primitives and objects.
Primitives
The following snippet shows the initialisation of a variable into a string primitive.
var str = 'Hello'
Here, the variable str
contains the string ‘Hello’. There is nothing more to the variable. No properties can be assigned to it if we tried.
str.xyz = 'World'
That snippet would fail to affect the variable str
usually. In JavaScript strict mode, it will throw a TypeError as str
is not an object, just a primitive. Primitives can be instantiated without any issues and regardless of the presence of the String
function.
Objects
The line below will create an object of the String
function and save a reference to it in the variable str
.
var str = new String( 'Hello' )
This str
can now be assigned properties. Here’s something else: the constructor property of str
holds a reference to the String
function.
var strCons = str.constructor // strCons now references the String function
But how is that relevant?
Yeah! What does that matter? If the String
function itself gets messed up, how are you going to initialise a String
object in the first place, let alone access its constructor?
Well, JavaScript does this thing called type coercion. It’s what makes it such a fun language to code in.
Type coercion
The constructor property and basically every property and method that forms the prototype of the String
function can also be accessed on primitives. JavaScript will coerce the string primitive to act like a String
object for commands where it is necessary.
This means, writing a snippet like so…
var Str = 'hello'.constructor
… would let Str
be the new reference to the built-in String
function.
Nice! Does it work on other types?
Sure it does. The following is how one can access the other built in constructors.
var Num = (0).constructor // Number var Bool = (false).constructor // Boolean var Arr = [].constructor // Array var Fn = (function () {}).constructor // Function var Obj = ({}).constructor // Object
Optimisations
JavaScript objects are also associative arrays, which is why instead of accessing the constructor property of the different coerced primitives, we could access the value of the ‘constructor’ key. So, to access Number
, we could write something like…
var Num = (0)['constructor']
Now, since the string 'constructor'
can be stored in a variable, we can access all constructors of all the built in types as follows.
There’s a reason we don’t assign values to a variable called constructor
in the global scope.
var constr = 'constructor' var Str = ''[constr] var Num = (0)[constr] var Bool = (false)[constr] var Arr = [][constr] var Fn = (function () {})[constr] var Obj = ({})[constr]
Of course, we don’t need to use the empty string there. constr
already exists. We can just use that instead. Also, we don’t need the empty anonymous function literal. The constructor of String
, Number
and every built in type is a Function
.
var Str = constr[constr] var Fn = constr[constr][constr]
Let’s store our new references to these types with their proper names as properties of a globally accessible object.
(function () { var constructor = 'constructor' window.Types = { String : constructor[constructor], Number : (0)[constructor], Boolean : (false)[constructor], Array : [][constructor], Function : constructor[constructor][constructor], Object : ({})[constructor] } })()
This can, of course, be further optimised to…
(function () { var c = 'constructor' window.Types = { String : c[c], Number : 0[c], Boolean : (!1)[c], Array : [][c], Function : c[c][c], Object : {}[c] } })()
And there you have it.
Thanks for reading. I hope you’ve found this helpful. Can this piece be extended? Did I miss anything? Do you wish to know more? Critiques? Let me know in the comments…