零宽字符

3,011 阅读3分钟

什么是零宽字符

所谓零宽字符,就是不可见的「非打印」字符,通过视觉无法看出字符串中是否有零宽字符,但是通过代码遍历,是可以获取到该字符的。有些软件也可以将零宽字符打印出来。

常见的零宽字符有:

零宽空格(zero-width space, ZWSP)用于可能需要换行处。
    Unicode: U+200B  HTML: ​
零宽不连字 (zero-width non-joiner,ZWNJ)放在电子文本的两个字符之间,抑制本来会发生的连字,而是以这两个字符原本的字形来绘制。
    Unicode: U+200C  HTML: ‌
零宽连字(zero-width joiner,ZWJ)是一个控制字符,放在某些需要复杂排版语言(如阿拉伯语、印地语)的两个字符之间,使得这两个本不会发生连字的字符产生了连字效果。
    Unicode: U+200D  HTML: ‍
左至右符号(Left-to-right mark,LRM)是一种控制字符,用于计算机的双向文稿排版中。
    Unicode: U+200E  HTML: ‎ ‎ 或‎
右至左符号(Right-to-left mark,RLM)是一种控制字符,用于计算机的双向文稿排版中。
    Unicode: U+200F  HTML: ‏ ‏ 或‏
字节顺序标记(byte-order mark,BOM)常被用来当做标示文件是以UTF-8、UTF-16或UTF-32编码的标记。
    Unicode: U+FEFF

零宽字符有什么用

隐藏信息

零宽字符可以用来隐藏和加密一些信息。

例如我想在一条链接中带上隐形水印LvLin,可以先将LvLin转成二进制字符串:

let name = 'LvLin';

// 将 name 进行字符拆分,然后每个字符进行转译
function textToBinary(name) {
    return name.split('').map(char => {
        return char.charCodeAt(0).toString(2);
    })
}

let binaryArr = textToBinary(name);
// ['1001100', '1110110', '1001100', '1101001', '1101110']

将二进制字符串换成零宽字符替代:

function binaryToZeroWidth (binary) {
    return binary.split('').map((binaryNum) => {
        if (+binaryNum === 1) {
            return '•'; // zero-width space
            // '•'.charCodeAt(0).toString(16) => '200b'
        } else {
            return '•'; // zero-width non-joiner
            // '•'.charCodeAt(0).toString(16) => '200c'
        }
    }).join('');
}

let zeroWidthArr = binaryArr.map(binaryToZeroWidth); 

将零宽字符拼接在链接后面:

let link = 'lvalue.com';

link = link + zeroWidthArr.join('•') // zero-width joiner
// '•'.charCodeAt(0).toString(16) => '200d'
// link.length => 49

这样我们就获取到了一个带有隐形水印的链接:lvalue.com‌‌‌‌‌‌‌‍‌‌‌‌‌‌‌‍‌‌‌‌‌‌‌‍‌‌‌‌‌‌‌‍‌‌‌‌‌‌‌

提取隐藏信息

将零宽字符从链接中提取出来,转成二进制数,再转成字符串。

// 提取隐藏信息
let zeroWidthArr = link.replace(/[^\u200b-\u200d]/g, "").split('•'); // zero-width joiner

// 将零宽字符串转成二进制数,再转成字符
function zeroWidthToStr (zeroWidthStr) {
    let binaryStr = zeroWidthStr.split('').map((zeroWidthChar) => {
        if (zeroWidthChar === '•') {
            return '1';
        } else {
            return '0';
        }
    }).join('');
        
    return binaryToStr(+binaryStr)
}

// 将二进制数转成字符
function binaryToStr(binary) {
    return String.fromCharCode(parseInt(binary, 2));
}

let myName = zeroWidthArr.map(zeroWidthToStr).join('');
console.log({myName}) // 'LvLin'

常用操作

过滤零宽字符

newStr = str.replace(/[\u200b-\u200f\uFEFF\u202a-\u202e]/g, "");

提取零宽字符

newStr = str.replace(/[^\u200b-\u200f\uFEFF\u202a-\u202e]/g, "");

参考

[翻译]小心你复制的内容:使用零宽字符将用户名不可见的插入文本中

什么零宽度字符,以及零宽度字符在JavaScript中的应用