Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with:

Invoking A Native JavaScript Constructor Using Call() Or Apply()

By Ben Nadel on

This morning, I was working on some JavaScript code in which I wanted to create a proxy to a native JavaScript constructor (ex. Array, String). In the past, I've often invoked super-class constructors using JavaScript's call() or apply() method. But, in those cases, I had existing sub-class instances that would act as the target context (ie. "this"). When invoking a native JavaScript constructor, on the other hand, I had no existing sub-class instance; so, would this even be possible?

As a quick recap, JavaScript's call() and apply() methods allow us to change the execution context of a given function. That is, they allow us to change the "this" reference used within the body of a given function invocation. Typically, when a function is invoked, any embedded reference to "this" is implicitly a reference to the object on which the method was invoked. Using call() and apply(), however, we can explicitly define a new reference for "this" no matter where the target method resides.

When dealing with a native JavaScript constructor function, I didn't have a pre-existing variable to use as the "this" context. As such, I simply tried to pass "null" to the apply() method:

  • <!DOCTYPE html>
  • <html>
  • <head>
  • <title>Invoking A Native JavaScript Constructor With Apply()</title>
  •  
  • <script type="text/javascript">
  •  
  •  
  • // A proxy to the native Array constructor.
  • function createArray(){
  •  
  • // Invoke the native constructor, passing in any given
  • // arguments. When doing this, the new array is returned
  • // from the constructor.
  • var newArray = Array.apply( null, arguments );
  •  
  • // Return the newly created array.
  • return( newArray );
  •  
  • }
  •  
  •  
  • // A proxy to the native String constructor.
  • function createString(){
  •  
  • // Invoke the native constructor, passing in any given
  • // arguments. When doing this, the new string is returned
  • // from the constructor.
  • var newString = String.apply( null, arguments );
  •  
  • // Return the newly created string.
  • return( newString );
  •  
  • }
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Create a new Array.
  • var friends = createArray( "Sarah", "Tricia", "Joanna" );
  •  
  • // Log the friends.
  • console.log( friends );
  •  
  • // Log the type of object created.
  • console.log( Object.prototype.toString.call( friends ) );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • console.log( "...." );
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Create a new String.
  • var message = createString( "Boom shaka laka!" );
  •  
  • // Log the message.
  • console.log( message );
  •  
  • // Log the type of object created.
  • console.log( Object.prototype.toString.call( message ) );
  •  
  •  
  • </script>
  • </head>
  • <body>
  • <!-- Left intentionally blank. -->
  • </body>
  • </html>

As you can see, both the Array and String proxy methods simply pass "null" in as the first argument to the apply() method call. And, when we run the above code, we get the following console output:

["Sarah", "Tricia", "Joanna"]
[object Array]
....
Boom shaka laka!
[object String]

