views:

2522

answers:

5

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.

A: 

For a simple function like it, you can do :

window.myAlias = function(id){return document.getElementById(id);}
Fabien Ménager
That's a wrapper, which I wasn't interested in. Thanks, though.
Kev
+5  A: 

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.

Maciej Łebkowski
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
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
Actually, you should submit that as an answer...
Kev
A: 

You actually can't "pure alias" a function on a predefined object. 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">
Kev
This is slightly incorrect. You can 'pure alias' a function provided that the function implementation's use of 'this'. So it depends.
SolutionYogi
Sorry, I meant in the context of getElementById. You're right. I've changed the answer slightly to reflect this...
Kev
Oh Kev this really is OP lolzzzz
AamirAfridi.com
+53  A: 

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 JS functions/objects work.

Whenever you invoke a JS function, JS 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, JS interpreter sees that you are calling getAge function on the 'dave' object, so it sets 'this' to 'dave' and calls getAge function. getAge() will correctly return 100.


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

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

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

In the above line, instead of letting JS 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 JS 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 will throw an error! What happened?

If you read my above points carefully, you would note that dave.getAge method called with 'dave' as 'this' object whereas JS could not determine the 'scope' for ageMethod execution. So it passed global 'Window' as 'this'. Now as 'Window' doesn't have 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 was 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()); //Outputs = -1;

JS 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)); //Ouput = 100;

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


console.log(ageSmartestAlias.apply(anotherYogi)); //Ouput = 200;

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

SolutionYogi
+1 for why IE works. The rest is a bit off-topic but still helpful in general. :)
Kev
Excellent answer. I don't see how any of it is off topic though.
Justin Johnson
Very nice answer, indeed. A somewhat shorter version is written [here](http://stackoverflow.com/questions/585840/implement-map-in-javascript-that-supports-object-methods-as-mapped-functions/585918#585918).
Marcel Korpel
A: 

Never use window.console in your code directly (Wont work in ie etc)

create a function in global context.

function debug(msg)
{
    if(window.console && window.console.log) window.console.log(msg);
    else alert(msg);
}
AamirAfridi.com
Even better is to just strip `console.log` statements from production build.
kangax
What does this have to do with the OP?
Kev
Kev - Do you thing all the answers are related with OP ????
AamirAfridi.com
Yes, answers should answer the question (i.e., the OP/Original Post), not some other question that wasn't asked here...
Kev