Why does JavaScript's `function.call` have to be called explicitly?
Why 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 thecall
method 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