JavaScript 字符串截取乱码问题与解决方案

作者:闭包旅人时间:2026-10-26 07:20:14浏览:184

本文探讨JavaScript中使用slice或substring截取包含生僻字的字符串时,因UTF-16编码占用两个码元导致的乱码或截断问题。通过引入codePointAt和fromCodePoint方法,基于码点而非码元进行遍历与截取,可有效解决该Bug,确保生僻字完整显示。

前言

在JavaScript中,对字符串进行部分截取时,开发者通常会使用slice()函数,或者直接调用substring()函数。然而,在实际操作中,这种截取方式有时会引发意想不到的Bug。

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

前言

在控制台运行代码可以发现,预期截取的字符串应为'小𠮷和小𧨁',但实际结果却少了一个字。这种现象背后的原因值得深入探究。

js的字符编码

早期JavaScript采用的编码规范是16位的字符编码(USC-2)。该规范规定每个字符对应16位的存储空间,这16位空间被称为一个“码元”。字符串的所有属性和方法(例如length属性和charAt方法)均基于这16位的码元进行计算。然而,随着生僻字符的日益增多,16位的空间已无法满足所有字符的存储需求。

为了解决这一问题,编码方式升级为UTF-16。UTF-16允许一个字符占用16位空间(即一个码元),或者占用32位空间(即两个码元)。一些特殊字符,如'𠮷'和'𧨁',就需要占用两个码元才能完整表示。

使用码元截取的bug

前言

JavaScript中的length属性实际上统计的是码元的数量,而slice()方法截取字符串则是依据下标进行的,这里的下标同样指的是码元的下标。

以截取'小𠮷'这两个字为例,如果将slice()的截取范围设置为0到1(即执行console.log(str.slice(0,2))),由于'𠮷'占用了两个码元,slice()仅截取了它第一个码元的值。单个码元无法构成完整的汉字,因此最终得到的结果并非完整的字,而是乱码。

前言

前言

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

既然基于码元的截取方式无法获取正确的字符,那么改用“码点”进行截取便成为可行的解决方案。什么是码点?无论一个字符占用多少空间,它始终只对应一个码点。一个码点可以对应一个码元(16位)或两个码元(32位)。使用码点截取需要编写一个自定义的截取函数。

我们可以在字符串的原型对象上新建一个函数,该函数接收截取的起始坐标和结束坐标作为参数。同时,准备一个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位(即大于0xFFFF),说明该文字占用了两个码元。我们可以通过判断码点值来确定码元指针应该移动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));

前言

总结

到此这篇关于JS字符串截取出现的bug以及解决方式的文章就介绍到这了,更多相关JS字符串截取bug内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

作者最新文章