Strings

It’s a very common belief that strings are essentially just arrays of characters. While the implementation under the covers may or may not use arrays, it’s important to realize that JavaScript strings are really not the same as arrays of characters. The similarity is mostly just skin-deep.

For example, let’s consider these two values:

  1. var a = "foo";
  2. var b = ["f","o","o"];

Strings do have a shallow resemblance to arrays — array-likes, as above — for instance, both of them having a length property, an indexOf(..) method (array version only as of ES5), and a concat(..) method:

  1. a.length; // 3
  2. b.length; // 3
  3. a.indexOf( "o" ); // 1
  4. b.indexOf( "o" ); // 1
  5. var c = a.concat( "bar" ); // "foobar"
  6. var d = b.concat( ["b","a","r"] ); // ["f","o","o","b","a","r"]
  7. a === c; // false
  8. b === d; // false
  9. a; // "foo"
  10. b; // ["f","o","o"]

So, they’re both basically just “arrays of characters”, right? Not exactly:

  1. a[1] = "O";
  2. b[1] = "O";
  3. a; // "foo"
  4. b; // ["f","O","o"]

JavaScript strings are immutable, while arrays are quite mutable. Moreover, the a[1] character position access form was not always widely valid JavaScript. Older versions of IE did not allow that syntax (but now they do). Instead, the correct approach has been a.charAt(1).

A further consequence of immutable strings is that none of the string methods that alter its contents can modify in-place, but rather must create and return new strings. By contrast, many of the methods that change array contents actually do modify in-place.

  1. c = a.toUpperCase();
  2. a === c; // false
  3. a; // "foo"
  4. c; // "FOO"
  5. b.push( "!" );
  6. b; // ["f","O","o","!"]

Also, many of the array methods that could be helpful when dealing with strings are not actually available for them, but we can “borrow” non-mutation array methods against our string:

  1. a.join; // undefined
  2. a.map; // undefined
  3. var c = Array.prototype.join.call( a, "-" );
  4. var d = Array.prototype.map.call( a, function(v){
  5. return v.toUpperCase() + ".";
  6. } ).join( "" );
  7. c; // "f-o-o"
  8. d; // "F.O.O."

Let’s take another example: reversing a string (incidentally, a common JavaScript interview trivia question!). arrays have a reverse() in-place mutator method, but strings do not:

  1. a.reverse; // undefined
  2. b.reverse(); // ["!","o","O","f"]
  3. b; // ["!","o","O","f"]

Unfortunately, this “borrowing” doesn’t work with array mutators, because strings are immutable and thus can’t be modified in place:

  1. Array.prototype.reverse.call( a );
  2. // still returns a String object wrapper (see Chapter 3)
  3. // for "foo" :(

Another workaround (aka hack) is to convert the string into an array, perform the desired operation, then convert it back to a string.

  1. var c = a
  2. // split `a` into an array of characters
  3. .split( "" )
  4. // reverse the array of characters
  5. .reverse()
  6. // join the array of characters back to a string
  7. .join( "" );
  8. c; // "oof"

If that feels ugly, it is. Nevertheless, it works for simple strings, so if you need something quick-n-dirty, often such an approach gets the job done.

Warning: Be careful! This approach doesn’t work for strings with complex (unicode) characters in them (astral symbols, multibyte characters, etc.). You need more sophisticated library utilities that are unicode-aware for such operations to be handled accurately. Consult Mathias Bynens’ work on the subject: Esrever (https://github.com/mathiasbynens/esrever).

The other way to look at this is: if you are more commonly doing tasks on your “strings” that treat them as basically arrays of characters, perhaps it’s better to just actually store them as arrays rather than as strings. You’ll probably save yourself a lot of hassle of converting from string to array each time. You can always call join("") on the array of characters whenever you actually need the string representation.