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

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP


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) 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).
– Xufox
17 mins ago




.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.

Popular posts from this blog

Makefile test if variable is not empty

Will Oldham

Visual Studio Code: How to configure includePath for better IntelliSense results