Why is `lodash.map (['A'], String.prototype.toLowerCase.call)` not working?
If I want to get an array of strings converted to lowercase, this looks like a normal thing:
lodash = require('lodash')
lodash.map(['A', 'B'], String.prototype.toLowerCase.call)
TypeError: object is not a function
at Function.map (/Users/alejandro.carrasco/repos/cap-proxy/node_modules/lodash/dist/lodash.js:3508:27)
at repl:1:9
at REPLServer.self.eval (repl.js:110:21)
at Interface.<anonymous> (repl.js:239:12)
at Interface.EventEmitter.emit (events.js:95:17)
at Interface._onLine (readline.js:202:10)
at Interface._line (readline.js:531:8)
at Interface._ttyWrite (readline.js:760:14)
at ReadStream.onkeypress (readline.js:99:10)
at ReadStream.EventEmitter.emit (events.js:98:17)
I dug around the code a bit and it seems that the problem occurs when the createCallback
passed function wrapper is used internally map
:
lodash.createCallback(String.prototype.toLowerCase.call)('A')
TypeError: object is not a function
at repl:1:58
at REPLServer.self.eval (repl.js:110:21)
at Interface.<anonymous> (repl.js:239:12)
at Interface.EventEmitter.emit (events.js:95:17)
at Interface._onLine (readline.js:202:10)
at Interface._line (readline.js:531:8)
at Interface._ttyWrite (readline.js:760:14)
at ReadStream.onkeypress (readline.js:99:10)
at ReadStream.EventEmitter.emit (events.js:98:17)
at emitKey (readline.js:1095:12)
But I don't quite understand what's going on there ...
I know it works if I pass the callback like this:
function(x) {return x.toLowerCase()}
but curiosity is killing me ...
source to share
Why
For the same reason ['A', 'B'].map(String.prototype.toLowerCase.call)
does not work - it effectively uses Function.prototype.call.call(thisArg, currentValue)
as an iterator that throws TypeError: object is not a function
because yours thisArg
is the global context ( process
) instead String.prototype.toLowerCase
.
how
// You expect this to be your iterator:
String.prototype.toLowerCase.call('A');
// Instead, you got this:
String.prototype.toLowerCase.call.call(thisArg, 'A');
Since it is thisArg
attached to process
in your example, the line above is almost the same as:
process()
with this
attached to 'A'
.
// The "correct" invocation "should" be:
String.prototype.toLowerCase.call.call(String.prototype.toLowerCase, 'A');
Fix
You can fix this by passing "correct" thisArg
. The following work, but map
they don't look better than they do function(x) {return x.toLowerCase()}
to me:
['A'].map(String.prototype.toLowerCase.call, String.prototype.toLowerCase);
['A'].map(String.prototype.toLowerCase.call.bind(String.prototype.toLowerCase));
['A'].map(Function.prototype.call, String.prototype.toLowerCase);
['A'].map(Function.prototype.call.bind(String.prototype.toLowerCase));
upd With ES2015 and transpilers , since it is quite mature nowadays, I would prefer the following:
['A'].map(letter => letter.toLowerCase());
source to share
When you pass String.prototype.toLowerCase.call
as an iterator function, you are really just passing the function call
without context.
The function context is call
usually the function to be called. In this case, it seems that the context is set to some object (global object?) And that's why you get the error object is not a function
.
A possible solution is to bind the context Function.call
to String.prototype.toLowerCase
, for example:
_.map(['A', 'B'], Function.call.bind(String.prototype.toLowerCase));
Or a little shorter:
_.map(['A', 'B'], Function.call.bind("".toLowerCase));
After some testing, I found that the following code works with underscore, but not Lo-Dash, at least in the browser:
_.map(['A', 'B'], Function.call, "".toLowerCase);
source to share