在日常开发项目的时候,经常会有提供用户输入的表单的情况。难免会碰到用户输入了一些奇奇怪怪的生僻字或者emoji,如果有对输入的长度有计算需求的话,往往会遇到以下的情况:
'𠮷啊'.length; // 3
'🙈啊'.length; // 3
出现上述的原因是js对字符的编码一开始使用的规范是ucs-2。ucs-2规定每一个文字ta对应16位(二进制编码)的空间,这16位的空间被称之为码元,即一个文字对应一个码元。
随着生僻字越来越多,还加入了emoji表情,16位的空间不够用了。于是js把编码方式换成了utf-16,ta允许一个文字可以占用16位空间,即一个码元。也可以占用32位(二进制编码)的空间,也就是两个码元。这样一来,这些生僻字或者emoji表情就占用了两个码元。
而js里面字符串的length属性,实际上ta数的是字符占用码元的数量。 字符串的下标,也指的是码元的下标。
如果取生僻字或者emoji表情的第一个下标,这样得到第一个码元的值输出来会是乱码,因为一个码元ta形成不了文字。
'𠮷啊'[0]; // "�"
如果想要取到想要的东西,应该使用码点来进行处理。码点可以对应16位空间,也可以对应32位空间,一个文字就是一个码点。
// slice - 通过码点取
String.prototype.sliceByPoint = function(pStart = 0, pEnd = this.length) {
let result = ''; // 截取的结果
let pIndex = 0; // 码点的指针
let cIndex = 0; // 码元的指针
while(1) {
// 结束条件:码点的指针到达指定位置 || 码元的指针到达数组的最后
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;
}
'𠮷啊🙈啊'.slice(0, 3); // "𠮷啊" '𠮷啊🙈啊'.sliceByPoint(0, 3); // '𠮷啊🙈'
// 根据码点计算字符串长度
function is32bit(char, i) {
// 如果码点大于了16位二进制的最大值,则其是32位的
return char.codePointAt(i) > 0xffff;
}
// 获取字符串length属性
function getStringLength(string) {
let len = 0;
for (let i = 0; i < str.length; i++) {
// i在索引码元
if (is32bit(str, i)) {
// 当前字符串,在i这个位置,占用了两个码元
i++;
}
len++;
}
return len;
}
getStringLength('𠮷啊🙈啊'); // 4
当然,更简便的办法是使用es6处理
// 取length属性
let str = '𠮷啊🙈啊';
str.length; // 6
[...str].length; // 4
Array.from('𠮷啊🙈啊').length; // 4
// slice
[...str].slice(0, 3); // ['𠮷', '啊', '🙈']