Function.prototype.apply revisited

December 26th, 2006

You can learn a lot about JavaScript by implementing your own version of apply. In this post I present my own implementation with explanatory notes to highlight hidden complexity:

Function.prototype.apply = function(thisArg, argArray) {
    if (typeof this != “function”) {
        throw new Error(”apply called on incompatible ” +
                    “object (not a function)”);
    }
    if (argArray != null && !(argArray instanceof Array)
        && typeof argArray.callee != “function”) {
	throw new Error(”The 2nd argument to apply must ” +
                    “be an array or arguments object”);
    }

    thisArg = (thisArg == null) ? window : Object(thisArg);
    thisArg.__applyTemp__ = this;

    // youngpup’s hack
    var parameters = [],
        length = (argArray || “”).length >>> 0;
    for (var i = 0; i < length; i++) {
	parameters[i] = “argArray[” + i + “]”;
    };
    var functionCall =
            “thisArg.__applyTemp__(” + parameters + “)”;

    try {
	return eval(functionCall)
    } finally {
	try {
            delete thisArg.__applyTemp__
        } catch (e) {
            /* ignore */
        }
    }
}

Function.prototype.call = function(thisArg) {
    return this.apply(thisArg,
            Array.prototype.slice.apply(arguments, [1]));
}

Download: apply-call.js

Validating apply’s context and parameters

The ECMAScript specification says that apply may only be called on objects with a [[Call]] property (i.e. functions), and a TypeError should be thrown if an attempt is made to call it on non-function objects. The typeof operator allows us to identify functions easily: typeof object evaluates to "function" if and only if the object has a [[Call]] property.

The second parameter to apply is optional, but if it’s supplied it must be either an array or an arguments object - if not, a TypeError should be thrown. The expression argArray != null is deceptively simple, but exploits an interesting JavaScript fact: object == null evaluates to true if-and-only-if object is null or undefined (so object != null evaluates to true for any value except null or undefined.)

Checking whether an object is an instance of an array is simply a matter of using the instanceof operator with the Array constructor Array (i.e. object instanceof Array.) To check whether something is an arguments object is a little more complicated as the ECMAScript specification does not define an Arguments class or function. We resort to a capability test: we check whether the object has a callee property that’s a function (typeof object.callee == "function".) I think this is a better test than checking for a numeric length property, as both String and NodeList have length properties defined, but neither of them are should be accepted when passed as the second argument to apply.

Getting an appropriate object to use as this

If thisArg is null or undefined, the global-scope must be used as this. Since I’m writing this version of apply for use in a browser: the global-scope is the window object.

JavaScript’s typeof operator allows you to differentiate several types of primitive value (booleans, numbers and strings) To turn primitive values into objects (as required by the specification of apply) we pass them through the Object function. From this point on, you should use instanceof instead of typeof to differentiate different types of object.

Using functions as methods

The purpose of apply is to be able to assign a specific object as this for the duration of a function call. Without native browser-support, the only way to achieve this is to assign the function as a property of the object (thereby making the function a ‘method’ of the object.) Ideally we would generate and check for a non-existent property-name - but this cannot be achieved with 100% reliability on our main target browser (Internet Explorer 5) as there is no way of checking for the existence of a property (the specification defines Object.prototype.hasOwnProperty for this purpose; IE5 doesn’t support it, and it cannot be implemented reliably.) Accepting the futility of the situation, we go with a ‘good enough’ implementation and hope that __applyTemp__ isn’t being used for any other purpose.

Establishing how many parameters to pass

We’ve established that argArray is either null, undefined, an array or an arguments object, we now need to establish how many parameters need passing to the function call. The rather obtuse expression (argArray || "").length >>> 0) is dense code to meet the specification: no arguments (0) are passed if argArray is null or undefined, otherwise the length property is cast to an unsigned 32-bit integer, and the result is the number of parameters that will be passed (it’s extremely unlikely that the result will be any different to using the length property as-is; but since it’s trivial to do things properly, we do so.) A more simplistic and readable version of the code would read: (argArray == null) ? 0 : argArray.length

Calling a function with an arbitrary number of parameters

If you google for implementations of apply you’ll inevitably come across code derived from youngpup’s version. I don’t know if he created the hack, but it’s the inspiration for my version, so credit where credit’s due. The purpose of the hack is to pass a variable number of parameters to a function, without having to consider 1, 2, 3, 4 arguments (etc.) separately. The hack works by building a function-call as a string, then passing the string to eval. For example, to pass two parameters, this would be passed to eval: "thisArg.__applyTemp__(argArray[0], argArray[1])"

Error handling and cleaning-up

Earlier on, we attached the function as a method of the object. To ensure this is a temporary attachment, we have to remove the method once the function call has completed (using delete). We have to be careful though, as the function call may cause an error to be thrown, and we don’t want to swallow or ignore this error. Fortunately, we can use the try/finally construct to run some clean-up code after the function call, regardless of whether the function-call ends in an exception or not.

