直接实现的方式
String.prototype.sliceByPoint = function (pStart, pEnd) {
let result = ""; // 截取的结果
let pIndex = 0; // 码点的指针
let cIndex = 0; // 码元的指针
// eslint-disable-next-line no-constant-condition
while (true) {
if (pIndex >= pEnd || cIndex >= this.length) {
// 如果码点起始指针位置大于要截取的最大长度(结束)或者码元的起始指针大于整个字符串的长度(结束)
break;
}
const point = this.codePointAt(cIndex);
if (pIndex >= pStart) {
result += String.fromCodePoint(point);
}
pIndex++;
cIndex += point > 0xffff ? 2 : 1;
}
return result;
};
testee() {
const str = "阿是𠮷娃娃";
console.log("1",str.slice(0, 3));
console.log("2",str.sliceByPoint(0, 3));
console.log("3",str);
console.log(0xffff);
},
执行结果为
码元与码点
JavaScript字符串使用了两种Unicode编码混合的策略:UCS-2和UTF-16。对于可以菜哦也能够16位编码的字符(U+0000~U+FFFF),这两种编码实际上是一样的。(《JavaScript高级程序设计》)
关于码元和和码点,通过一个例子进行介绍。
如图,字符串'😊'只有一个“笑脸”符号,但是通过length属性发现,“长度”为2,string.length到底表示什么?答:码元的个数
什么是码元?码元就是编码的最小单元,UTF-16和UCS-2的码元为16个比特(2字节)。也就是说,'😊'使用了两个码元,也就是4字节进行编码。
通过string.charCodeAt(index)方法可以返回对应位置的码元。
那什么是码点呢?码点就是对应字符的编码,通过对应编码规则,将编码转换为1个或多个码元。
通过string.codePointAt(index)方法可以返回对应位置的码点。
这里再说回到Unicode和UCS-2、UTF-16等之间的关系。
Unicode编码,是统一的,对于一个确定的字符,他的Unicode编码就是确定的。就拿上面的例子来说,'😊'的Unicode编码为128522。那UTF-8、UTF-16等等这些编码是什么?这里更加容易理解的说法就是,他们都是Unicode编码的一种格式,也就是不同的“转换规则”。
这里使用了一个在线的编码工具Unicode编码转换,UTF编码转换(UTF-8、UTF-16、UTF-32)
如果码点(即字符编码)值大于0xFFFF,则将其减去0x10000,之后,在该数字的前10位(bits)加上0xD800,就得到UTF-16四字节编码中的前两个字节(第一个码元);该数字的后10位(bits)加上0xDC00,得到后两个字节(第二个码元)。
就拿上面的例子说:
'😊':Unicode编码位0x1F60A
0x1F60A - 0x10000 = 0xF60A = 0x0F60A = 0000 1111 0110 0000 1010
前10bits = 00 0011 1101 = 0x03D
后10bits = 10 0000 1010 = 0x20A
前10bits + 0xD800 = 0x03D + 0xD800 = 0xD83D
后10bits + 0xDC00 = 0x20A + 0xDC00 = 0xDE0A
可以看到,通过UTF-16的转换规则,将码点为0x1F60A的字符,转换为0xD83D和0xDE0A两个码元。
码元、码点和字符串相关的方法
1. 获取指定位置的码元
- String.proto.charCodeAt(index)
上面也提到过,string.length返回码元的个数,所以这里的index有效范围是[0, length - 1]
2. 获取指定位置的字符(如果可以用一个码元进行编码,码元的值和码点的值相等)
- String.proto.charAt(index)
charCodeAt()一个返回的是码元(数值),而charAt()返回码元对应的字符,如果一个字符有多个码元,这里仅仅取出其中一个码元,并把它当作码点,就会出现乱码。
3. 将字符串指定位置的码元转换成码点(如果是多个码元表示的码点,从指定位置开始,取多个码点进行转换)
String.proto.codePointAt(index)
如图,“笑脸”码元开始的位置是1;当传入2的时候,下标2位置对应的是“笑脸”的第二个码元,不是完整的两个码元开始的位置,所以直接返回了第2个码元,而不是转换成码点返回。
4.通过码元创建字符串
- String.proto.charCodeFrom(...charCode)
将码元转换成字符串
5.通过码点创建字符串
- String.proto.codePointFrom(...codePoint)