Why does JavaScript's `function.call` have to be called explicitly?

Clash Royale CLAN TAG#URR8PPPWhy does JavaScript's `function.call` have to be called explicitly?
I have a string, " test ". It's a really ugly string, so let's trim it.
" test "
" test ".trim() returns "test". Nice!
" test ".trim()
"test"
Now let's try to do it with that string as an argument.
String.prototype.trim.call(" test ") also returns "test". Nice again!
String.prototype.trim.call(" test ")
"test"
Oh, that means I can use String.prototype.trim.call to map an array of ugly strings by their trimmed counterparts, right?
String.prototype.trim.call
[" test "].map(String.prototype.trim.call) does not return ["test"].
[" test "].map(String.prototype.trim.call)
["test"]
It throws TypeError: undefined is not a function.
TypeError: undefined is not a function
Why is this not allowed?
.map(String.prototype.trim.call)
call
String.prototype.trim
String.prototype.trim.bind(String.prototype)
1 Answer
1
All function call methods are the same function value:
call
> 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:
Function.prototype.call
this
this
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.
this
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.
String.prototype.trim.call
Array#map
Function.prototype.call
String.prototype.trim
Array#map
thisArg
undefined
Function.prototype.call
this
undefined
The code is fixable by passing the correct value of this as thisArg:
this
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):
Set
[" 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.
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
.map(String.prototype.trim.call)passes thecallmethod without the context ofString.prototype.trim. Try either just a normal arrow function or something similar toString.prototype.trim.bind(String.prototype).– Xufox
17 mins ago