我们在工作中,经常会遇到把字符串切割成数组的情况,官方也为我们提供了 stringObject.split(separator,howmany) 方法,实例:

const str = '开心哈哈哈哈,悲伤哈哈哈,快乐哈哈哈,是你,开始,开心,悲伤'
console.log(str.split(','))

// 结果
// ['开心哈哈哈哈', '悲伤哈哈哈', '快乐哈哈哈', '是你', '开始', '开心', '悲伤']

但它只能以 某一个字符 进行分割,并且分割完之后,该字符( split(‘开心’) 里的‘开心’ )会被 的 ‘,’ 替换掉,不能保留原始的 字符串。

现在,我们有这样一个需求,把 ‘开心哈哈哈哈,悲伤哈哈哈,快乐哈哈哈,是你,开始,开心,悲伤’ 这个字符串,按照 ['‘开心’, ‘悲伤’] 这两个字符进行切分,并且得到的数组能够完全保留原始字符串

// 输入
const str = '开心哈哈哈哈,悲伤哈哈哈,快乐哈哈哈,是你,开始,开心,悲伤'

// 处理
const result = splitFun(str)

// 输出
console.log(result)
['开心', '哈哈哈哈,', '悲伤', '哈哈哈,快乐哈哈哈,是你,开始,', '开心', ',', '悲伤']


//最终我们再把 输出的数组 渲染到页面上,能够得到 与原始字符串 完全相同的字符串

// react:
(result || []).map(item => <span>{item}</span>)

// vue:
<span v-for"item in result" :key="item">{{item}}</span>

// 开心哈哈哈哈,悲伤哈哈哈,快乐哈哈哈,是你,开始,开心,悲伤

思路梳理:

1、 实现 字符串分割为数组 有很多方式:split, slice,正则表达式…等等方法,最简单的还是采用 split 。
2、 虽然 split 一次只能以一个字符进行分割,但是我们通过 轮询(for循环)的方式,多执行几次,应该是可以达到效果:当我们 第一次把一个字符串 按照第一个字符 分割成为数组之后,还需要对数组里面的 每一项 按照第二个字符 进行分割, 以此类推 可以实现按照多个字符串进行分割。
3、 split 以某个字符 切割完之后,这个字符就被替换为 ‘,’ 了,要想保留原始字符,必须在每次 split 之后,将这个字符插入进去。

第一步:轮询 分割字符 数组

// 为什么以数组的形式存放 该字符串:当我们 第一次把一个字符串 按照第一个字符 分割成为数组之后,
// 还需要对数组里面的 每一项 按照第二个字符 进行分割,这是一个轮询的过程,
// 所以,一开始我们就以一个数组的形式去存放初始数组。
let array = ['开心哈哈哈哈,悲伤哈哈哈,快乐哈哈哈,是你,开始,开心,悲伤']

const labels = ['开心', '悲伤']

for(let i = 0; i < labels.length; i++) {
	// 分割字符串操作
	const twoArray = splitFunc(array, labels[i])
}

第二步:分割字符

const splitFunc = (array, label) => {
	// 定义 一个数组 去存放这一轮 最终被分割 好的数组
      const outArray = []
      
      // 循环 需要被分割的 数组字符串
      for(let i = 0; i < array.length; i++) {
      // 定义 一个数组 去存放 当前项 最终被分割 好的数组
        let newArr = []
        
        // 把 当前项 的字符串 按照 传进来 字符进行分割
        const splitArr = array[i].split(label)
        
        // 得到分割好的数组后,将被 替换为 ‘,’ 的 label 追加进数组的相应位置
        // 例如: ‘开心哈哈哈哈,悲伤哈哈哈,快乐哈哈哈,是你,开始,开心,悲伤’, 以 ‘开心’ 分割
        // 被分割为 ["", "哈哈哈哈,悲伤哈哈哈,快乐哈哈哈,是你,开始,", ",悲伤"]
        // , 每一项的末尾,就应该应该是 开心 二字,最后一项的末尾除外
        
		// 遍历被分割好的数组
        for(let j = 0; j < splitArr.length; j++) {
        // 向被分割好的数组里 追加 label 字符
          newArr.push(splitArr[j])
          
          // 数组的末尾无需 追加
          if (j < splitArr.length -1) {
            newArr.push(label)
          }
        }
        
		// 把当前轮得到 的数组 推到 outArray 里去
        outArray.push(newArr)
      }
      
      // 返回 outArray ,注意 outArray 是一个二维数组
      return outArray
}

第三步:轮询调用 splitFunc 函数

for(let i = 0; i < labels.length; i++) {
	 // 接收 当前轮 返回的 二维数组
      const twoArray = splitFunc(array, labels[i])
      // 将二维数组平铺成为 一维数组
      const oneArray = twoArray.reduce(function (a, b) { return a.concat(b)} )
      // 过滤掉一维数组里的空字符串,并把它重新赋值给 array, 
      // 下一轮循环将 切割 上一轮 已经被分割好的 数组
      array = oneArray.filter(item => item !== "")
 }

// 最终循环结束的 array 就是被彻底分割好的数组

最终代码:

const splitFunc = (array, label) => {
      const outArray = []
      for(let i = 0; i < array.length; i++) {
        let newArr = []
        const splitArr = array[i].split(label)
        console.log('splitArr', splitArr)
        for(let j = 0; j < splitArr.length; j++) {
          newArr.push(splitArr[j])
          if (j < splitArr.length -1) {
            newArr.push(label)
          }
        }
        outArray.push(newArr)
      }
      return outArray
}

 let array = ['开心哈哈哈哈,悲伤哈哈哈,快乐哈哈哈,是你,开始,开心,悲伤']
 const labels = ['开心', '悲伤']

 for(let i = 0; i < labels.length; i++) {
      const twoArray = splitFunc(array, labels[i])
      const oneArray = twoArray.reduce(function (a, b) { return a.concat(b)} )
      array = oneArray.filter(item => item !== "")
 }

 console.log('array', array)

为什么需要如此麻烦的 将一个字符串 分割完成 之后,在组装渲染到页面上?

主要是为了 这些字符里的 关键字符(也就是 需要被分割的字符),在渲染的时候,能够被我们所控制为他设置CSS样式,追加点击事件等等,达到动态的效果,如果仅仅是一个字符串被渲染到页面上,这些操作我们将无法控制。

如果以上算法逻辑有不足之处,欢迎大家指出教导👏👏。

2025.01.03日更新

今天又遇到类似的需求,换了一种实现方式,用正则匹配。
在这里插入图片描述

interface HighlightSegment {
    text: string;
    highlight: boolean;
}

function processHighlightText(text: string, keywords: string[]): HighlightSegment[] {
    // 如果没有关键词,直接返回整个文本作为非高亮片段
    if (!keywords.length) {
        return [{ text, highlight: false }];
    }

    // 将关键词按长度降序排序,避免短词先匹配导致的问题
    const sortedKeywords = [...keywords].sort((a, b) => b.length - a.length);

    // 创建正则表达式
    // 步骤1: k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
    // 这部分是转义正则表达式中的特殊字符
    // 比如如果关键词中包含 '.' '*' '+' 等特殊字符,会在前面加上 '\'

    // 步骤2: sortedKeywords.map(...).join('|')
    // 将所有关键词用 '|' 连接
    // 结果类似:'灵活|高效|美观'

    // 步骤3: new RegExp(`(${...})`, 'g')
    // 最终生成的正则表达式类似:/(灵活|高效|美观)/g
    const regex = new RegExp(`(${sortedKeywords.map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|')})`, 'g');

    const result: HighlightSegment[] = [];
    let lastIndex = 0;

    // 使用正则表达式匹配所有关键词
    text.replace(regex, (match, _, index) => {
        // 添加匹配词之前的非高亮文本
        if (index > lastIndex) {
            result.push({
                text: text.slice(lastIndex, index),
                highlight: false
            });
        }

        // 添加高亮关键词
        result.push({
            text: match,
            highlight: true
        });

        lastIndex = index + match.length;
        return match;
    });

    // 添加最后一个非高亮片段(如果有的话)
    if (lastIndex < text.length) {
        result.push({
            text: text.slice(lastIndex),
            highlight: false
        });
    }

    return result;
}

export default processHighlightText

// 测试代码
// const str = '简单灵活,让开发变得更,让产品变得更美观高效';
// const keywords = ['灵活', '高效', '美观'];
// const result = processHighlightText(str, keywords);
// console.log(result);
// 输出:
// [
// {text: '简单', highlight: false},
// {text: '灵活', highlight: true},
// {text: ',让开发变得更,让产品变得更', highlight: false},
// {text: '美观', highlight: true},
// {text: '高效', highlight: true},
]
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