The following is from an interactive session in the Chrome console:
myarray
//-> [67, 65, 84]
String.fromCharCode(67)
//-> "C"
String.fromCharCode(67).length
//-> 1
String.fromCharCode(myarray[0])
//-> "C"
String.fromCharCode(myarray[0]).length
//-> 1
myarray.map(String.fromCharCode)
//-> ["C", "A", "T"]
myarray.map(String.fromCharCode)[0]
//-> "C"
myarray.map(String.fromCharCode)[0].length
//-> 3
Can anyone explain why the last operation returns 3
?
The following is from an interactive session in the Chrome console:
myarray
//-> [67, 65, 84]
String.fromCharCode(67)
//-> "C"
String.fromCharCode(67).length
//-> 1
String.fromCharCode(myarray[0])
//-> "C"
String.fromCharCode(myarray[0]).length
//-> 1
myarray.map(String.fromCharCode)
//-> ["C", "A", "T"]
myarray.map(String.fromCharCode)[0]
//-> "C"
myarray.map(String.fromCharCode)[0].length
//-> 3
Can anyone explain why the last operation returns 3
?
- Firefox 4 also says "3" (fiddle) – Pointy Commented Apr 6, 2011 at 13:27
- @T.J. Crowder The presentation does lack some explanatory support but, though I was dubious at first, I'm hooked :-) – Pointy Commented Apr 6, 2011 at 13:28
3 Answers
Reset to default 17Very interesting question.
And the answer is: The reason you're getting 3
at the end is that fromCharCode
returns a string with as many characters as there are arguments in the call to it, and map
calls the callback function with three arguments: The value of the element, the index of the element, and the object being traversed. Thus, a string with three characters, but not three readily-displayed characters. Tricky.
Specifically, we get "C"
for the first argument; character 0 for the second argument (since we're looking at [0]
; it would be 1 for [1]
, etc.), and a character based on coercing the array into a number (using whatever rules fromCharCode
uses for that; Šime Vidas says below that it'll do ToUInt16
on it, which will result in 0
).
So, for instance:
> String.fromCharCode(67, 0, [67, 65, 84]).length
3
...just as with the call from map
.
For clarity:
> String.fromCharCode(67, 0, [67, 65, 84]).length
3
> String.fromCharCode(67, 0, null).length
3
> String.fromCharCode(67, 0, 0, 0, 0, 0, 0).length
7
If we ensure we only pass it the one argument at a time, we don't see that unexpected effect, so while using it directly gives us a result I think most of us wouldn't expect:
> [67, 65, 84].map(String.fromCharCode)
["C", "A", "T"]
> [67, 65, 84].map(String.fromCharCode)[0]
"C"
> [67, 65, 84].map(String.fromCharCode)[0].length
3
...this gives us a more easily-understood result:
> [67, 65, 84].map(function(value) { return String.fromCharCode(value); })
["C", "A", "T"]
> [67, 65, 84].map(function(value) { return String.fromCharCode(value); })[0]
"C"
> [67, 65, 84].map(function(value) { return String.fromCharCode(value); })[0].length
1
I can't expand much on T.J. Crowder's great answer, but I can provide a workaround for this specific problem.
Rather than
myarray.map(String.fromCharCode);
Use
String.fromCharCode.apply(String, myarray).split("");
Aside from the benefit of returning the expected result, this also works without relying on the ECMAScript 5th Edition method, Array.prototype.map
, thus making it more patible.
Ran into the same problem and found this thread. If you're into ES6, I propose this solution:
arr.map((c)=>String.fromCharCode(c));