Top-level Array generics didn’t make it into EcmaScript. Here’s how to hand-roll them.
const myFilter = Function.prototype.call.bind(Array.prototype.filter);
Code language: JavaScript (javascript)
And that gives is a function that can be called as:—
const isNotUpper = e=>e>"Z"; // lexicographic comparison
myFilter("asdAxE", isNotUpper).join(""); // "asdx"
Code language: JavaScript (javascript)
How does this filter
“magic” work? Let’s look step-by-step with another example, Array.prototype.forEach
. But first, let’s get some “basics” out of the way.
Function.prototype.call
Function.prototype.call
calls the function it’s called on, passing its first argument as the this
value to that function.
In the following example, window.prompt
is called three times, passing the value of each character, followed by the index in which it occurs, followed by the this value (a String object).
[].forEach.call("asd", prompt);
Code language: JavaScript (javascript)
The interpreter will execute the above as the following:
thisArg = new String("asd");
prompt("a", 0, thisArg);
prompt("s", 1, thisArg);
prompt("d", 2, thisArg);
Code language: JavaScript (javascript)
(Function window.prompt
ignores the last argument.)
We can do likewise with console.log
, which prints any number of arguments.
[].forEach.call("asd", console.log);
Code language: JavaScript (javascript)
In each call, the arguments passed are (1) the value at each index, (2) the index, and (3) the String object (promoted from a string value), used as the thisArg
.
Function.prototype.call.call
We can further abstract the forEach
call with:—
Function.prototype.call.call (func, thisArg, ...args )
Code language: JavaScript (javascript)
— as:—
Function.prototype.call.
call([].forEach, "asd", console.log)
Code language: JavaScript (javascript)
— resulting:—
In the above code, the this
value to call
is [].forEach
, the this
value to forEach
is "asd"
, promoted to a String object, and the callback function, the first argument to forEach
, is console.log
.
Function.prototype.call.bind
If the second call
method is replaced with call to Function.prototype.bind
, forEach
will be bound as the this
value to a function from call
as:—
let arrayForEach = Function.prototype.call.bind([].forEach);
Code language: PHP (php)
The steps by which this new function is created are a bit tricky, but it essentially creates a new function with a `[[BoundThis]]
value assigned to the first argument (promoted to an object) (See: 10.4.1.3 BoundFunctionCreate ( targetFunction, boundThis, boundArgs )).
The forEach
method can now be called generically, without call
.
arrayForEach("asd", console.log)
Code language: JavaScript (javascript)
The bound function arrayForEach
is passed with "asd"
, which is promoted to a string object with length=3
, and used as the this
arg for [].forEach
. Function [].forEach
is called with, console.log
three times, such as:
console.log(thisArg[i], i, thisArg)
Code language: JavaScript (javascript)
We can reuse this top-level Array.prototype.forEach
:
const arrayForEach =
Function.prototype.call.bind([].forEach);
arrayForEach("asd", console.log)
arrayForEach("qwe", console.log)
Code language: JavaScript (javascript)
Function.prototype.call Shortcut
Just as Array.prototype.forEach
is found on every array instance such as [].forEach
, so too is Function.prototype.call
found on every function instance, such as (function(){}).call
.
Base Object to Call and Arrow Functions
Arrow functions get their this value from the lexical environment and Bound functions have a bound thisArg
(more on this later). This:—
(e=>e).call("foo")
Code language: JavaScript (javascript)
— results undefined
For our intent, this doesn’t matter. We can still use call a layer of abstraction out, as call.call
. We don’t access the Base object that far back. We can also use other functions or the built-in function constructor function, Function
.
(function(){return this}).call({})
Code language: JavaScript (javascript)
— which returns the object argument, {}
.
Calling of forEach
is stored by binding forEach
to the call
method:
let boundForEach = Function.prototype.call.bind([].forEach);
Code language: JavaScript (javascript)
This can be later called with any thisArg
and any callback.
boundForEach("asd", prompt);
Code language: JavaScript (javascript)
Bound Method for Array.prototype.filter
Back to the problem at hand, we want to invoke Array.prototype.filter
when it’s called and with the arguments passed in:
const myFilter = Function.prototype.call.bind(Array.prototype.filter);
Code language: JavaScript (javascript)
And that gives is a function that can be called as:—
const isNotUpper = e=>e>"Z"; // lexicographic comparison
myFilter("asXd", isNotUpper);
Code language: JavaScript (javascript)
— results:—
['a', 's', 'd']
Code language: JavaScript (javascript)
Function myFilter
is called with array-like object to act as the this
value for the actual function call to Array.prototype.filter
.Array.prototype.filter
promotes its thisArg
to a String object and calls the second parameter, isNotUpper
(with that String as the thisArg
).
Just as we get Array.prototype.forEach
with [].forEach
, so too can we get Function.prototype.call
from any function (except arrow or bound functions).
Function.call
is the same Function.prototype.call
, but gets it this
arg a Function
, the base object, if called as Function.call()
. As mentioned, we’re not calling it like that, rather, we’re using the value of that function as the Base object for bind
.
From: https://leetcode.com/problems/filter-elements-from-array/discuss/5024144/Function.prototype.call.bind