第7章 操作字符串
字符串是有限字符的序列,主要包括字母、数字、特殊字符(如空格等),在程序设计中,它也是经常使用的一种数据类型。在JavaScript中,字符串只有一种类型,没有字符、字符串或文本等子类型。
字符串操作常用在表单处理、HTML文本解析、异步响应文本解析中,与正则表达式配合使用,以提升字符串操作的灵活性。字符串操作包括字符匹配、查找、替换、截取编码/解码、连接等。本章将详细讲解各种String方法的使用和应用技巧。
【学习重点】
▲ 定义字符串
▲ 字符串查找、连接和截取
▲ 编辑字符串
▲ 比较字符串
▲ 字符串检测
▲ 字符串加解密
7.1 定义字符串
定义字符串有多种方式,如字符串直接量、构造字符串和使用字符编码设计字符串,下面分别进行介绍。
7.1.1 字符串直接量
定义字符串直接量的方法很简单,使用双引号或单引号包含字符文本即可。例如:
【示例1】任何字符被引号包含,都会被转换为字符串类型数据。
由于一些字符包含双重或多重语义,把它们包含在字符串中,会破坏字符串的值,甚至破坏字符串的类型,因此需要转义特殊字符,避免产生歧义。
【示例2】单引号和双引号配合使用,以应对特殊形式的字符串需要。
var s =“alert(‘Hello,World’)”;
可以这样写:
var s=‘alert(“Hello,World”)’;
单引号可以包含双引号,或者双引号包含单引号。但是不能够在单引号中包含单引号,或者在双引号中包含双引号。下面写法都是错误的:
【示例3】如果要表示引号字符,需要使用转义序列来表示。
【示例4】对于字符串类型的语句或表达式,可以调用静态方法eval()来执行。
7.1.2 构造字符串
String是JavaScript内置对象,调用String()构造函数可以构造字符串,该函数可以接收一个参数,并把它作为初始值来初始化字符串。
【示例1】下面的代码使用了new运算符调用String()构造函数,将创建一个字符串型对象。
注意:通过String构造函数构造的字符串与字符串直接量的类型是不同的。前者为引用型对象,后者为值类型的字符串。
【示例2】下面的代码比较了构造字符串和字符串直接量的数据类型的不同。
从上面示例可以看到,String构造函数实际上是字符串的包装类,利用它可以把值类型字符串包装为引用型对象,以适应各种字符串的特殊处理。
【示例3】String()也可以作为普通函数使用,把参数转换为字符串类型的值返回,此时它就不是构造函数了。
【示例4】String()可以带有多个参数,但是它仅处理第一个参数,并把它转换为字符串返回。
String构造函数也可以附带多个参数,它仅负责构造第一个参数,并返回它的字符串。但是,所附带的多个参数是会被JavaScript执行计算的。
【示例5】下面的变量n在构造函数内经过多次计算之后,最后值递增为5。如下:
7.1.3 使用字符编码
String对象预定义了fromCharCode()方法,该方法能够根据字符编码创建字符串,这对于希望通过数字动态生成字符串来说,是非常有用的。
fromCharCode()方法可以包含多个整数参数,每个参数代表字符的Unicode编码。返回值含有指定编码的字符的新字符串。
【示例】下面的代码演示了如何将一组字符串编码转换为字符串。
可以把所有字符串按顺序传递给fromCharCode()方法。
var b=String.fromCharCode(35835, 32773, 24744, 22909) ; //传递多个参数,返回字符串"读者您好"
可以使用Function的apply()方法动态调用数组参数:
fromCharCode()方法是String对象的静态方法,不能在字符串中直接调用。
提示:fromCharCode()方法可以与String对象的charCodeAt()配合使用,执行相反操作。charCodeAt()可以把字符串转换为编码,而fromCharCode()方法能够把编码转换为字符串。
7.2 使用字符串
String对象预定义了很多方法,利用这些方法可以灵活处理字符串。字符串操作主要包括:
☑ 查找字符,如是否包含数字、字母还是其他特定字符。
☑ 查找字符串长度和字符格式,如数组、日期等。
☑ 字符串基本操作,如截取子字符串、插入字符、删除字符、置换字符、字符串比较、字符串连接和拆分等。
☑ 字符串高级操作,如模糊匹配、字符加密等。
7.2.1 案例:计算字符串长度
String对象预定义了length属性,该属性存储着当前字符串的长度。它可以显示字符串的字符个数。
【示例1】下面的代码使用了字符串的length属性获取字符串的字符长度。
字符包括单字节、双字节两种类型,为了精确计算字符串的字节长度,可以采用以下两个方法。
【示例2】利用循环结构枚举每个字符,并根据字符的字符编码,判断当前字符是单字节还是双字节,然后递加字符串的字节数。
测试:
【拓展】在检测字符是否为双字节或单字节时,方法也是有多种的,这里再提供两种思路:
或者使用正则表达式进行字符编码验证:
【示例3】利用正则表达式把字符串中双字节字符临时替换为两个字符,然后调用length属性获取临时字符串的长度:
上述方法比较简洁,但是执行效率相对要慢,因为它需要两次遍历字符串,即调用replace()方法一次,使用length属性时一次。而第一种方法中只进行一次字符串遍历。
注意:String对象的length属性是只读属性,这与Array的length属性不同(即数组的长度是可以动态改变的)。不过,又与数组一样,字符串可以使用下标来定位单个字符在字符串中的位置,其中第一个字符的下标值为0,最后一个字符的下标值为length-1。但是字符串中的字符是不能够被for/in循环枚举的。运算符delete也不能删除字符串中指定位置的字符。
7.2.2 案例:连接字符串
为一个字符串添加字符串的方法也很多。最简单的是使用加号运算符,把多个字符串或字符连接在一起。
【示例1】下面的代码使用了加号运算符连接两个字符串。
String对象定义了concat()方法,该方法可以把多个参数添加到指定字符串的尾部。不过,在实际开发中,直接使用加号运算符执行字符串连接操作通常更简便一些。
【示例2】下面的代码使用了字符串的concat()方法将多个字符串连接在一起。
concat()方法的参数是没有限制的,参数类型也没有限制。如果需要,在执行连接操作时,它会把所有参数都转换为字符串,然后按顺序连接到当前字符串的尾部,最后返回连接后的字符串。当然,concat()方法不会修改原字符串的值。这与数组的concat()非常相似,操作也一样。
7.2.3 案例:查找字符串
检索字符串、查找特定字符串是字符串操作中的基本技能,实际开发中大量工作也是与此有关的,如表单验证、查找指定字符等。为此,String对象定义了很多功能强大的方法,详细说明如表7-1所示。
表7-1 String对象的查找字符串方法
1.获取指定位置的字符
使用String的charAt()和charCodeAt()方法,可以根据参数返回指定下标位置的字符或字符的编码。
【示例1】使用charAt()把字符串中每个字符都装入一个数组中,从而可以为String对象扩展一个原型方法,用来把字符串转换为数组。
然后就可以实现对字符串的所有字符进行遍历。
对于charAt()方法来说,字符串中第一个字符的下标值为0。如果参数n不在0和length-1之间,则返回空字符串。
字符实际上也是字符串,即长度为1的字符串。JavaScript没有定义字符类型的数据,而在其他强类型语言中,是把字符和字符串分为两种不同类型的数据。
charCodeAt()与charAt()方法操作一样,不过它返回的是指定位置的字符编码。如果指定下标值为负数,或者大于等于字符串的长度,则该方法将返回NaN,而不再是0。
2.查找子字符串的位置
charAt()和charCodeAt()方法是根据下标查找字符,而indexOf()和lastIndexOf()方法则是根据指定字符串查找它的下标位置。
indexOf()方法有两个参数,第一个参数为一个子字符串,是指将要查找的对象。第二个参数为一个整数值,用来指定查找的起始位置,其取值范围是0~length-1,对于该参数来说:
☑ 如果值为负数,则视为0,就相当于从第一个字符开始查找。
☑ 如果省略了这个参数,也将从字符串的第一个字符开始查找。
☑ 如果值大于等于length属性值,则视为当前字符串中没有指定的子字符串,即返回-1。不过在JavaScript 1.0和JavaScript 1.1中会返回一个空字符串(这是一个Bug)。
【示例2】下面的代码查询了字符串中首个字母a的下标位置。
indexOf()方法只返回查找到的第一个子字符串的起始下标值,如果没有找到将返回-1。
【示例3】下面的代码查询了URL字符串中首个字母w的下标位置。
如果要想查找下一个子字符串,则可以使用第二个参数来限定范围。
【示例4】下面的代码分别查询了URL字符串中两个点号字符的下标位置。
注意:indexOf()方法是按着从左到右的顺序进行查找的。如果希望从右到左来进行查找,则可以使用lastIndexOf()方法来查找。
【示例5】下面的代码以从后往前的方式查询URL字符串中最后一个点号字符的下标位置。
提示:使用lastIndexOf()方法时,需要注意下面几个问题。
(1)lastIndexOf()方法的查找顺序是从右到左,但是其参数和返回值都是根据字符串的下标从左到右的顺序来计算的,而不是反着来计算字符串的下标值。即字符串的左侧第一个字符下标值始终都是0,而最后一个字符的下标值始终都是length-1。
(2)第二个参数指定起始查找的下标位置,这个起始是指指定下标位置左侧开始查找,因为它是按从右到左的顺序执行的。例如:
var s=“http://www.mysite.cn/index.html”;
var n=s.lastIndexOf("." , 11); //返回值为10,而不是15
其中第二个参数值11表示字符c(第一个)的下标位置,然后从其左侧开始向左查找,所以就返回第一个点号的位置。
(3)如果查找到,则返回第一次查找的子字符串的起始下标值,这个起始位置是最左侧的意思,而不是最右侧位置。例如:
var s=“http://www.mysite.cn/index.html”;
var n=s.lastIndexOf(“www”); //返回值为7,而不是10
如果第二个参数出现没有传递,或为负值,或大于等于length属性值等情况,则将遵循indexOf()方法来计算。
3.匹配字符串
search()和match()方法能够查找子字符串。由于它们都与正则表达式紧密联系,所以只有掌握正则表达式的使用之后,才能够灵活使用它们。
search()方法的功能与indexOf()方法相似,都是查找子字符串第一次出现的下标位置。但是它仅有一个参数,即指定的匹配模式,也没有lastIndexOf()的反向检索的功能,同时它也不支持全局模式。
【示例6】下面的代码使用了search()方法匹配斜杠字符在URL字符串的下标位置。
提示:使用search()方法时需要注意以下几个问题。
(1)该方法的参数为正则表达式,即声明要匹配的RegExp对象。如果该参数不是RegExp对象,则JavaScript会使用RegExp()构造函数把它转换成RegExp对象。
(2)search()方法遵循从左到右的查找顺序,并返回第一个匹配的子字符串的起始下标值。如果没有找到,则返回-1。
(3)该方法没有第二个参数,它无法查找指定的范围,换句话说它始终返回的是第一个匹配子字符串的下标值。从这点上考虑,它没有indexOf()灵活和实用。
【示例7】与search()方法相比,match()方法要强大很多,它能够找出所有匹配的子字符串,并存储在一个数组中返回。
【拓展】match()方法的用法也比较灵活,由于需要一定的正则表达式基础,下面介绍该方法应该注意的问题。
(1)match()方法返回的是一个数组,但是它的行为受正则表达式的匹配模式限制,如果匹配模式没有附带全局匹配修饰符g,那么match()方法只能执行一次匹配。例如,下面的匹配模式中没有g修饰符,只能够执行一次匹配,返回仅有一个元素h的数组。
(2)如果没有找到匹配字符,则返回null,而不是空数组。
(3)当不执行全局匹配(即附带有g修饰符)时,如果在匹配模式中包含有子表达式,则返回的数组中存放着找到的匹配文本相关的信息。
【示例8】下面的代码使用了match()方法匹配URL字符串中所有点号字符。
在这个正则表达式“/(.).(.).(.)/”中,左右两个斜杠是匹配模式分隔符,JavaScript解释器能够根据这两个分隔符来识别正则表达式。在正则表达式中小括号表示子表达式,每个子表达式匹配的文本信息多会被独立存储,以备调用。反斜杠表示转义序列,因为点号在正则表达式中表示匹配任意字符,星号表示前面的匹配字符可以匹配任意多次。
在上面示例中,数组a并非仅有一个元素,而包含4个元素,且每个元素存储着不同的信息。其中第一个元素存放的是匹配文本,其余的元素存放的是与正则表达式的子表达式匹配的文本。
另外,这个数组还包含两个对象属性,其中index属性存储匹配文本的起始字符在字符串中的位置,input属性存储对匹配字符串的引用。
(4)在全局匹配模式下,即附带有g修饰符。match()将执行全局匹配。此时返回的数组的内容与非全局匹配完全相同,它的数组元素存放的是字符串中所有匹配子串,该数组没有index属性和input属性。同时不再提供子表达式匹配的文本信息,也不声明每个匹配子串的位置。如果需要这些全局检索的信息,可以使用RegExp.exec()方法。
7.2.4 案例:截取子字符串
在字符串操作中,截取子字符串与查找子字符串一样都很重要。如果说查找是定位,那么截取才是操作的核心。JavaScript提供了3个字符串截取方法,如表7-2所示。
表7-2 String对象的截取子字符串方法
1.根据长度截取子字符串
substr()方法能够根据指定长度来截取子字符串。它可以包含两个参数,第一个参数表示准备截取的子串的起始下标,第二个参数表示截取的长度。
【示例1】在本示例中使用lastIndexOf()获取字符串的最后一个点号的下标位置,然后从其后的位置开始截取4个字符:
提示:使用substr()方法时,需要注意两个问题。
(1)如果省略第二个参数,则表示截取从起始位置开始到结尾的所有字符。考虑到扩展名的长度不固定,省略第二个参数会更灵活:
var b=s.substr(s.lastIndexOf(".")+1);
(2)如果第一个参数为负值,则表示从字符串的尾部开始计算下标位置,即-1表示最后一个字符,-2表示倒数第二个字符,依此类推。这对于左侧字符长度不固定时非常有用。
ECMAScript没有标准化该方法,所以被列为不建议选用的方法,建议使用slice()和substring()方法。
2.根据起止下标截取子字符串
slice()和substring()方法都是根据指定的起止下标位置来截取子字符串,也就是说,它们都可以包含两个参数,第一个参数表示起始下标,第二个参数表示结束下标。不过slice()方法显得更加灵活。
【示例2】下面的代码使用了substring()方法截取URL字符串中网站主机名信息。
提示:使用时请注意两个问题。
(1)截取子字符串包含第一个参数所指定的字符。结束点不被截取,即不包含在子字符串中。
(2)第二个参数如果省略,表示截取到结尾的所有字符串。
但是slice()和substring()方法也存在很多不同点,具体比较如下:
区别一,如果第一个参数值比第二个参数值大,substring()方法能够在执行截取之前,先交换两个参数,而对于slice()方法来说则被视为无效,并返回空字符串。
【示例3】下面的代码比较了substring()方法和slice()方法其用法的不同。
这对于起始点和结束点的值无法确定时是有效的,因为substring()方法能够自动对参数进行调整。
区别二,如果参数值为负值,则slice()方法能够把负号解释为从右侧开始定位,这与Array的slice()方法相同。但是substring()方法会视其为无效,并返回空字符串。
【示例4】下面的代码进一步比较了substring()方法和slice()方法用法的不同。
7.2.5 案例:编辑字符串
字符串编辑操作主要包括替换子字符串和字符串大小写转换,具体方法如表7-3所示。
表7-3 String对象的编辑字符串方法
1.替换子字符串
replace()方法包含两个参数,第一个参数表示执行匹配的正则表达式,第二个参数表示准备代替匹配的子字符串。
【示例1】下面的代码使用了replace()方法将字符串中的“html”修改为“htm”。