Ideally, cleaning-up would simply be a matter of writing delete object.property; but in the world of Internet Explorer nothing is that simple. Errors can be thrown when you try to delete properties on host objects (a host object is an object supplied by the browser and accessible through JavaScript, but not defined in the ECMAScript specification. e.g. ActiveXObject.) As we don’t imagine anyone will be interested in errors generated during clean-up, we wrap a simple try/catch around the clean-up code to swallow any errors generated here.

Function.prototype.call

It’s trivial to implement call in terms of apply. The first parameter to call becomes the first parameter to apply. The rest of the parameters are passed by applying Array’s slice method to the arguments object. This demonstrates our first useful application of apply: converting arguments objects to standard arrays.

Conclusion

To implement apply we’ve had to understand typeof, functions, methods, this, null, the loose equality operator, global-scope, object properties, try/catch/finally, the arguments object, eval, primitive values and errors. This is a lot of ground to cover for such a short function, and I can’t think of any other function that teaches you so much while writing it.

Numbers in form-fields

January 8th, 2007

JavaScript is often used for client-side form validation to save unnecessary round-trips to the server. Unfortunately, lots of client-side validation relies on lenient JavaScript methods such as parseInt, allowing numbers to be input in ways totally unacceptable to your server-side code. Let’s have a look at the problems and some solutions.

Server-side validation

It’s a golden-rule of web-development that you never depend on client-side validation. Users can turn JavaScript off, and hackers can send any data they like at your servers. So, the first thing you have to decide is what data is acceptable to your server. For numbers on the server, are you planning on storing them in a database or performing calculations with them? In either case, what format do the numbers need to be in: integers? decimals? Are you going to manually trim white-space from the number, are you going to allow negative numbers? Read the rest of this entry »

Array push and pop: a complete embarrassment for JavaScript

December 6th, 2006

Most JavaScript libraries include trivial implementations of Array’s push and pop methods to provide support for older browsers. However, literally every library’s implementation is flawed. While this is bad enough, I’ve also found that every browser’s implementation of push and pop contains bugs. These vary from browser to browser: Internet Explorer’s methods can’t be reused; Safari has type-conversion issues; and Firefox & Opera don’t truncate Arrays properly.

It’s been seven years since the publication of the official ECMAScript Language Specification, and I think we should expect a little more from our browsers. In this article, I’ve documented the current problems, and I show how to write library implementations conforming precisely to the language spec’. Read the rest of this entry »

Iterating over sparse arrays

December 6th, 2006

This is a quick tip for iterating over arrays and other objects with a length property. It’s particularly suitable for sparse arrays, without having to worry whether any methods have been added to the prototype chain. Read the rest of this entry »

Fixing Firebug’s Style Tab

December 11th, 2006

Lots of people are unable to use the Style tab in the new Firebug beta. The Firebug FAQ leads to a thread suggesting this fix:

  1. Uninstall Firefox
  2. Delete the (left-over) install folder (e.g. C:\Program Files\Mozilla Firefox)
  3. Reinstall Firefox using a custom install (to ensure the DOM Inspector is installed

Knowing that I already had the DOM Inspector installed, I thought I’d try removing just a few files, instead of the whole install folder. Whatever I did worked, and the Style tab works for me now. This more conservative procedure is:

  1. Uninstall Firefox
  2. Delete only the named inspector* in C:\Program Files\Mozilla Firefox\components
  3. Reinstall Firefox using a custom install (to ensure the DOM Inspector is installed

I’m not sure whether the uninstall and reinstall procedure is strictly necessary, so I’m hoping someone out there can try this:

  1. Stop Firefox
  2. Delete all files named inspector* in C:\Program Files\Mozilla Firefox\components
  3. Restart Firefox

Buy Viagra
Buy Viagra Online
Buy Generic Viagra
Discount Viagra
Order Viagra
Generic Viagra
Cheap Viagra
Viagra
Viagra Online
Purchase Viagra
Viagra Sale
Order Viagra Online
Online Viagra
Viagra Cheap
Buy Viagra Cheap
buy acomplia
Buy Cialis
Buy Cialis Online
Buy Generic Cialis
Discount Cialis
Order Cialis
Generic Cialis
Cheap Cialis
Cialis
Cialis Online
Purchase Cialis
Cialis Sale
Order Viagra Online
online viagra
cheapest viagra
cheap viagra online
viagra order
Buy Levitra
Cheap Levitra
Order Levitra
buy propecia
cheap propecia
propecia online
Buy Flomax
Buy Zoloft
Buy Xanax
Cheap Xanax
Generic Xanax
Order Xanax
buy neurontin
buy wellbutrin
buy lexapro
buy prozac
buy paxil
buy soma
cheap soma
buy tramadol
soma online
cheap tramadol
tramadol online
buy phentermine
cheap phentermine
order phentermine
phentermine online
buy fosamax
buy hoodia
buy levaquin
buy accutane
buy zyban
buy provigil
buy xenical
buy amoxicillin
buy augmentin
buy cipro
buy zithromax
buy zovirax