91

I was just reading this question and wanted to try the alias method rather than the function-wrapper method, but I couldn't seem to get it to work in either Firefox 3 or 3.5beta4, or Google Chrome, both in their debug windows and in a test web page.

Firebug:

>>> window.myAlias = document.getElementById
function()
>>> myAlias('item1')
>>> window.myAlias('item1')
>>> document.getElementById('item1')
<div id="item1">

If I put it in a web page, the call to myAlias gives me this error:

uncaught exception: [Exception... "Illegal operation on WrappedNative prototype object" nsresult: "0x8057000c (NS_ERROR_XPC_BAD_OP_ON_WN_PROTO)" location: "JS frame :: file:///[...snip...]/test.html :: <TOP_LEVEL> :: line 7" data: no]

Chrome (with >>>'s inserted for clarity):

>>> window.myAlias = document.getElementById
function getElementById() { [native code] }
>>> window.myAlias('item1')
TypeError: Illegal invocation
>>> document.getElementById('item1')
<div id=?"item1">?

And in the test page, I get the same "Illegal invocation".

Am I doing something wrong? Can anyone else reproduce this?

Also, oddly enough, I just tried and it works in IE8.

3
  • 1
    in IE8 — it could be some kind of magic function or something. Commented Jun 17, 2009 at 14:42
  • I've added comments to the incorrect answers at stackoverflow.com/questions/954417 and directed them here. Commented Jun 17, 2009 at 21:37
  • 2
    For browsers that support the Function.prototype.bind method: window.myAlias = document.getElementById.bind(document); Search the MDN docs, there will be a bind shim.
    – Ben
    Commented Aug 12, 2011 at 0:09

6 Answers 6

194

I dug deep to understand this particular behavior and I think I have found a good explanation.

Before I get in to why you are not able to alias document.getElementById, I will try to explain how JavaScript functions/objects work.

Whenever you invoke a JavaScript function, the JavaScript interpreter determines a scope and passes it to the function.

Consider following function:

function sum(a, b)
{
    return a + b;
}

sum(10, 20); // returns 30;

This function is declared in the Window scope and when you invoke it the value of this inside the sum function will be the global Window object.

For the 'sum' function, it doesn't matter what the value of 'this' is as it is not using it.


Consider following function:

function Person(birthDate)
{
    this.birthDate = birthDate;    
    this.getAge = function() { return new Date().getFullYear() - this.birthDate.getFullYear(); };
}

var dave = new Person(new Date(1909, 1, 1)); 
dave.getAge(); //returns 100.

When you call dave.getAge function, the JavaScript interpreter sees that you are calling getAge function on the dave object, so it sets this to dave and calls the getAge function. getAge() will correctly return 100.


You may know that in JavaScript you can specify the scope using the apply method. Let's try that.

var dave = new Person(new Date(1909, 1, 1)); //Age 100 in 2009
var bob = new Person(new Date(1809, 1, 1)); //Age 200 in 2009

dave.getAge.apply(bob); //returns 200.

In the above line, instead of letting JavaScript decide the scope, you are passing the scope manually as the bob object. getAge will now return 200 even though you 'thought' you called getAge on the dave object.


What's the point of all of the above? Functions are 'loosely' attached to your JavaScript objects. E.g. you can do

var dave = new Person(new Date(1909, 1, 1));
var bob = new Person(new Date(1809, 1, 1));

bob.getAge = function() { return -1; };

bob.getAge(); //returns -1
dave.getAge(); //returns 100

Let's take the next step.

var dave = new Person(new Date(1909, 1, 1));
var ageMethod = dave.getAge;

dave.getAge(); //returns 100;
ageMethod(); //returns ?????

ageMethod execution throws an error! What happened?

If you read my above points carefully, you would note that dave.getAge method was called with dave as this object whereas JavaScript could not determine the 'scope' for ageMethod execution. So it passed global 'Window' as 'this'. Now as window doesn't have a birthDate property, ageMethod execution will fail.

How to fix this? Simple,

ageMethod.apply(dave); //returns 100.

Did all of the above make sense? If it does, then you will be able to explain why you are not able to alias document.getElementById:

var $ = document.getElementById;

$('someElement'); 

$ is called with window as this and if getElementById implementation is expecting this to be document, it will fail.

Again to fix this, you can do

$.apply(document, ['someElement']);

So why does it work in Internet Explorer?

I don't know the internal implementation of getElementById in IE, but a comment in jQuery source (inArray method implementation) says that in IE, window == document. If that's the case, then aliasing document.getElementById should work in IE.

To illustrate this further, I have created an elaborate example. Have a look at the Person function below.

function Person(birthDate)
{
    var self = this;

    this.birthDate = birthDate;

    this.getAge = function()
    {
        //Let's make sure that getAge method was invoked 
        //with an object which was constructed from our Person function.
        if(this.constructor == Person)
            return new Date().getFullYear() - this.birthDate.getFullYear();
        else
            return -1;
    };

    //Smarter version of getAge function, it will always refer to the object
    //it was created with.
    this.getAgeSmarter = function()
    {
        return self.getAge();
    };

    //Smartest version of getAge function.
    //It will try to use the most appropriate scope.
    this.getAgeSmartest = function()
    {
        var scope = this.constructor == Person ? this : self;
        return scope.getAge();
    };

}

For the Person function above, here's how the various getAge methods will behave.

Let's create two objects using Person function.

var yogi = new Person(new Date(1909, 1,1)); //Age is 100
var anotherYogi = new Person(new Date(1809, 1, 1)); //Age is 200

console.log(yogi.getAge()); //Output: 100.

Straight forward, getAge method gets yogi object as this and outputs 100.


var ageAlias = yogi.getAge;
console.log(ageAlias()); //Output: -1

JavaScript interepreter sets window object as this and our getAge method will return -1.


console.log(ageAlias.apply(yogi)); //Output: 100

If we set the correct scope, you can use ageAlias method.


console.log(ageAlias.apply(anotherYogi)); //Output: 200

If we pass in some other person object, it will still calculate age correctly.

var ageSmarterAlias = yogi.getAgeSmarter;    
console.log(ageSmarterAlias()); //Output: 100

The ageSmarter function captured the original this object so now you don't have to worry about supplying correct scope.


console.log(ageSmarterAlias.apply(anotherYogi)); //Output: 100 !!!

The problem with ageSmarter is that you can never set the scope to some other object.


var ageSmartestAlias = yogi.getAgeSmartest;
console.log(ageSmartestAlias()); //Output: 100
console.log(ageSmartestAlias.apply(document)); //Output: 100

The ageSmartest function will use the original scope if an invalid scope is supplied.


console.log(ageSmartestAlias.apply(anotherYogi)); //Output: 200

You will still be able to pass another Person object to getAgeSmartest. :)

