1. 字符串的新增方法
1.1 String.fromCodePoint()
String.fromCharCode()
ES5 提供的方法:
作用:从 Unicode 码点返回对应字符,范围在0x0000~0xFFFF
。
局限:不能识别码点大于0xFFFF
的字符,大于0xFFFF
的数字将被截断。
语法:String.fromCharCode(num1[, ...[, numN]])
。
注意:该方法是一个静态方法,返回值是字符串,而不是一个String
对象。
String.fromCharCode(65, 66, 67); // "ABC" String.fromCharCode(0x20BB7); // "ஷ"
String.fromCodePoint()
ES6 中的方法:
作用:从 Unicode 码点返回对应字符。
局限:不能识别码点大于0xFFFF
的字符,大于0xFFFF
的数字将被截断。
语法:String.fromCodePoint(num1[, ...[, numN]])
。
注意:该方法是一个静态方法,返回值是字符串,而不是一个String
对象。如果传入无效的 Unicode 编码,将会抛出一个RangeError (例如: “RangeError: NaN is not a valid code point”)。
String.fromCodePoint(0x20BB7) // "𠮷" String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y' // true String.fromCodePoint('_'); // RangeError String.fromCodePoint(Infinity); // RangeError String.fromCodePoint(-1); // RangeError String.fromCodePoint(3.14); // RangeError String.fromCodePoint(3e-2); // RangeError String.fromCodePoint(NaN); // RangeError
1.2 String.raw()
作用:是一个模板字符串的标签函数,是用来获取一个模板字符串的原始字符串的。比如说,占位符(例如 ${foo})
会被处理为它所代表的其他字符串,而转义字符(例如 \n)
不会。
该方法返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,往往用于模板字符串的处理方法。
语法:String.raw(callSite, ...substitutions)
、String.raw
`templateString
`
参数:callSite
一个模板字符串的“调用点对象”。类似{ raw: [‘foo’, ‘bar’, ‘baz’] }。
参数:…substitutions
任意个可选的参数,表示任意个内插表达式对应的值。
参数:templateString
模板字符串,可包含占位符(${…})
注意:如果第一个参数没有传入一个格式正确的对象,则会抛出 TypeError 异常。
String.raw`Hi\n${2+3}!` // 实际返回 "Hi\\n5!",显示的是转义后的结果 "Hi\n5!" String.raw`Hi\u000A!`; // 实际返回 "Hi\\u000A!",显示的是转义后的结果 "Hi\u000A!"
如果原字符串的斜杠已经转义,那么String.raw()
会进行再次转义。
String.raw`Hi\\n` // 返回 "Hi\\\\n" String.raw`Hi\\n` === "Hi\\\\n" // true
1.3 实例方法:codePointAt()
作用:返回 一个 Unicode 编码点值的非负整数。
语法:str.codePointAt(index)
index
这个字符串中需要转码的元素的位置。
注意:返回值是在字符串中的给定索引的编码单元体现的数字,如果在索引处没找到元素则返回 undefined
。
'ABC'.codePointAt(1); // 66 '\uD800\uDC00'.codePointAt(0); // 65536 'XYZ'.codePointAt(42); // undefined
JavaScript 内部,字符以 UTF-16 的格式储存,每个字符固定为2
个字节。对于那些需要4
个字节储存的字符(Unicode 码点大于0xFFFF
的字符),JavaScript 会认为它们是两个字符。
var s = "𠮷"; s.length // 2 s.charAt(0) // '' s.charAt(1) // '' s.charCodeAt(0) // 55362 s.charCodeAt(1) // 57271
上面代码中,汉字“𠮷”(注意,这个字不是“吉祥”的“吉”)的码点是0x20BB7
,UTF-16 编码为0xD842 0xDFB7
(十进制为55362 57271
),需要4
个字节储存。对于这种4
个字节的字符,JavaScript 不能正确处理,字符串长度会误判为2
,而且charAt()
方法无法读取整个字符,charCodeAt()
方法只能分别返回前两个字节和后两个字节的值。
ES6 提供了codePointAt()
方法,能够正确处理 4 个字节储存的字符,返回一个字符的码点。
let s = '𠮷a'; s.codePointAt(0) // 134071 s.codePointAt(1) // 57271 s.codePointAt(2) // 97
codePointAt()
方法的参数,是字符在字符串中的位置(从 0 开始)。上面代码中,JavaScript 将“𠮷a”视为三个字符,codePointAt 方法在第一个字符上,正确地识别了“𠮷”,返回了它的十进制码点 134071(即十六进制的20BB7
)。
在第二个字符(即“𠮷”的后两个字节)和第三个字符“a”上,codePointAt()
方法的结果与charCodeAt()
方法相同。
总之,codePointAt()
方法会正确返回 32 位的 UTF-16 字符的码点。对于那些两个字节储存的常规字符,它的返回结果与charCodeAt()
方法相同。
codePointAt()
方法返回的是码点的十进制值,如果想要十六进制的值,可以使用toString()
方法转换一下。
let s = '𠮷a'; s.codePointAt(0).toString(16) // "20bb7" s.codePointAt(2).toString(16) // "61"
你可能注意到了,codePointAt()
方法的参数,仍然是不正确的。比如,上面代码中,字符a
在字符串s
的正确位置序号应该是 1,但是必须向codePointAt()
方法传入 2。解决这个问题的一个办法是使用for...of
循环,因为它会正确识别 32 位的 UTF-16 字符。
let s = '𠮷a'; for (let ch of s) { console.log(ch.codePointAt(0).toString(16)); } // 20bb7 // 61
另一种方法也可以,使用扩展运算符(...
)进行展开运算。
let arr = [...'𠮷a']; // arr.length === 2 arr.forEach( ch => console.log(ch.codePointAt(0).toString(16)) ); // 20bb7 // 61
codePointAt()
方法是测试一个字符由两个字节还是由四个字节组成的最简单方法。
function is32Bit(c) { return c.codePointAt(0) > 0xFFFF; } is32Bit("𠮷") // true is32Bit("a") // false
1.4 实例方法:normalize()
作用:按照指定的一种 Unicode 正规形式将当前字符串正规化。(如果该值不是字符串,则首先将其转换为一个字符串)。
语法:str.normalize([form])
form
可选
四种 Unicode 正规形式(Unicode Normalization Form)"NFC"
、"NFD"
、"NFKC"
,或 "NFKD"
其中的一个, 默认值为"NFC"
。
注意:如果给 form
传入了上述四个字符串以外的参数,则会抛出 RangeError 异常。
许多欧洲语言有语调符号和重音符号。为了表示它们,Unicode 提供了两种方法。一种是直接提供带重音符号的字符,比如
Ǒ
(\u01D1)。另一种是提供合成符号(combining character),即原字符与重音符号的合成,两个字符合成一个字符,比如O
(\u004F)和ˇ
(\u030C)合成Ǒ
(\u004F\u030C)。
这两种表示方法,在视觉和语义上都等价,但是 JavaScript 不能识别。
'\u01D1'==='\u004F\u030C' //false '\u01D1'.length // 1 '\u004F\u030C'.length // 2
上面代码表示,JavaScript 将合成字符视为两个字符,导致两种表示方法不相等。
ES6 提供字符串实例的
normalize()
方法,用来将字符的不同表示方法统一为同样的形式,这称为 Unicode 正规化。
'\u01D1'.normalize() === '\u004F\u030C'.normalize() // true
normalize
方法可以接受一个参数来指定normalize
的方式,参数的四个可选值如下。
normalize
方法可以接受一个参数来指定normalize
的方式,参数的四个可选值如下。
NFD
,表示“标准等价分解”(Normalization Form Canonical Decomposition),即在标准等价的前提下,返回合成字符分解的多个简单字符。NFKC
,表示“兼容等价合成”(Normalization Form Compatibility Composition),返回合成字符。所谓“兼容等价”指的是语义上存在等价,但视觉上不等价,比如“囍”和“喜喜”。(这只是用来举例,normalize
方法不能识别中文。)NFKD
,表示“兼容等价分解”(Normalization Form Compatibility Decomposition),即在兼容等价的前提下,返回合成字符分解的多个简单字符。
'\u004F\u030C'.normalize('NFC').length // 1 '\u004F\u030C'.normalize('NFD').length // 2
上面代码表示,NFC
参数返回字符的合成形式,NFD
参数返回字符的分解形式。
不过,
normalize
方法目前不能识别三个或三个以上字符的合成。这种情况下,还是只能使用正则表达式,通过 Unicode 编号区间判断。
1.5 实例方法:includes(), startsWith(), endsWith()
includes()
作用:用于判断一个字符串是否包含在另一个字符串中,根据情况返回 true
或 false
。
语法:str.includes(searchString[, position])
searchString
要在此字符串中搜索的字符串。
position
可选从当前字符串的哪个索引位置开始搜寻子字符串,默认值为
0
。
注意:includes()
方法是区分大小写的。例如,下面的表达式会返回 false
:
'Big Letter'.includes('big'); // 返回:false
startsWith()
作用:用来判断当前字符串是否以另外一个给定的子字符串开头,并根据判断结果返回 true
或 false
。
语法:str.startsWith(searchString[, position])
searchString
要搜索的子字符串。
position
可选在
str
中搜索searchString
的开始位置,默认值为0
。
注意:这个方法区分大小写。
const str1 = 'Saturday night plans'; console.log(str1.startsWith('Sat')); // true console.log(str1.startsWith('SAt')); // false
endsWith()
作用:用来判断当前字符串是否是以另外一个给定的子字符串“结尾”的,根据判断结果返回 true
或 false
。
语法:str.endsWith(searchString[, length])
searchString
要搜索的子字符串。
length
可选作为
str
的长度。默认值为str.length
。
注意:这个方法也是大小写区分敏感的。
const str1 = 'Cats are the best!'; console.log(str1.endsWith('best', 17)); // true console.log(str1.endsWith('Best', 17)); // false
1.6 实例方法:repeat()
作用:构造并返回一个新字符串,该字符串包含被连接在一起的指定数量的字符串的副本。表示将原字符串重复n
次。
语法:str.repeat(count)
count
介于 0
和 +Infinity
(正无穷)之间的整数。表示在新构造的字符串中重复了多少遍原字符串。
注意:重复次数不能为负数,重复次数必须小于 infinity,且长度不会大于最长的字符串,否则会报错。
"abc".repeat(-1) // RangeError: repeat count must be positive and less than inifinity "abc".repeat(0) // "" "abc".repeat(1) // "abc" "abc".repeat(2) // "abcabc" "abc".repeat(3.5) // "abcabcabc" 参数count将会被自动转换成整数. "abc".repeat(1/0) // RangeError: repeat count must be positive and less than inifinity
但是,如果参数是 0 到-1 之间的小数,则等同于 0,这是因为会先进行取整运算。0 到-1 之间的小数,取整以后等于-0
,repeat
视同为 0。
'na'.repeat(-0.9) // ""
参数NaN
等同于 0。
'na'.repeat(NaN) // ""
如果repeat
的参数是字符串,则会先转换成数字。
'na'.repeat('na') // "" 'na'.repeat('3') // "nanana"
1.7 实例方法:padStart(),padEnd()
padStart()
作用:用另一个字符串填充当前字符串(如果需要的话,会重复多次),以便产生的字符串达到给定的长度。从当前字符串的左侧开始填充。
语法:str.padEnd(targetLength [, padString])
targetLength
当前字符串需要填充到的目标长度。如果这个数值小于当前字符串的长度,则返回当前字符串本身。
padString
可选
填充字符串。如果字符串太长,使填充后的字符串长度超过了目标长度,则只保留最左侧的部分,其他部分会被截断。此参数的缺省值为 " "(U+0020)。
'abc'.padStart(10); // " abc" 'abc'.padStart(10, "foo"); // "foofoofabc" 'abc'.padStart(6,"123465"); // "123abc" 'abc'.padStart(8, "0"); // "00000abc" 'abc'.padStart(1); // "abc"
padEnd()
作用:会用一个字符串填充当前字符串(如果需要的话则重复填充),返回填充后达到指定长度的字符串。从当前字符串的末尾(右侧)开始填充。
语法:str.padEnd(targetLength [, padString])
targetLength
当前字符串需要填充到的目标长度。如果这个数值小于当前字符串的长度,则返回当前字符串本身。
padString
可选
填充字符串。如果字符串太长,使填充后的字符串长度超过了目标长度,则只保留最左侧的部分,其他部分会被截断。此参数的缺省值为 " "(U+0020)。
'abc'.padEnd(10); // "abc " 'abc'.padEnd(10, "foo"); // "abcfoofoof" 'abc'.padEnd(6, "123456"); // "abc123" 'abc'.padEnd(1); // "abc"
如果用来补全的字符串与原字符串,两者的长度之和超过了最大长度,则会截去超出位数的补全字符串。
'abc'.padStart(10, '0123456789') // '0123456abc'
如果省略第二个参数,默认使用空格补全长度。
'x'.padStart(4) // ' x' 'x'.padEnd(4) // 'x '
padStart()
的常见用途是为数值补全指定位数。下面代码生成 10 位的数值字符串。
'1'.padStart(10, '0') // "0000000001" '12'.padStart(10, '0') // "0000000012" '123456'.padStart(10, '0') // "0000123456"
另一个用途是提示字符串格式。
'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12" '09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"
1.8 实例方法:trimStart(),trimEnd()
trimStart()
作用:从字符串的开头删除空格。trimLeft()
是此方法的别名。
语法:str.trimStart()
或 str.trimLeft()
注意:trimStart()
/ trimLeft()
方法移除原字符串左端的连续空白符并返回一个新字符串,并不会直接修改原字符串本身。
var str = " foo "; console.log(str.length); // 8 str = str.trimStart() // 等同于 str = str.trimLeft(); console.log(str.length); // 5 console.log(str); // "foo "
trimEnd()
作用:从一个字符串的末端移除空白字符。trimRight()
是这个方法的别名。
语法:str.trimEnd()
或 str.trimRight()
注意:trimEnd()
/ trimRight()
方法移除原字符串右端的连续空白符并返回,trimEnd()
/ trimRight()
方法并不会直接修改原字符串本身。
var str = " foo "; alert(str.length); // 8 str = str.trimRight(); // 或写成str = str.trimEnd(); console.log(str.length); // 6 console.log(str); // ' foo'
除了空格键,这两个方法对字符串头部(或尾部)的 tab
键、换行符等不可见的空白符号也有效。
1.9 实例方法:matchAll()
作用:返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。
语法:str.matchAll(regexp)
regexp
正则表达式对象。如果所传参数不是一个正则表达式对象,则会隐式地使用
new RegExp(obj)
将其转换为一个RegExp
。
注意:RegExp
必须是设置了全局模式g
的形式,否则会抛出异常TypeError。
const regexp = /t(e)(st(\d?))/g; const str = 'test1test2'; const array = [...str.matchAll(regexp)]; console.log(array[0]); // expected output: Array ["test1", "e", "st1", "1"] console.log(array[1]); // expected output: Array ["test2", "e", "st2", "2"]
1.10 实例方法:replaceAll()
作用:返回一个新字符串,新字符串所有满足 pattern
的部分都已被replacement
替换。
pattern
可以是一个字符串或一个 RegExp
, replacement
可以是一个字符串或一个在每次匹配被调用的函数。原始字符串保持不变。
语法:const newStr = str.replaceAll(regexp|substr, newSubstr|function)
注意:当使用一个 regex
时,您必须设置全局(“ g”)标志,否则,它将引发 TypeError:“必须使用全局 RegExp 调用 replaceAll”。
'aabbcc'.replaceAll(/b/, '.'); TypeError: replaceAll must be called with a global RegExp 'aabbcc'.replaceAll(/b/g, '.'); "aa..cc"
原来的字符串的实例方法replace()
只能替换第一个匹配。
'aabbcc'.replace('b', '_') // 'aa_bcc'
上面例子中,replace()
只将第一个b
替换成了下划线。
如果要替换所有的匹配,不得不使用正则表达式的
g
修饰符。
'aabbcc'.replace(/b/g, '_') // 'aa__cc'
正则表达式毕竟不是那么方便和直观,ES2021 引入了replaceAll()
方法,可以一次性替换所有匹配。
'aabbcc'.replaceAll('b', '_') // 'aa__cc'
它的用法与replace()
相同,返回一个新字符串,不会改变原字符串。
String.prototype.replaceAll(searchValue, replacement)
上面代码中,searchValue
是搜索模式,可以是一个字符串,也可以是一个全局的正则表达式(带有g
修饰符)。
如果
searchValue
是一个不带有g
修饰符的正则表达式,replaceAll()
会报错。这一点跟replace()
不同。
// 不报错 'aabbcc'.replace(/b/, '_') // 报错 'aabbcc'.replaceAll(/b/, '_')
上面例子中,/b/
不带有g
修饰符,会导致replaceAll()
报错。
replaceAll()
的第二个参数replacement
是一个字符串,表示替换的文本,其中可以使用一些特殊字符串。
$&
:匹配的子字符串。$`
:匹配结果前面的文本。$'
:匹配结果后面的文本。$n
:匹配成功的第n
组内容,n
是从1开始的自然数。这个参数生效的前提是,第一个参数必须是正则表达式。$$
:指代美元符号$
。
下面是一些例子。
// $& 表示匹配的字符串,即`b`本身 // 所以返回结果与原字符串一致 'abbc'.replaceAll('b', '$&') // 'abbc' // $` 表示匹配结果之前的字符串 // 对于第一个`b`,$` 指代`a` // 对于第二个`b`,$` 指代`ab` 'abbc'.replaceAll('b', '$`') // 'aaabc' // $' 表示匹配结果之后的字符串 // 对于第一个`b`,$' 指代`bc` // 对于第二个`b`,$' 指代`c` 'abbc'.replaceAll('b', `$'`) // 'abccc' // $1 表示正则表达式的第一个组匹配,指代`ab` // $2 表示正则表达式的第二个组匹配,指代`bc` 'abbc'.replaceAll(/(ab)(bc)/g, '$2$1') // 'bcab' // $$ 指代 $ 'abc'.replaceAll('b', '$$') // 'a$c'
replaceAll()
的第二个参数replacement
除了为字符串,也可以是一个函数,该函数的返回值将替换掉第一个参数searchValue
匹配的文本。
'aabbcc'.replaceAll('b', () => '_') // 'aa__cc'
上面例子中,replaceAll()的第二个参数是一个函数,该函数的返回值会替换掉所有b的匹配。
这个替换函数可以接受多个参数。第一个参数是捕捉到的匹配内容,第二个参数捕捉到是组匹配(有多少个组匹配,就有多少个对应的参数)。此外,最后还可以添加两个参数,倒数第二个参数是捕捉到的内容在整个字符串中的位置,最后一个参数是原字符串。
const str = '123abc456'; const regex = /(\d+)([a-z]+)(\d+)/g; function replacer(match, p1, p2, p3, offset, string) { return [p1, p2, p3].join(' - '); } str.replaceAll(regex, replacer) // 123 - abc - 456
上面例子中,正则表达式有三个组匹配,所以replacer()
函数的第一个参数match
是捕捉到的匹配内容(即字符串123abc456
),后面三个参数p1
、p2
、p3
则依次为三个组匹配。