最近遇到一个问题:在做字符串截取操作时,如果字符串中包含了 emoji 字符(一个表情占多个 unicode 字符),而碰巧又把它截断了,程序会出错。在 ReactNative App 下的具体表现就是崩溃。由于以前做的是网页比较多,基本没有输入表情字符的案例,而在手机上就不一样了,因此这个问题还是第一次发现。
比如说:
'😋Emoji😋'.substring(0, 2)
|
因此,如果对这个字符串做 substring(0, 1)
操作,就会截取到一个未知字符。
中间的探索过程就不谈了,Google 了一下解决方案,以及咨询同事们以后,发现最简单的办法是通过 lodash
自带的 toArray
方法,先将它转为数组,然后将整个逻辑改为数据的截取操作,最后再转回字符串。
export function safeSubStr (str, start, end) { const charArr = _.toArray(str); return _.slice(charArr, start, end).join(''); }
|
实际上解决问题的是 _.toArray
,它帮我们把表情字符正确地截了出来:
其实我也比较好奇它是怎么做的,通过观察源码,发现了真正的解决方案:
var rsAstralRange = '\\ud800-\\udfff', rsComboMarksRange = '\\u0300-\\u036f', reComboHalfMarksRange = '\\ufe20-\\ufe2f', rsComboSymbolsRange = '\\u20d0-\\u20ff', rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange, rsVarRange = '\\ufe0e\\ufe0f';
var rsAstral = '[' + rsAstralRange + ']', rsCombo = '[' + rsComboRange + ']', rsFitz = '\\ud83c[\\udffb-\\udfff]', rsModifier = '(?:' + rsCombo + '|' + rsFitz + ')', rsNonAstral = '[^' + rsAstralRange + ']', rsRegional = '(?:\\ud83c[\\udde6-\\uddff]){2}', rsSurrPair = '[\\ud800-\\udbff][\\udc00-\\udfff]', rsZWJ = '\\u200d';
var reOptMod = rsModifier + '?', rsOptVar = '[' + rsVarRange + ']?', rsOptJoin = '(?:' + rsZWJ + '(?:' + [rsNonAstral, rsRegional, rsSurrPair].join('|') + ')' + rsOptVar + reOptMod + ')*', rsSeq = rsOptVar + reOptMod + rsOptJoin, rsSymbol = '(?:' + [rsNonAstral + rsCombo + '?', rsCombo, rsRegional, rsSurrPair, rsAstral].join('|') + ')';
var reUnicode = RegExp(rsFitz + '(?=' + rsFitz + ')|' + rsSymbol + rsSeq, 'g');
function unicodeToArray(string) { return string.match(reUnicode) || []; }
module.exports = unicodeToArray;
|
一大堆正则就不谈了,也不知道它是从哪里找来的这些值,最后组装了一个 reUnicode
正则来实现 unicode 转数组。话又说回来,这么做会不会有性能问题呢?我表示比较担忧。好在项目里面需要这么做的场景不多,字符串也不长,可以如此暴力解决。如果换个场景,还真不好说。也许又需要一种更高效的解决方案了。