背景
是这样的,最近在写一个微信公众号的处理脚本,用来替换替换文章中的指定内容。
function getInsertElement(rootElement) {
const matchFlag = 'AA'
const pList = [...rootElement.querySelectorAll('p')]
let matchedElement = pList.find(el => {
const text = (el.textContent || el.innerText).replace(/\u00a0/gi, '').trim()
return text === matchFlag
})
return matchedElement
}
上面的方法是脚本的一部分,用于获取文章中指定字符串所在的 DOM 元素,思路是通过 Node.textContent
来匹配的。
在调试的时候,发现有时候匹配不上,用「肉眼」看是没问题的,但硬是匹配不上。经过一番排查之后,发现了一个有趣的事情,如图:
在编辑器内有字符 AA
,然后使用 encodeURIComponent($0.textContent)
的编码结果是 AA%E2%80%8B
,所以上面 text === matchFlag
比较结果为 false
,自然就匹配不上了。下面将其转换为 Unicode 字符:
function string2Unicode(str) {
return str
.split('')
.map(value => {
const temp = value.charCodeAt(0).toString(16).padStart(4, '0').toUpperCase()
if (temp.length > 2) return '\\u' + temp
return value
})
.join('')
}
const encodedStr = '%E2%80%8B'
const originString = decodeURIComponent(encodedStr)
const unicodeStr = string2Unicode(originString)
console.log(unicodeStr) // \u200B
转换得出 %E2%80%8B
的 Unicode 编码为 U+200B
,然后查询这里发现它是「零宽空格」,是一种不可见的字符。
因此,只要使用正则表达式 /\u200b/gi
,把所有零宽空格干掉就行了。
function getInsertElement(rootElement) {
const matchFlag = 'AA'
const pList = [...rootElement.querySelectorAll('p')]
let matchedElement = pList.find(el => {
const text = (el.textContent || el.innerText)
.replace(/\u00a0/gi, '')
.replace(/\u200b/gi, '')
.trim()
return text === matchFlag
})
return matchedElement
}
零宽空格
零宽空格(zero-width space,ZWSP)是一种不可见、不可打印的 Unicode 字符,用于可能需要换行处。
在 Unicode 中,该字元为 U+200B
。在 HTML 中转义字符有:
、​
、
。一般情况下,它是不可见的,但一些软件对这些不可见字符做了处理,视觉上可感知。举个例子:
相邻单词之间有一个零宽空格
LoremIpsumDolorSitAmetConsecteturAdipiscingElitSedDoEiusmodTemporIncididuntUtLaboreEtDoloreMagnaAliquaUtEnimAdMinimVeniamQuisNostrudExercitationUllamcoLaborisNisiUtAliquipExEaCommodoConsequatDuisAuteIrureDolorInReprehenderitInVoluptateVelitEsseCillumDoloreEuFugiatNullaPariaturExcepteurSintOccaecatCupidatatNonProidentSuntInCulpaQuiOfficiaDeseruntMollitAnimIdEstLaborum
相邻单词之间无零宽空格
LoremIpsumDolorSitAmetConsecteturAdipiscingElitSedDoEiusmodTemporIncididuntUtLaboreEtDoloreMagnaAliquaUtEnimAdMinimVeniamQuisNostrudExercitationUllamcoLaborisNisiUtAliquipExEaCommodoConsequatDuisAuteIrureDolorInReprehenderitInVoluptateVelitEsseCillumDoloreEuFugiatNullaPariaturExcepteurSintOccaecatCupidatatNonProidentSuntInCulpaQuiOfficiaDeseruntMollitAnimIdEstLaborum
它们在 VS Code 及页面中的展示效果,如图所示:
扩展
除此之外,还有零宽连字、零宽不连字也是不可见字符。
零宽连字(zero-width joiner,ZWJ)是一个控制字符,放在某些需要复杂排版语言(如阿拉伯语、印地语)的两个字符之间,使得这两个本不会发生连字的字符产生了连字效果。其 Unicode 编码为
U+200D
,HTML 转义字符有:
、&zwj
。零宽不连字(zero-width non-joiner,ZWNJ)是一个不打印字符,放在电子文本的两个字符之间,抑制本来会发生的连字,而是以这两个字符原本的字形来绘制。其 Unicode 编码为
U+200C
,HTML 转义字符为:
。
相信你也看过网友「把幸福的一家几口强行分开」的段子,哈哈:
我们利用前面的 string2Unicode()
方法,将其转化为 Unicode 编码,如下:
其实它们是由多个字符组合而成的,前面所看到的“空字符串”其实就是 U+200D
(零宽连字)。
一篇有趣的文章:Why […‘❤️’] returns [‘’, ‘’, ‘❤’, ‘️’, ‘’, ‘’, ‘’, ‘’] in JavaScript?
应用
零宽字符能做什么?
- 传递信息:利用其不可见的特性,在未对零宽字符做过滤的网站插入不可见的隐形文本。
- 水印:同样利用其不可见的特性,给我们的产品添加隐形水印。
- ...
The end.