If I want get an array of strings converted to lowercase, this seems like the normal thing to do:
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've dug a little bit in the code and it seems that the problem is produced by the createCallback
wrapping the passed function used inside 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 really understand what's going on there...
I know that it works if I pass a callback like this:
function(x) {return x.toLowerCase()}
but the curiosity is killing me...
The same reason ['A', 'B'].map(String.prototype.toLowerCase.call)
is not working — it effectively uses Function.prototype.call.call(thisArg, currentValue)
as an iterator which throws TypeError: object is not a function
because your thisArg
is global context (process
) instead of String.prototype.toLowerCase
.
// You expect this to be your iterator:
String.prototype.toLowerCase.call('A');
// Instead, you got this:
String.prototype.toLowerCase.call.call(thisArg, 'A');
Since thisArg
is bound to process
in your example, line above is pretty much the same as:
process()
with this
bound to 'A'
.
// The "correct" invocation "should" be:
String.prototype.toLowerCase.call.call(String.prototype.toLowerCase, 'A');
You can fix it by passing "correct" thisArg
. The following ones work, but such map
s do not look better than 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));
When you pass String.prototype.toLowerCase.call
as the iterator function, you're really just passing the call
function, without a context.
The context of the call
function is usually the function which should be called. In this case it seems like the context is set to some object (global object?), and therefore you get the error object is not a function
.
A possible solution is to bind the context of Function.call
to String.prototype.toLowerCase
, like so:
_.map(['A', 'B'], Function.call.bind(String.prototype.toLowerCase));
Or a bit shorter:
_.map(['A', 'B'], Function.call.bind("".toLowerCase));
After some more testing I found that the following code works with underscore but not with Lo-Dash, at least in the browser:
_.map(['A', 'B'], Function.call, "".toLowerCase);