0

I have a string, " test ". It's a really ugly string, so let's trim it.

" test ".trim() returns "test". Nice!

Now let's try to do it with that string as an argument.

String.prototype.trim.call(" test ") also returns "test". Nice again!

Oh, that means I can use String.prototype.trim.call to map an array of ugly strings by their trimmed counterparts, right?

[" test "].map(String.prototype.trim.call) does not return ["test"].

It throws TypeError: undefined is not a function.

Why is this not allowed?

Grant Gryczan
  • 473
  • 7
  • 21
  • 1
    `.map(String.prototype.trim.call)` passes the `call` method without the context of `String.prototype.trim`. Try either just a normal arrow function or something similar to `String.prototype.trim.bind(String.prototype)`. – Sebastian Simon Jul 30 '18 at 00:44

1 Answers1

1

All function call methods are the same function value:

> String.prototype.trim.call === Function.prototype.call
true

> String.prototype.trim.call === alert.call
true

The function to be called is passed to Function.prototype.call as its this value. The this value for a function call varies depending on how the call is made:

f();             // calls f with this === undefined
x.f();           // calls x.f with this === x
x['f']();        // same as previous
f.call(y);       // calls f with this === y
f.apply(y, []);  // same as previous

When referencing a function in all cases that aren’t calls, there is no this value involved.

const f = x.f;   // no association with x is maintained
x();             // calls f with this === undefined

So, when you pass String.prototype.trim.call to Array#map, you’re passing the function Function.prototype.call, with no relationship to String.prototype.trim. Array#map then calls it with the thisArg given as the second argument (undefined, since you only passed one argument). Function.prototype.call calls the this value it was given, as it does, and fails because it can’t call undefined.

The code is fixable by passing the correct value of this as thisArg:

["  test "].map(String.prototype.trim.call, String.prototype.trim)

Pretty verbose, huh? You can abuse the fact that all methods from prototypes are equal to make it shorter (Set being a built-in function with a short name):

["  test "].map(Set.call, ''.trim)

but even that’s no shorter than the usual, readable way:

["  test "].map(x => x.trim())

which has the bonus of not forwarding unexpected arguments.

Ry-
  • 199,309
  • 51
  • 404
  • 420