导语: 若字符串中出现一些特殊字符,我们该如何截取才能不出现乱码的现象呢?
转载:https://www.xiabingbao.com/post/truncated/js-string-truncated.html
在以QQ和微信作为第三方登录的系统中,用户的昵称里经常会有表情等字符,比如这样的:非拉?非拉。如果项目中正好有个需要将用户昵称强行按照字符个数进行截断的需求,你会发现截取的字符串中会有乱码的现象。
以上面的用户昵称 非拉?非拉 为例,我们算下这个字符串的长度。
因html编码的原因,在以下字符串和代码中,无法正常的显示出樱桃的图像,全部使用Y
进行代替,测试时,请将Y
替换为具体的樱桃符号
let nickname = ‘非拉Y非拉’;
nickname.length; // 6
说明中间的表情字符占了2个字符的长度,如果单独输出nickname[2]
或者nickname[3]
时,均会输出乱码字符。
JavaScript 内部,字符以 UTF-16 的格式储存,每个字符固定为2个字节。对于那些需要4个字节储存的字符,JavaScript 会认为它们是两个字符。
如果针对所有的昵称都要截取前3个字符时,该怎么截取呢?如果是中文、英文、数字、标点符号等常见字符,那么直接从3开始截取就行了,如果上面的那种有特殊符号的怎么办呢?
1. Array.from方法
Array.from
这个方法能够将类数组转换为真实的数组,比NodeList
, argument
等,同样,也包括字符串。
Array.from(nickname); // [“非”, “拉”, “Y”, “非”, “拉”]
nickname.split(''); // [“非”, “拉”, “�”, “�”, “非”, “拉”]
使用Array.from
把nickname
转换后,可以看到转换成一个真实的数组了,樱桃字符占了数组中的一个位置,然后按照数组中的方法截取再进行拼接即可,而使用split
方法拆分,则还是乱码:
function truncated(str,num){
return Array.from(str).slice(0, num).join('');
}
truncated(nickname, 3); // 非拉Y
2. codePointAt()方法
在ES6之前, JS 的字符串以 16 位字符编码(UTF-16)为基础。每个 16 位序列(相当于2个字节)是一个编码单元(code unit),可简称为码元,用于表示一个字符。字符串所有的属性与方法(如length
属性与charAt()
方法等)都是基于16位序列。
比如length
方法、nickname[2]
、split
方法等操作,都会产生异常。为此在ES6中,加强了对 Unicode 的支持,并且扩展了字符串对象。
对于 Unicode 码点大于0xFFFF
的字符,是使用4个字节进行存储。ES6 提供了codePointAt
方法,能够正确处理 4 个字节储存的字符,返回一个字符的码点。
// 获取樱桃的码点
"Y".codePointAt(0).toString(16); // 1f352
// 输出码点对应的字符
"\u{1f352}"; // Y
请注意: 在之前Unicode
编码,均在[\u000-\uFFFF]
之间,因此可以使用类似\u0047
这样的编码;但是现在码点超过\uFFFF
的界限,若再这样使用,则获取不到对应的字符。因此在ES6中,码点的字符放在中括号内,类似上面的格式(所有的码点均可以使用这种格式):
"\u{1f352}"; // Y
"\u{47}"; // G
"\u{0047}"; // G
那么就容易了:判断需要截取的位置是否正好是4字节的字符,如果是则延长一位截取,否则正常截取:
function truncated(str, num){
let index = Array.from(str)[num-1].codePointAt(0)>0xFFFF ? num+1 : num;
return str.slice(0, index);
}
truncated(nickname, 3); // 非拉Y
3. for-of
for-in
方法是遍历key
值,for-of
是遍历value
值:
let arr = [‘a’, ‘b’, ‘c’];
for(let k in arr){
console.log(k); // 0 1 2
}
for(let v of arr){
console.log(v); // a b c
}
for(let v of nickname){
console.log(v); // 非 拉 Y 非 拉
}
因此利用这个功能,我们也能进行截取:
function truncated(str, num){
let s = '';
for(let v of nickname){
s += v;
num--;
if(num<=0){
break;
}
}
return s;
}
truncated(nickname, 3);