JS Array.prototype.filter on prototype method

2020-03-24 03:10发布

Is there an easier way to call filter on a prototype method without an anonymous function?

I wonder if there is an equivalent to myArray.filter(function(it){ it.method() }).

This looks close to what might work (it doesn't):

function X() {}
X.prototype.method = function() { console.log(this); }

[new X(), new X()].filter(X.prototype.method.call);

Instead I get a TypeError in both latest Firefox and Chrome, it's because it doesn't quite do what I want:

x = function() { console.log(this) }
x.call(123) //logs 123
y = x.call //reports that y is of type function in console
y(123) //TypeError: Function.prototype.call called on incompatible undefined
y.call(x, 123); //this is what you really need

I tried using bind, maybe I'm missing it, but if it's not a one-liner, it's not any better than the anonymous method form:

function X() {}
X.prototype.method = function() { console.log(this); }

y = X.prototype.method.call
y.bind(X.prototype.method)

[new X(), new X()].filter(y);

标签: javascript
1条回答
劳资没心,怎么记你
2楼-- · 2020-03-24 03:54

Let's set up some variables:

var method = X.prototype.method,
    array = [new X(), new X()];    

Your attempt can now be written as:

array.filter(method.call);

The problem is that call is getting called but with no this. It needs a this of method. method.call is precisely the same as the raw Function.prototype.call, with no binding to any this. Merely saying method.call does not give you a version of call bound to method. To arrange for call to be bound to the right this, namely method, you need to, well, bind it:

array.filter(method.call.bind(method));

Walking through this:

  1. method.call.bind(method) returns a new version of Function#call which is bound to X#method; think of it as method.call(waiting), which is waiting to be called with a value which will call X#method against a particular instance of X.

  2. Array#filter passes each argument in the array to that bound version of Function#call, which results in method.call(elt, remaining_args...), which is the equivalent of elt.method(remaining_args...).

Output:

> array.filter(method.call.bind(method));
  X {method: function}
  X {method: function}

Some sugar

One could make this more semantic and readable with a little wrapper, which we will call thisify:

function thisify(fn) { return fn.call.bind(fn); }

array.filter(thisify(method));

Using the context parameter to filter

You could use the little-used context parameter of filter and its brethren (except reduce), essentially, letting filter do the binding for you, if you choose to look at it that way, since

Array#filter(fn, context) === Array#filter(fn.bind(context))

So we can just write:

array.filter(method.call, method);

That actually looks cleaner to me. I doubt if it could get much simpler than that.

查看更多
登录 后发表回答