Showing posts with label hack. Show all posts
Showing posts with label hack. Show all posts

April 30, 2007

eval() bug in IE

eval('function(){}') evaluates to undefined in IE, the same for eval('(function(){})').

How to get the Function?

eval('[function(){}][0]')

IE, you always let me down!

February 6, 2007

Closure, eval and Function

eval() evaluates a string of JavaScript code. The Function constructor can be used to create a function from string. Someone says that the Function constructor is another form of eval(). However, one significant difference between eval() and the Function constructor is that while eval() keeps the lexical scope, the Function constructor always creates top-level functions.

function f1() {
var bbb = 2;
eval('alert(bbb);');
}
f1(); //alerts 2

function f2() {
var bbb = 2;
new Function('alert(bbb)')();
}
f2(); //bbb undefined error

function f3() {
var bbb = 2;
eval('function() {alert(bbb);}')();
}
f3(); //alerts 2

eval() inside a function body creates a closure while new Function() doesn't. This difference may not bother you for the whole lifetime. However, it happens to bother me once. It's about jQuery - a new type of JavaScript library. I'm using jQuery in my bookmarklet application. In order to make the code as unobtrusive as possible, I decided to put all my code including the jQuery code inside an anonymous function. It looks like this:

(function() {
//jQuery code
//my code
})();

In this way, even the jQuery object is just a local variable. The outside environment is completely unaffected. But $().parents(), $().children, $().prev(), $().next() and $().siblings() always fail in my code. These functions are created by the Function constructor in $.grep() and $.map().

// If a string is passed in for the function, make a function
// for it (a handy shortcut)
if ( typeof fn == "string" )
fn = new Function("a","i","return " + fn);

So they are all top-level and the identifier "jQuery" inside is resolved as window.jQuery which is undefined and the code fails.


We can implement an alternative to the Function constructor and use it within the lexical scope:

var createFunc = (function () {
var args = [].slice.call(arguments);
var body = args.pop();
return eval('function(' + args.join(',') + ') {' + body + '}');
}).toString();

function f4() {
var bbb = 2;
eval(createFunc)('alert(bbb);')();
}
f4(); //alerts 2
You can use eval(createFunc) just like new Function(), but you get the bonus lexical scope binding.
function f6() {
var add = function(a, b) {return a + b;};
return eval(createFunc)('x', 'y', 'return add(x, y);');
}
f6()(3, 5); //8

At last, I quote Douglas Crockford's words on eval() and the Function constructor


"eval is Evil


The eval function is the most misused feature of JavaScript. Avoid it.


eval has aliases. Do not use the Function constructor. Do not pass strings to setTimeout or setInterval. "

January 19, 2007

Converting the arguments object to an Array

The arguments object is an Array-like object providing access to arguments passed to a function. When hacking with functions, we frequently need to manipulate and pass around arguments as an Array. The Prototype library uses it's $A() function to accomplish the conversion, which is intuitive and beautiful.

Function.prototype.bind = function() {
var __method = this, args = $A(arguments), object = args.shift();
return function() {
return __method.apply(object, args.concat($A(arguments)));
}
}

Yet there is a another way.

//... inside a function definition
var args = [].slice.call(arguments);

// or var args = Array.prototype.slice.call(arguments);
//...

The slice(start, end) method of an Array object returns a copy of a specified portion of the array. Here we omit the start and end arguments and call slice() upon the arguments object. An array containing all arguments is returned. Then we can modify the array as we need and pass it to the apply() method of functions.


Notice that slice() only does a shallow copy and the return value is just an array any without magical behavior of the arguments object. There is no args.callee property. Moreover, if a parameter is listed at the nth position in the parameter list of the function definition, arguments[n] is a synonym for the local variable corresponding to the nth argument. Any change made to arguments[n] will affect the local variable because it's actually modifying the named property of the call object. However, since args is just a shallow copy of arguments, assigning args[n] will not affect the local variable. To demonstrate this,


function f (a) {
    alert('a: ' + a);
    var args = [].slice.call(arguments);
    arguments[0] = 'Assigning arguments[0] affects a';
    alert('a: ' + a);
    args[0] = 'Assigning args[0] does not change a';
    alert('a: ' + a);
}
f('JavaScript rocks!');


The three alerts will display "a: JavaScript rocks", "a: Assigning arguments[0] affects a" and "a: Assigning arguments[0] affects a" in order.


Tip: [].slice.call() can be used for any Array like objects, not only limited to the arguments object.