含有emoji的字节截取功能(utf-8编码下)

2,279 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

业务需求描述:一个标题输入框中要求字节数不能超过20(不限制文字类型和表情)

/**
     * @description: 得到十六进制字节数(utf-8下的字节数)
     * @param {*} str 十六进制码
     * @return {*} 字节数
     */
    getByteByHex(str) {
        if (str <= 0x007f) {
            return 1;
        } else if (str <= 0x07ff) {
            return 2;
        } else if (str <= 0xffff) {
            return 3;
        } 
        return 4; 
    },
    /**
     * @description: 字符串转Unicode十六进制
     * @param {string} str 传入的字符串
     * @return {*} result 转出的十六进制码
     */
    strToUnicode(str) {
        let result = '';
        for (let i = 0; i < str.length; i++) {
            let code = str.codePointAt(i).toString(16);
            // eslint-disable-next-line no-useless-escape
            result += `\u{${code}}`; 
            if (code.length > 4) {
                i++; // 由于str.length也只能处理两个字节的文字,所以这里需要判断如果codePointAt得到多字符就得跳过一次循环
            }
        }
        return result;
    },
    /**
     * @description: 截取指定字符数长度的文本 如果后一个字符截取后超出指定的长度,将不会截取该字符
     * @param {string} str 字符串
     * @param {number} maxLength 需要截取字节长度
     * @return {string} result 截取所需字节后的字符串
     */
    cutStr(str, maxLength) {
        let data = this.strToUnicode(str);
        let reg = new RegExp(/\u{[A-z0-9]+}(\u{200d}{1}\u{[A-z0-9]+})*/, 'g');// 使用正则表达式分割每个完整字符
        let datas = reg[Symbol.match](data);
        let result = '';
        let length = 0;
        
        for (let i in datas) {
            let value = datas[i].split('\u').slice(1);
            // eslint-disable-next-line no-loop-func
            value = value.map(str => {
                let value = str.replace(/\u/g, '')
                    .replace(/{/g, '')
                    .replace(/}/g, '');
                const parseVal = parseInt(val, 16);
                length += this.getByteByHex(parseVal);
                return parseVal;
            });
            if (length <= maxLength) {
                result += String.fromCodePoint(...value);
            } else break;
        }
        return result;
    },
  • Symbol.match 匹配的是正则表达式而不是字符串
const regexp1 = /foo/;
  regexp1[Symbol.match] = false;
  
  console.log('/foo/'.startsWith(regexp1));
  // expected output: true
  
  console.log('/baz/'.endsWith(regexp1));
  // expected output: false
  • parseInt() 函数可解析一个字符串,并返回一个整数。(详细介绍请见本人js专栏parseInt())

  • 思路

    1、将文本转换成Unicode十六进制(可以存为数组,也可以存储为“\u{十六进制}”,后面这种方法js的表示转义表示方法)

    2、 循环数组,如果十六进制长度是1-2表示是一个字节,是3-4则表示是2个字节大于4表示3个字节(一般不会有5个字节,组合字符除外)

    3、如果在第2步循环中遇到200d则表示下一次循环字符和上一次循环字符是组合字符(将他们存储到一起)

    4、在第2步循环时统计长度,将长度和得到的十六进制存储到数组

    5、循环得到的数组,如果长度相加小于给定的长度则将16进制转成文本追加到结果变量,否则跳出循环

  • codePointAt()方法在字符串的指定索引处返回字符的Unicode值