第二章:值 - Strings
String
一个很常见的想法是,string
实质上只是字符的 array
。虽然内部的实现可能是也可能不是 array
,但重要的是要理解 JavaScript 的 string
与字符的 array
确实不一样。它们的相似性几乎只是表面上的。
举个例子,让我们考虑这两个值:
var a = "foo";
var b = ["f","o","o"];
String 确实与 array
有很肤浅的相似性 — 也就是上面说的,类 array
— 举例来说,它们都有一个 length
属性,一个 indexOf(..)
方法(在 ES5 中仅有 array
版本),和一个 concat(..)
方法:
a.length; // 3
b.length; // 3
a.indexOf( "o" ); // 1
b.indexOf( "o" ); // 1
var c = a.concat( "bar" ); // "foobar"
var d = b.concat( ["b","a","r"] ); // ["f","o","o","b","a","r"]
a === c; // false
b === d; // false
a; // "foo"
b; // ["f","o","o"]
那么,它们基本上都仅仅是“字符的数组”,对吧? 不确切:
a[1] = "O";
b[1] = "O";
a; // "foo"
b; // ["f","O","o"]
JavaScript 的 string
是不可变的,而 array
是相当可变的。另外,在 JavaScript 中用位置访问字符的 a[1]
形式不总是广泛合法的。老版本的 IE 就不允许这种语法(但是它们现在允许了)。相反,正确的 方式是 a.charAt(1)
。
string
不可变性的进一步的后果是,string
上没有一个方法是可以原地修改它的内容的,而是创建并返回一个新的 string
。与之相对的是,许多改变 array
内容的方法实际上 是 原地修改的。
c = a.toUpperCase();
a === c; // false
a; // "foo"
c; // "FOO"
b.push( "!" );
b; // ["f","O","o","!"]
另外,许多 array
方法在处理 string
时非常有用,虽然这些方法不属于 string
,但我们可以对我们的 string
“借用”非变化的 array
方法:
a.join; // undefined
a.map; // undefined
var c = Array.prototype.join.call( a, "-" );
var d = Array.prototype.map.call( a, function(v){
return v.toUpperCase() + ".";
} ).join( "" );
c; // "f-o-o"
d; // "F.O.O."
让我们来看另一个例子:翻转一个 string
(顺带一提,这是一个 JavaScript 面试中常见的细节问题!)。array
拥有一个原地的 reverse()
修改器方法,但是 string
没有:
a.reverse; // undefined
b.reverse(); // ["!","o","O","f"]
b; // ["!","o","O","f"]
不幸的是,这种“借用” array
修改器不起作用,因为 string
是不可变的,因此它不能被原地修改:
Array.prototype.reverse.call( a );
// 仍然返回一个“foo”的 String 对象包装器(见第三章) :(
另一种迂回的做法(也是黑科技)是,将 string
转换为一个 array
,实施我们想做的操作,然后将它转回 string
。
var c = a
// 将 `a` 切分成一个字符的数组
.split( "" )
// 翻转字符的数组
.reverse()
// 将字符的数组连接回一个字符串
.join( "" );
c; // "oof"
如果你觉得这很难看,没错。不管怎样,对于简单的 string
它 好用,所以如果你需要某些快速但是“脏”的东西,像这样的方式经常能满足你。
警告: 小心!这种方法对含有复杂(unicode)字符(星型字符、多字节字符等)的 string
不起作用。你需要支持 unicode 的更精巧的工具库来准确地处理这种操作。在这个问题上可以咨询 Mathias Bynens 的作品:Esrever(https://github.com/mathiasbynens/esrever)。
另外一种考虑这个问题的方式是:如果你更经常地将你的“string”基本上作为 字符的数组 来执行一些任务的话,也许就将它们作为 array
而不是作为 string
存储更好。你可能会因此省去很多每次都将 string
转换为 array
的麻烦。无论何时你确实需要 string
的表现形式的话,你总是可以调用 字符的 array
的 join("")
方法。