0

Observe the following:

function array_map(array, callback) {
    for (var i = 0; i < array.length; i += 1) {
        callback(array[i]);
    }
}
var a = [], b = [];

array_map([1, 2, 3], function (x) { a.push(x); });
// just gives a = [1, 2, 3] as expected

// but why does this not work: ?
array_map([1, 2, 3], b.push);
// Chrome: a = [], Firefox: can't convert undefined to object

I do understand why this happens, namely: push is no longer bound to b (but to the global object) if you pass it to array_map directly. I don't really understand why Chrome doesn't give an error, at least Firefox seems to give some kind of error.

How can I detect if a function like this is passed to array_map to avoid these kinds of bugs?

I'm hoping there are advanced reflection techniques available to trace the origin of a function. For instance b.push.constructor gives Function, but that's not what I'm looking for.

1
  • 1
    You can't. You can control it using bind when you're calling the function, but inside the function, you can't. Commented Aug 20, 2012 at 13:00

3 Answers 3

2

I'm not sure what you expect there to happen. Array.prototype.map requires a function as second parameter, which returns a new value for every iteration.

Passing in just a function reference (which you do in your second example) doesn't tell the function what it has to do anyway. So you're kinda expecting that .map() applies some black magic and calls the passed in method with the correct parameter, which it obviously, can't do.

I totally didn't get that you wrote your own mapping function. However, your problem there is that you're losing scope of that .push() function. Only if you call it on the Array / Object like xxx.push(), the this within the called function will correctly reference the target object. Once you just passed the reference, this will either point to global / window or undefined and won't work anymore.

So solve that issue you could call it like

array_map([1, 2, 3], b.push.bind(b));

which also would apply an ES5 function. You can't really detect for it within the array_map(). A function is a function, your best shot would be to detect whether or not the passed in method is a native or not, but I wouldn't recommend that.

Sign up to request clarification or add additional context in comments.

4 Comments

He's using [].push, not map.
I understand that there are workarounds to avoid this, like using bind or a closure. But this code passes unattested in Chrome, if there is any easy way to catch it: I would like to.
You mean: do a string conversion on the function and check for the occurance of "native code"? I agree that it's not a good idea ..
@FritsvanCampen: Pretty much that and no, that's not a good idea. Anyway, I don't think its a good idea in general to check for that kind of stuff within that array.map function. Its totally the wrong place. Actually, there shouldn't be any check.
1

Usually these sorts of functions would allow you to set the context of the callback.

If you change your array_map function to accept a context, then it will work like this.

function array_map(array, callback, context) {
    for (var i = 0; i < array.length; i += 1) {
        callback.call(context, array[i]);
    }
}
var b = [];

// now the push method is called from the b context
array_map([1, 2, 3], b.push, b);

Comments

1

The problem is, that Javascript has no such thing like "methods" - a function exclusively bound to a certain object. In your first example you pass a function which invokes a.push. this is bound to a here because you invoke it directly on a.

In your second code you just pass the function push without the context of b - this will be bound to the execution context which is the gloabl object.

You need to bind the the context of the function like the following:

array_map([1, 2, 3], b.push.bind(b));

or jQuery's proxy(). I can't find another simple solution at the moment but the one to hand over the context directly in a third parameter: function array_map(array, callback, context)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.