2

If I have an array of Strings that represent numbers, and want to convert the type of every entry in the array to Number, I can use map:

var stringNumbers = ["11", "23", "5813"];
stringNumbers.map(parseFloat);
// [11, 23, 5813]

This works because parseFloat is globally accessible. However, if I want the result of an object's methods, I seem to need to use an anonymous function.

var Dog = function(name) {
  this.name = name;
};

Dog.prototype.getName = function() {
  return this.name;
};

var dogs = [new Dog("Beatrice"), new Dog("Baxter")];

dogs.map(function(dog) {
  return dog.getName();
});
// ["Beatrice", "Baxter"]

Ideally, I would be able to do something like:

dogs.map(getName); // ["Beatrice", "Baxter"]

But this does not work because getName is not globally accessible.

Is there a way to bind each function executed by map to the context of the object it is iterating over?

drewblaisdell
  • 609
  • 5
  • 16
  • `dogs.map(Function.prototype.call.bind(Dog.prototype.getName))` - uh. Use the function expression for your own sanity :-) – Bergi Jul 13 '14 at 22:51
  • It is possible but SUPER UGLY. Youd be better of with just having a standalone getDogName(dog) function. – MightyPork Jul 13 '14 at 22:53

2 Answers2

4

It technically IS possible as

var r = dogs.map(Function.prototype.call.bind(Dog.prototype.getName));

JSFiddle: http://jsfiddle.net/VCvp7/

Explanation:

Function.prototype.call.bind(Dog.prototype.getName)

returns a function reference that is equal to Dog.prototype.getName.call and that expects the first parameter as a context (the this value to use during the call to getName), which is what map() passes the exact Dog instance to.

PS: sorry, cannot explain it better. If you don't get it - just spend couple more minutes to figure it out.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
zerkms
  • 249,484
  • 69
  • 436
  • 539
  • 1
    That. Is. Absolutely. Brilliant. I'm not saying I'd *use* it. But... – T.J. Crowder Jul 13 '14 at 22:53
  • 1
    Brilliant, maybe. Very similar to http://stackoverflow.com/a/21186656/2180189 ? Certainly. Useful & readable? NOPE. Why not just make a helper function for getting a dog name? – MightyPork Jul 13 '14 at 22:55
  • @MightyPork: OP didn't know if it is even possible. And if it is - OP didn't know if it's readable. So I'm sure OP will finally choose an explicit anonymous function instead. I agree it's a duplicate though. – zerkms Jul 13 '14 at 22:56
  • 1
    What it the function was defined in the Dog constructor? Is there still an option then? http://jsfiddle.net/NAw79/ – Andrew Shepherd Jul 13 '14 at 22:57
  • I don't think it is. Both use the same underlying mechanism, but I don't think the question duplicates that question. – T.J. Crowder Jul 13 '14 at 22:57
  • 1
    @AndrewShepherd: **Excellent** question. I think the answer then is what my now-deleted answer was: Nope, sorry, can't do it. You have to use a function (anonymous or otherwise). – T.J. Crowder Jul 13 '14 at 22:58
  • 1
    @Andrew Shepherd: not an option anymore. There is no a shared function to bind to. – zerkms Jul 13 '14 at 22:58
1

If I had the underscore library in my project, I would use invoke:

// Use _.invoke to call method getName() on each dog in dogs
var dogNamesInvoke = _.invoke(dogs, 'getName');
// ["Beatrice", "Baxter"]

But also realize that there is a second argument you can pass to map that is thisArg.

// Use Array.map but pass the function name 'getName' as the 2nd parameter to map
var dogNamesMap = dogs.map(function(value) {
    var functionName = this;
    return value[functionName].apply(value);
}, 'getName');
// ["Beatrice", "Baxter"]

Here is a link to a Plunker that shows your original code along with these two other options.

Jeremy Zerr
  • 874
  • 7
  • 12