1

I was looking at a question yesterday in which the poster was asking how to convert the case of an array's contents. I know that I can pass a reference to a function to map like:

function appendText (el){
    return el += ' - appended text';
}

['a','b'].map(appendText);  //["a - appended text", "b - appended text"]

But when I tried with

array.map(String.toUpperCase);
array.map(String.prototype.toUpperCase);

I get the error

Uncaught TypeError: String.prototype.toUpperCase called on null or undefined

Which makes it sound like the method isn't getting passed the element map would, as I understand it, be passing. Why doesn't this work?

Vikas Sardana
  • 1,593
  • 2
  • 18
  • 37
1252748
  • 14,597
  • 32
  • 109
  • 229
  • 3
    `appendText` expect an argument, `toUpperCase` doesn't as the string would be `this` so it does get the element from map but is not designed to use it. It's used as `'stuff'.toUpperCase()` not as `String.toUpperCase('stuff')` – GillesC Feb 03 '17 at 16:13
  • Try `array.map(s => s.toUpperCase())` – hsfzxjy Feb 03 '17 at 16:14
  • @hsfzxjy That will upper case an array of values, but that's not the question. – 1252748 Feb 03 '17 at 16:14
  • 1
    I explained why it doesn't work which answer your question. Which tbh I'm pretty sure has been asked in SO before. – GillesC Feb 03 '17 at 16:16
  • @GillesC Oh, of course. – 1252748 Feb 03 '17 at 16:16
  • @GillesC I think you're confused about who I addressed that to. Yes, you answered my question perfectly. – 1252748 Feb 03 '17 at 16:16
  • @1252748 GillesC points out the problem, and I just give a solution. – hsfzxjy Feb 03 '17 at 16:18
  • 1
    See @GillesC comment. You could use `bind()` to set the `this` value explicitly and then it will work: `array.map(String.prototype.toUpperCase.call.bind(String.prototype.toUpperCase));` – Alex Feb 03 '17 at 16:23
  • A shorter version of @Jaco's code would be `array.map(Function.call.bind("".toUpperCase))`, or make a reusable binding function, like this: `var binder = Function.bind.bind(Function.call)` and use it like this: `array.map(binder("".toUpperCase))` Arrow functions make this all much nicer. –  Feb 03 '17 at 16:38
  • @squint `Function.bind.bind(Function.call)`. That looks interesting. Can you explain what's happening there? – 1252748 Feb 03 '17 at 18:06
  • @1252748: Yes, it's creating a version of the `.bind()` method that has the `.call()` method bound to its `this` value. So when you invoke that resulting function with a function as its first arg, you're invoking `.bind()` with `.call()` as its `this`, and the arg as its first argument. Because it's an invocation of `.bind()`, it returns a new function that is the `.call()` method with the function arg bound as call's `this` value. So `binder("".toUpperCase)` returns `.call()` with `toUpperCase` bound as `this`. When invoking that function, because the `this` value is already bound... –  Feb 03 '17 at 18:31
  • ...any further args supplied will simply be passed in the argument positions. And because the `.call()` method *invokes* its `this` value, it will ultimately invoke `toUpperCase` with the given arguments. Confusing, but effective. –  Feb 03 '17 at 18:32
  • ...I left out the important point that because the first argument to `.call` sets the `this` value of the function it's calling, the first argument given will be the `this` value of `toUpperCase`. So `var uc = binder("".toUpperCase)` and then `uc("foo")` is basically `"".toUpperCase.call("foo")`, which is `"foo".toUpperCase()` –  Feb 03 '17 at 18:38

1 Answers1

1

You are not passing anything to the prototype / extension method. Simply use an arrow function to pass in the value of the array item.

To answer the logic behind the question, you simply can't pass the context-less (e.g. no this) prototype method (that is, from the base String class). map will run the function by name with the value as the first parameter (as shown with appendText(a))

function appendText (el){
    return el += ' - appended text'
}

var array = ['a','b']

console.log(array.map(appendText))
console.log(array.map(a => appendText(a)))
console.log(array.map(a => a.toUpperCase()))
G0dsquad
  • 4,230
  • 1
  • 17
  • 22
  • Please comment if you are going to downvote, as I feel this answers the question in terms of correct output and the reason why you can't pass in a `prototype` method. – G0dsquad Feb 03 '17 at 16:28
  • `Please comment if you are going to downvote, as I feel this answers your question` implies that it was me who downvoted you, which I did not. – 1252748 Feb 03 '17 at 16:30
  • Apologies if it wasn't, I just based this on your comment above to @hsfzxjy. Edited my comment to be clearer... – G0dsquad Feb 03 '17 at 16:32