字符串和正则表达式
字符串和正则表达式
Javascript字符串一直基于16位字符编码(UTF-16)进行构建。每16位的序列是一个编码单元(code unit
),代表一个字符。length、charAt()等字符串属性和方法都基于这个编码单元构造的。
Unicode的目标是为世界上每一个字符提供全球唯一的标识符。如果我们把字符长度限制在16位,码位数量将不足以表示如些多的字符。“全球唯一的标识符”,又称作码位(code point),是从0开始的数值。而表示字符的这些数值或码位,我们称之为字符编码(character encode)。字符编码必须将码位编码为内部一致的编码单元。对于UTF-16来说,码位可以由多种编码单元表示。
更好的Unicode支持,在过去的16位足以包含任何字符(每16位的序列是一个编码单元,代表一个字符,在过去16足以包含任何字符),直到
Unicode
引入扩展字符集,编码规则才不得不进行变更。
UTF-16
前$2^{16}$个码位均以16位的编码单元表示,这个范围被称作基本多文种平面BMP(Basic Multilingual Plan)。超出这个范围的码位则要归属于某个辅助平面(supplementary plane) ,utf16引入了代理对,其规定用两个16位编码单元表示一个码位。这也就是说,字符串里的字符有两种,一种是由一个编码单元16
表示的BMP字符,另一种是由两个编码单元32
位表示的辅助平面字符 如 字符:‘𠮷’ (String.fromCodePoint(134071)
)
在ECMAScript 5
中,所有字符串的操作都基于16位编码单元。如果采用同样的的方式处理包含代理对的UTF-16
编码字符,得到的结果可能与预期不符
let text = "𠮷";
console.log(text.length); //2
console.log(/^.$/.test(text)); //false
console.log(text.charAt(0)); // ""
console.log(text.charAt(1)); // ""
console.log(text.charCodeAt(0)); // 55362
console.log(text.charCodeAt(1)); // 57271
- 变量text的长度事实上是1,但它的length属性却是2
- 变量text被判定为两个字符,因此匹配单一字符的正则表达式失效
- 前后两个16位的编码单元都不表示任何可打印的字符,因此
charAt()
方法不会返回合法字符串 charCodeAt()
同样不能正确地识别字符。它会返回每个16位编码单元对应的数值
codePointAt()方法
对于BMP字符集中的字符,codePointAt()
方法的返回值与chartCodeAt()
方法相同,而对于非BMP字符集来说返回值则不同。字符串‘𠮷a’第一个字符是非bmp的,包含两个编码单元,所以它的length属性值为3. ES6完全支持UTF-16
的codePointAt()
方法,这个方法接受编码单元的位置而非字符位置作为参数,返回与字符串中给定位置对应的码位,即一个整数值。
let text = '𠮷a'
console.log(text.length)
console.log(text.charCodeAt(0)) // 55362
console.log(text.charCodeAt(1)) // 57271
console.log(text.charCodeAt(2)) // 97
console.log(text.codePointAt(0)) // 134071
console.log(text.codePointAt(1)) // 57271
console.log(text.codePointAt(2)) // 97
在检测一个字符占用的编码单元,可以写如下的函数两检测
function is32Bite(c){
return c.codePointAt(0)>0xFFFF;
}
console.log(is32Bite('𠮷')) // true
console.log(is32Bite('a')) // false
fromCodePoint()方法
通过一个字符的码位返回一个字符,可以看成是String.fromCharCode()
的扩展版。对于BMP的所有字符两个方法的执行结果相同。只有传递非BMP的码位作为参数时,二者的执行结果才有可能不同。
console.log(String.fromCodePoint(134071)) // 𠮷
normalize()方法
Unicode的另一个有趣之处是,如果我们要对不同字符进行排序或比较操作,会存在一种可能,它们是等效的。代表相同文本的字符可能存在的码位不同。所以做比较时要使用normalize()
方法来先标准化一下
只要牢记,在对比字符串之前,一定先把它们标签化为同一种形式。
let normalized = values.map(funciton(text){
return text.normalize();
});
normalized.sort(funciton(first,second){
if(first < second){
return -1;
} else if (first === second) {
return 0;
} else {
return 1;
}
})
正则表达式u修饰符
一个支持Unicode
的修饰符u
使它从编码单元操作模式切换成为字符模式,如此一来正则表达式就不会视代理
对为两个字符,从而完全按照预期正常运行。如:
let text = '𠮷a'
console.log(text.length)
console.log(/^.$/.test(text)) //false
console.log(/^.$/u.test(text)) //true 使用了u修饰符后,正则表达式会匹配字符,从而就可以匹配日文文字字符
计算码位数量
es6仍然不支持字符串码位数量检测(length仍然返回字符串编码单元的数量),但有了u
修饰符后,你就可以通过正则来解决这个问题。
// 长字符串可能会有效率问题,可以使用字符串迭代器来处理
function codePointLength(text){
let rs = text.match(/[\s\S]/gu);
return rs?rs.length:0
}
// 判断浏览器是否支持u
function hasRegExU(){
try{
var partten = new RegExp(".","u");
return true
}catch(ex){
return false
}
}
字符串的子串识别
trim()
includes()
如果在字符串的起始部分检测到指定文本则返回true,否则返回falsestartWith()
如果字符的起始部分检测到指文本则返回true,否则返回falseendsWith()
如果在字符串的结束部分检测到指定文本则返回true,否则返回falserepeat()
返回当前字符串重复一定次数的新字符串
两个参数 第一个指定要搜索的文本 第二个参数是可选的,指定搜索位置的索引位置,如果你需要在一个字符串中寻找一个子字符串的实际位置,还是需要使用
indexOf()
或lastIndexOf()
方法
repeat()方法
ES6还增加了一个repeat()
,它接受一个number类型的参数,表示,该字符串的重复次数,返回值是当前字符串重复一定次数后的新字符串。比如在代码格式化工具中创建缩进级别
let indent = " ".repeat(4),
indentLevel = 0;
// 当需要增加缩进时
let newIndent = indent.repeat(++indentLevel)
正则表达式中的y修饰符
它会影响正则表达式搜索过程中sticky属性,当在字符串中开始字符匹配时,它会通知搜索从正则表达的lastIndex属性开始进行。如果在指定位置没有成功匹配,则停止继续匹配。只有调用exec()和test()这些正则表达式表达式对象的方法时才会涉及lastIndex属性
正则表达式的复制
var reg1 = /ab/i,
// es5中抛出异常,es6中正常运行
reg2 = new RegExp(reg1,"g")
let re = /ab/g
console.log(re.source); // "ab"
console.log(re.flags); // "g"
模板字面量
- 多行字符串 一个正式的多行字符串的概念
- 基本的字符串格式化 将变量的值嵌入字符串的能力
- HTML转义 向html插入经过安全置换后的字符串的能力
模板字面量里不需要转义单、双引号,如果要使用反撇号则需要通过\来转义。有变量可以使用${变量名}占位(如果使用一个未定义的变量,总会抛出错误),模板字面量本身也是javascript表达式,所以你可以在一个模板字面量里嵌入另一个,如下
let name = "Nicholas",
message = `Hello ${
`my name is ${name}`
}`;
console.log(message);
标签函数的使用
function tag(literals,...substitutions){
// 返回一个字符串
}
// 举个栗子
let count = 10,
price = 0.25
message = passthru`${count}items cost $${count*price.toFixed(2)}.`
如果你有一个名为 passthru()
函数,那么作为一个模板字面量标签,它会接受3个参数:
首先是一个literals
数组:相当于两个变量位符把字符串切成了三段
- 第一占位符前面:空字符''
- 第一个和第二个占位符中间的
items cost $
- 第二个后面'.'
第二个参数就是count解释的值,传参为10,它也成为了substitutions数组的第一个元素,最后一个参数是count*price.toFixed(2)
解释的值2.5
作为substitutions数组的第二个元素。substitutions的元素个数总是比literals的长度少1。
function passthru(literals,...substitutions){
let result = '';
// 根据substition的数量来确定循环的次数
for(let i=0;i<substitutions.length;i++){
result+=literals[i];
result+=substitutions[i];
}
// 合并最后一个literal
result+=literals[literals.length-1];
return result;
}
String.raw()
模板标签同样可以访问原生字符串信息,也就是说通过模板标签可以访问到字符转义被转换成等价字符前的原生字符串,最简单的例子是使用内建的String.raw()
标签函数
let message1 = `Multiline\nstring`,
message2 = String.raw`Multiline\nstring`;
console.log(message1); // "Multiline
// string"
console.log(message2); // "Multiline\\nstring"
原生字符串信息同样被传入模板标签,标签函数的第一个参数是一个数组,它有一个额外的属性raw,是一个包含每一个字面值的原生等价信息的数组。如
literals[0]
总有一个等价的literals.raw[0]
,它包含着它的原生字符串信息。
主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://joyjs.cn/archives/4309