ThinkBonobo
ThinkBonobo

Reputation: 16515

Javascript Array.prototype.map function with instance methods

I was wondering if it was possible to pass an instance method into the Array.prototype.map function.

for example, I basically want this functionality without having to make a separate function

function Node(n) {
  this.val = n;
}
Node.prototype.isBig = function() {
  return this.val > 5;
}

var myArray = [
  new Node(1), new Node(2), new Node(3),
  new Node(7), new Node(8), new Node(9)
];

console.log(myArray);

var result = myArray.map(function(node) {
  return node.isBig();
});

console.log(result);

in java I would be able to do .map(Node::isBig). Can I do something to change the map parameter so that I don't have to create a function?

I am also open to solutions with lodash if you cannot do it with vanilla js.

Upvotes: 2

Views: 1356

Answers (2)

Ruan Mendes
Ruan Mendes

Reputation: 92274

You'd have to create a function that takes node as its parameter (that is, it doesn't rely on this

Node.isBig = function(node) {
  return node.isBig();
};

//works because function doesn't depend on this
myArray.map(Node.isBig);
// doesn't work, depends on this
myArray.map(Node.prototype.isBig);

Alternatively, you could create a function that returns another function that takes its first parameter and calls it as this

 function passFirstArgAsThis (fun) {
     return function() {
         fun.apply(arguments[0], arguments);
     }
 }
 myArray.map( passFirstArgAsThis(Node.prototype.isBig));

A slightly more cryptic implementation is

function passFirstArgAsThis(fn) {
    return function() {
        return fn.call.apply(fn, arguments);  
    };
}

The equivalent in ES6 is more readable

function passFirstArgAsThis(fn) {
    return function() {
        // arguments[0] will be the first argument passed to call
        // map will pass it as the node, we will make it become `this`
        // when calling the passed in function
        return fn.call(...arguments); 
    };
}

Understanding that, Oriol's answer becomes easier to comprehend

function passFirstArgAsThis(fn) {
    // we return a bound function that will execute the call method, bound
    // to the same passed in function 
    return fn.call.bind(fn);
    // Same as 
    // return function() {
    //    return fn.call(...arguments);
    // }
}

Personally, I would use fat arrow functions for readability, and not needing an extra "static" function.

nodes.map(n=>n.isBig())

Upvotes: 1

Oriol
Oriol

Reputation: 288020

I don't think you can do it strictly without creating new functions, but you can avoid function expressions and declarations by binding existing functions to some value.

For example, you can use Function.prototype.call to call Node.prototype.isBig with the appropriate this value.

var result = myArray.map(Function.prototype.call.bind(Node.prototype.isBig));

There is also a proposal of a bind operator, which I think it would allow

var result = myArray.map(::Node.prototype.isBig.call);

Note additional arguments (index and array) will be passed to the isBig method.

function Node(n) {
  this.val = n;
}
Node.prototype.isBig = function() {
  return this.val > 5;
};
var myArray = [
  new Node(1), new Node(2), new Node(3),
  new Node(7), new Node(8), new Node(9)
];
var result = myArray.map(Function.prototype.call.bind(Node.prototype.isBig));
document.write(JSON.stringify(result));

And in ES6, you can consider using arrow functions:

var result = myArray.map(node => node.isBig());

Upvotes: 4

Related Questions