6
  • 7
    +1 for why IE works. The rest is a bit off-topic but still helpful in general. :)
    – Kev
    Commented Jul 29, 2009 at 15:10
  • 44
    Excellent answer. I don't see how any of it is off topic though. Commented Dec 18, 2009 at 0:43
  • Very nice answer, indeed. A somewhat shorter version is written here. Commented Aug 30, 2010 at 23:00
  • 9
    One of the best answers I've seen on stackoverflow.
    – warbaker
    Commented Jun 30, 2011 at 20:40
  • why does it work in IE? because: stackoverflow.com/questions/6994139/… — so this method implementation may in fact be { return window[id] }, so it would work in any context Commented Feb 11, 2015 at 14:11
43

You have to bind that method to the document object. Look:

>>> $ = document.getElementById
getElementById()
>>> $('bn_home')
[Exception... "Cannot modify properties of a WrappedNative" ... anonymous :: line 72 data: no]
>>> $.call(document, 'bn_home')
<body id="bn_home" onload="init();">

When you’re doing a simple alias, the function is called on the global object, not on the document object. Use a technique called closures to fix this:

function makeAlias(object, name) {
    var fn = object ? object[name] : null;
    if (typeof fn == 'undefined') return function () {}
    return function () {
        return fn.apply(object, arguments)
    }
}
$ = makeAlias(document, 'getElementById');

>>> $('bn_home')
<body id="bn_home" onload="init();">

This way you don’t loose the reference to the original object.

In 2012, there is the new bind method from ES5 that allows us to do this in a fancier way:

>>> $ = document.getElementById.bind(document)
>>> $('bn_home')
<body id="bn_home" onload="init();">
2
  • 4
    Which is really a wrapper because it returns a new function. I think the answer to Kev's question is that it is not possible for the reasons you describe above. The method you describe here is probably the best option.
    – jiggy
    Commented Jun 17, 2009 at 14:56
  • Thanks for your comment, I hadn't looked closely enough to realize that. So, the syntax in the answers on the other question is completely bogus? Interesting...
    – Kev
    Commented Jun 17, 2009 at 16:16
3

This is a short answer.

The following makes a copy of (a reference to) the function. The problem is that now the function is on the window object when it was designed to live on the document object.

window.myAlias = document.getElementById

The alternatives are

  • to use a wrapper (already mentioned by Fabien Ménager)
  • or you can use two aliases.

    window.d = document // A renamed reference to the object
    window.d.myAlias = window.d.getElementById
    
2

Another short answer, just for wrapping/aliasing console.log and similar logging methods. They all expect to be in the console context.

This is usable when wrapping console.log with some fallbacks, in case you or your users have run into trouble when using a browser that doesn't (always) support it. This is not a full solution to that problem though, as it needs to be expanded checks and a fallback - your mileage may vary.

Example using warnings

var warn = function(){ console.warn.apply(console, arguments); }

Then use it as usual

warn("I need to debug a number and an object", 9999, { "user" : "Joel" });

If you prefer to see your logging arguments wrapped in an array (I do, most of the time), substitute .apply(...) with .call(...).

Should work with console.log(), console.debug(), console.info(), console.warn(), console.error(). See also console on MDN.

1

In addition to other great answers, there's simple jQuery method $.proxy.

You can alias like this:

myAlias = $.proxy(document, 'getElementById');

Or

myAlias = $.proxy(document.getElementById, document);
1
  • +1 because it's a feasible solution. But, strictly speaking, behind the scenes $.proxy is also a wrapper really.
    – pimvdb
    Commented Aug 17, 2012 at 9:33
-6

You actually can't "pure alias" a function on a predefined object if it internally references this in a way that's incompatible with your invocation-by-alias. Therefore, the closest to aliasing you can get without wrapping is by staying within the same object:

>>> document.s = document.getElementById;
>>> document.s('myid');
<div id="myid">
2
  • 5
    This is slightly incorrect. You can 'pure alias' a function provided that the function implementation's use of 'this'. So it depends. Commented Jul 29, 2009 at 17:10
  • Sorry, I meant in the context of getElementById. You're right. I've changed the answer slightly to reflect this...
    – Kev
    Commented Aug 4, 2009 at 17:52

Not the answer you're looking for? Browse other questions tagged or ask your own question.