25

I have an array of strings. I want to trim each string in the array.

I thought using [].map() with ''.trim() would work...

[' a', ' b   ', 'c'].map(String.prototype.trim);

...but my console said...

TypeError: String.prototype.trim called on null or undefined

jsFiddle.

I can't see any null or undefined values in my array.

String.prototype.trim() and Array.prototype.map() are defined in Chrome 17, which I'm using to test.

Why doesn't this work? I get the feeling I have overlooked something obvious.

I realise I could loop or drop a function in there. That's not the point of this question, however.

alex
  • 479,566
  • 201
  • 878
  • 984

4 Answers4

35

What @Slace says is the right explanation. @ThomasEding's answer also works but has one terrible inefficieny that it may create functions within a loop, which is not a good thing to do.

Another way of doing would be (reference here):

[' a', ' b   ', 'c'].map(Function.prototype.call, String.prototype.trim);  
// gives ["a", "b", "c"]

Standard browser disclaimer: This will work wherever Function.prototype.call and String.prototype.trim will work and for older browsers, you can easily substitute trim with a polyfill like this:

if(!String.prototype.trim) {  
  String.prototype.trim = function () {  
    return this.replace(/^\s+|\s+$/g,'');  
  };  
}

Update: Interstingly, while this works fastest in Chrome, @ThomasEding's method runs slightly faster in IE10 and FF20 - http://jsperf.com/native-trim-vs-regex-trim-vs-mixed

Community
  • 1
  • 1
Mrchief
  • 75,126
  • 20
  • 142
  • 189
  • 1
    I'm pretty sure that @ThomasEdig's answer does not create more than one function. I'm assuming the `map` function accepts a function reference and then *calls it* once per iteration. The function itself is created once and then *passed to* the `map` function--just like jQuery's implementation of `map`: http://james.padolsey.com/jquery/#v=1.7.2&fn=jQuery.map – Andrew Whitaker Apr 25 '13 at 15:49
  • Yeah... From my tests, that appears to be true. I'll do the corrections. – Mrchief Apr 25 '13 at 15:52
25

That's because trim is not being called with the proper this context. Remember that this is dynamically bound in JS. You will have to create a wrapper to pass to trim to properly bind this:

[' a', ' b   ', 'c'].map(function (str) {
  return str.trim();
});
Thomas Eding
  • 35,312
  • 13
  • 75
  • 106
  • 2
    Thanks for answering. I think I've been using jQuery's `$.trim()` too often. – alex Feb 16 '12 at 01:05
  • This is almost perfect except for the fact that you're creating functions in a loop. – Mrchief Apr 24 '13 at 16:25
  • 2
    @Mrchief: The above code does not do that. It would if you took my existing code and tossed it within a loop... – Thomas Eding Apr 24 '13 at 17:19
  • You're already in a loop when you're iterating thru the array. :) How do you think map is implemented? http://es5.github.io/#x15.4.4.19 – Mrchief Apr 24 '13 at 18:03
  • 4
    @Mrchief: That is one way to implement map. Another is via recursion. In either case, how map is implemented is moot. The function is created only once in this case. Then a pointer to the function is passed into map, and map uses the same reference as it does its work. – Thomas Eding Apr 24 '13 at 19:08
  • Good point! Not sure about the last part though. Its upto the browser how it optimizes. – Mrchief Apr 24 '13 at 19:15
20

trim is on the String prototype, meaning that it expects the this context to be that of a string where as the map method on Array provides the current array item as the first argument and the this context being the global object.

Aaron Powell
  • 24,927
  • 18
  • 98
  • 150
  • Exactly! I knew it would be something obvious. My brain isn't working today. Thanks Slace. – alex Feb 16 '12 at 00:59
  • 3
    So why does this not work? `[' a', ' b ', 'c'].map(String.prototype.trim.apply);` ... and why does this work outside the `map()` context? `String.prototype.trim(' a ')` (note that it doesn't actually *work* - returns an empty string - but it doesn't throw an error either.) – nrabinowitz Feb 16 '12 at 01:09
  • 5
    @nrabinowitz: `apply`'s `this` doesn't get bound to `String.prototype.trim` - it gets bound to `undefined` or `null` or `window`. (This is why they invented `Function.bind()`.) – Ry- Feb 16 '12 at 01:11
  • I was a bit slow with getting Ryan's comment at first, but I found [this](https://nemisj.com/some-interesting-aspects-of-call-method-in-javascript/) helpful. – Chris Sep 23 '16 at 18:15
3

With ES6 syntax it can be as easy as this:

[' hello  ', '  world'].map(str => str.trim());
mahdavipanah
  • 600
  • 5
  • 21