写点什么

JS 字符串的截取出现的 bug

  • 2023-04-16
    广东
  • 本文字数:1731 字

    阅读完需:约 6 分钟

JS字符串的截取出现的bug

前言

在 js 中我们对字符串进行一部分截取,可以使用slice()函数截取,也可以直接用substring()函数来截取,但是截取也有可能出 bug


const str='小𠮷和小𧨁今天吃了50块钱的KFC'console.log(str.slice(0,5));
复制代码



可以在控制台看到,本来应该截取的字符串是'小𠮷和小𧨁'才对,却少了一个字,这是什么原因呢?

js 的字符编码

在很早的时候,js 使用的编码规范是 16 位的字符编码(USC-2),规定了每一个字对应 16 位的空间,16 位的空间称为码元,字符串的所有属性和方法(像是 length 属性和 chatAt 方法)都是基于 16 位的码元,但是后来生僻字越来越多,16 位的空间不够用了


就把编码方式换成了 utf-16,utf-16 允许一个文字占用 16 位的空间也就是一个码元或者 32 位的空间就是两个码元,一些特殊的文字就占用了两个码元,像'𠮷'和'𧨁'就占用了两个码元

使用码元截取的 bug


我们使用的 length 属性实际上数的是码元的数量,而使用 slice()方法截取字符串是根据下标来截取的,下标也是指的码元的下标


比如我们截取'小𠮷'这两个字,将 slice()截取的范围改为 0 到 1 也就是console.log(str.slice(0,2)),'𠮷'占用了两个码元,slice()只截取到了它第一个码元的值,一个码元形不成文字,这样得到的就不是一个完整的字,而是一个乱码了




使用码点来正确截取字符串

既然使用码元获取不到正确的字符,那就可以使用码点来截取了,什么是码点呢?码点不管你占用多少空间,一个文字就占一个码点,一个码点对应一个码元或者两个码元,使用码点截取就要写一个截取的函数了


我们在字符串的原型对象上新建一个函数,传入一个截取的起始坐标和结束坐标,准备好一个 result 变量存储最终截取到的结果,和两个代表码元和码点指针的变量


String.prototype.strSlice=function(sStart,sEnd){//截取的起始坐标和结束坐标  let result='' //截取的结果  let dIndex=0 //码点的指针  let yIndex=0 //码元的指针}
复制代码


接下来就要不断地向右运行码点和码元的指针进行截取,所以需要一个无限循环,当码点的指针到达了结束的位置或者码元的指针超出了数组的长度就结束循环返回最终截取的结果


while(1){    if(dIndex>=sEnd || yIndex>=this.length){ //结束循环条件      break;    }    //截取操作}return result //返回截取结果
复制代码


每一次循环就码点和码元移动一次指针,码点直接每次移动 1 位,但是一个字符会存在两个码元,这样码元和码点就对应不上了,需要根据字符占据的码元数量来移动



在 ES6 为我们提供了一个函数codePointAt可以得到码点的值,码点的值有可能是 16 位或者 32 位的,而一个文字占用 16 位,如果码点的值超过 16 位说明这个文字占用了两个码元,我们就可以通过码点的值判断码元的指针应该移动 1 位或者 2 位


while(1){    if(dIndex>=sEnd || yIndex>=this.length){ //结束循环条件      break;    }    //截取操作    const point=this.codePointAt(yIndex) //获取码点的值        dIndex++ //码点指针每次+1     yIndex+=point > 0xffff ? 2:1 //判断码点的值是否超过16位,超过占用2个码元,指针+2,没有+1}return result //返回截取结果
复制代码


码点和码元的指针移动已经同步了,对应在同一个文字上,然后就可以截取文字了。当码点的指针大于等于起始坐标就把对应的文字取出来放在 result 里,不能通过 this[yIndex] 取值,不然还是取的码元对应的值,得通过码点对应的值取出来,在 ES6 里还提供了一个函数fromCodePoint,按照码点的值恢复这个文字,将文字加到 result 里就行了



String.prototype.strSlice=function(sStart,sEnd){//截取的起始坐标和结束坐标  let result='' //截取的结果  let dIndex=0 //码点的指针  let yIndex=0 //码元的指针  while(1){    if(dIndex>=sEnd || yIndex>=this.length){ //结束循环条件      break;    }    //截取操作    const point=this.codePointAt(yIndex) //获取码点的值    if(dIndex>=sStart){      result+=String.fromCodePoint(point)    }
dIndex++ yIndex+=point > 0xffff ? 2:1 //判断码点的值是否超过16位,超过占用2个码元,指针+2,没有+1 } return result //返回截取结果}
复制代码


最后调用 strSlice 方法,传入截取的起始坐标和结束坐标,截取到的结果也是我们想要的


console.log('截取的结果为:',str.strSlice(0,5));
复制代码



发布于: 刚刚阅读数: 4
用户头像

你若毁我天堂,我必戳你脊梁 2022-11-01 加入

还未添加个人简介

评论

发布
暂无评论
JS字符串的截取出现的bug_JavaScript_格斗家不爱在外太空沉思_InfoQ写作社区