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

+3


source to share


2 answers


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());

      

+4


source


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);

      

+3


source







All Articles