To be honest, I'm a bit surprised that this worked. I was almost certain that this approach would fail given the fact that we aren't using the "new" keyword as a means to create a new context. Now that I think about it, however, I believe that in the book JavaScript Enlightenment by Cody Lindley, it was mentioned that with native JavaScript constructors, the "new" keyword was optional (but don't quote me on that).

As a control to this experiment, I wanted to use the same kind of proxy with a custom constructor. This time, I create a new class, Friend, and tried to instantiate via a proxy:

  • <!DOCTYPE html>
  • <html>
  • <head>
  • <title>Invoking A Custom JavaScript Constructor With Apply()</title>
  •  
  • <script type="text/javascript">
  •  
  •  
  • // Create a custom constructor.
  • function Friend( name ){
  •  
  • // Store the property.
  • this.name = name;
  •  
  • }
  •  
  • // Define a class method.
  • Friend.prototype.getName = function(){
  •  
  • // Return the name property.
  • return( this.name );
  •  
  • };
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Create a proxy to the custom Friend constructor.
  • function createFriend(){
  •  
  • // Invoke the custom constructor, passing in any
  • // arguments that were provided.
  • var newFriend = Friend.apply( null, arguments );
  •  
  • // Return the newly created friend.
  • return( newFriend );
  •  
  • }
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Create a new friend.
  • var friend = createFriend( "Joanna" );
  •  
  • // Log the friend.
  • console.log( friend );
  •  
  •  
  • </script>
  • </head>
  • <body>
  • <!-- Left intentionally blank. -->
  • </body>
  • </html>

In this case, you can see that we are using the exact same approach as with the native JavaScript constructors; since we don't have a pre-existing instance, we are passing "null" in as the first argument to the apply() method. This time, when we run the page, however, we get the following console output:

undefined

My reservations about this approach have come into play now that we are working with a custom constructor. Since we didn't pass in a context, the target function ended up using the global object as the context. And, in fact, if we were to log "window.name" to the console, we would get:

"Joanna"

To get around this problem with the custom JavaScript class, we need to be able to pass an existing reference into the apply() function to be used as the explicit context. For this, we will rely on the Object.create() method as a way to extend the constructor's prototype:

  • <!DOCTYPE html>
  • <html>
  • <head>
  • <title>Invoking A Custom JavaScript Constructor With Apply()</title>
  •  
  • <script type="text/javascript">
  •  
  •  
  • // Create a custom constructor.
  • function Friend( name ){
  •  
  • // Store the property.
  • this.name = name;
  •  
  • }
  •  
  • // Define a class method.
  • Friend.prototype.getName = function(){
  •  
  • // Return the name property.
  • return( this.name );
  •  
  • };
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Create a proxy to the custom Friend constructor.
  • function createFriend(){
  •  
  • // Create an object that extends the target prototype.
  • var newFriend = Object.create( Friend.prototype );
  •  
  • // Invoke the custom constructor on the new object,
  • // passing in any arguments that were provided.
  • Friend.apply( newFriend, arguments );
  •  
  • // Return the newly created friend.
  • return( newFriend );
  •  
  • }
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Create a new friend.
  • var friend = createFriend( "Joanna" );
  •  
  • // Log the friend.
  • console.log( friend );
  •  
  •  
  • </script>
  • </head>
  • <body>
  • <!-- Left intentionally blank. -->
  • </body>
  • </html>

Here, rather than passing "null" into the apply() method, we are passing in an object that extends the prototype of the constructor we are trying to proxy. This time, when we run the above code, we get the following console output:

Friend { name="Joanna", getName=function() }

Awesome! We were able to proxy the custom JavaScript class constructor. As you can see, our new instance has the correct name (assigned using "this.name") as well as the prototype-based class method, getName().

Now that we have an approach working for custom class constructors, let's see if it will also work with native JavaScript class constructors. Hopefully we can come up with a unified approach to constructor function proxying. In the following code, we'll use the same logic with the Array class:

  • <!DOCTYPE html>
  • <html>
  • <head>
  • <title>Invoking A Native JavaScript Constructor With Apply()</title>
  •  
  • <script type="text/javascript">
  •  
  •  
  • // Create a proxy to the native Array constructor.
  • function createArray(){
  •  
  • // Create an object that extends the target prototype.
  • var newArray = Object.create( Array.prototype );
  •  
  • // Invoke the native constructor on the new object,
  • // passing in any arguments that were provided.
  • Array.apply( newArray, arguments );
  •  
  • // Return the newly created array.
  • return( newArray );
  •  
  • }
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Create a new Array.
  • var friends = createArray( "Sarah", "Tricia", "Joanna" );
  •  
  • // Log the friends.
  • console.log( friends );
  •  
  • // Log the type of object created.
  • console.log( Object.prototype.toString.call( friends ) );
  •  
  •  
  • </script>
  • </head>
  • <body>
  • <!-- Left intentionally blank. -->
  • </body>
  • </html>

As you can see, we are, again, using Object.create() to extend the Array's prototype object. Then, we are invoking the Array constructor function using this newly instantiated object. When we run the above code, however, we get the following console output:

[ ]
[object Object]

Hmmm. We were able to create an Array; but, nothing else worked - we didn't even get the right constructor property (ie. we got Object instead of Array). I can't say I really understand what happened here. It seems that explicitly assigning a context for the Array() constructor actually ended up erasing the context altogether?

If you look back at our first approach with the native JavaScript constructors, you can see that we were getting our new class instance as the return value from the native constructor. But, if you look at our custom constructor example, you can see that we were relying on the existing reference only. Perhaps we can combine these two approaches to use either the return value or the existing reference:

  • <!DOCTYPE html>
  • <html>
  • <head>
  • <title>Invoking A Native JavaScript Constructor With Apply()</title>
  •  
  • <script type="text/javascript">
  •  
  •  
  • // Create a proxy to the native Array constructor.
  • function createArray(){
  •  
  • // Create an object that extends the target prototype.
  • var newArray = Object.create( Array.prototype );
  •  
  • // Invoke the native constructor on the new object,
  • // passing in any arguments that were provided.
  • newArray = (Array.apply( newArray, arguments ) || newArray);
  •  
  • // Return the newly created array.
  • return( newArray );
  •  
  • }
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Create a new Array.
  • var friends = createArray( "Sarah", "Tricia", "Joanna" );
  •  
  • // Log the friends.
  • console.log( friends );
  •  
  • // Log the type of object created.
  • console.log( Object.prototype.toString.call( friends ) );
  •  
  •  
  • </script>
  • </head>
  • <body>
  • <!-- Left intentionally blank. -->
  • </body>
  • </html>

The key difference in this code is:

  • newArray = (Array.apply( newArray, arguments ) || newArray);

In this approach, we are either accepting the constructors return value; or, if the constructor returns a falsey, we are using the pre-existing object as defined by our Object.create() method call. This time, when we run the above code, we get the following console output:

["Sarah", "Tricia", "Joanna"]
[object Array]

Woohoo! We were able to successfully proxy the native JavaScript constructor function. We even ended up with the correct constructor property (ie. Array).

NOTE: If you were to proxy the Boolean() constructor and it returned a False, your logic would have to explicitly check for "undefined", not just any falsey value.

Now that we know this approach works with the native JavaScript constructors, let's try transferring it back to the custom JavaScript class constructors:

  • <!DOCTYPE html>
  • <html>
  • <head>
  • <title>Invoking A Custom JavaScript Constructor With Apply()</title>
  •  
  • <script type="text/javascript">
  •  
  •  
  • // Create a custom constructor.
  • function Friend( name ){
  •  
  • // Store the property.
  • this.name = name;
  •  
  • }
  •  
  • // Define a class method.
  • Friend.prototype.getName = function(){
  •  
  • // Return the name property.
  • return( this.name );
  •  
  • };
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Create a proxy to the custom Friend constructor.
  • function createFriend(){
  •  
  • // Create an object that extends the target prototype.
  • var newFriend = Object.create( Friend.prototype );
  •  
  • // Invoke the custom constructor on the new object,
  • // passing in any arguments that were provided.
  • newFriend = (Friend.apply( newFriend, arguments ) || newFriend);
  •  
  • // Return the newly created friend.
  • return( newFriend );
  •  
  • }
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Create a new friend.
  • var friend = createFriend( "Joanna" );
  •  
  • // Log the friend.
  • console.log( friend );
  •  
  •  
  • </script>
  • </head>
  • <body>
  • <!-- Left intentionally blank. -->
  • </body>
  • </html>

As you can see once again, we are using either the return value of the construtor or the pre-existing instance:

  • newFriend = (Friend.apply( newFriend, arguments ) || newFriend);

And, when we run the above code, we get the following console output:

Friend { name="Joanna", getName=function() }

Awesome! We now have a way to successfully proxy a JavaScript constructor function whether or not the constructor is for a native JavaScript object or a custom JavaScript object. This can be useful if we need a way to extend native objects without sacrificing native functionality. But, more thoughts to come on that later.




Reader Comments

Good timing as always. I just got through a chapter of "JS: the good parts" by Papa Crockford where he used apply() in the same way but didn't prefix it with useful info nor followed it with some great examples. The blind man now sees!

@Jean,

Ha ha, awesome my man, glad to be timely. That's a quality book. I read it on the plane rides to and from one of our beloved ColdFusion conferences (I think the last cf.Objective() conference).

Hey, I just ran into this scenario and this is an excellent write-up! Easy to understand and implement. Cheetos!