并不簡(jiǎn)單的中文姓名校驗(yàn)

談到中文姓名校驗(yàn),大家是既熟悉又陌生,茫茫然中使用面向百度編程大法找到一個(gè)正則表達(dá)式饭弓,放到項(xiàng)目中双饥。輸入張三,驗(yàn)證通過(guò)弟断,完美咏花!這就是我要的校驗(yàn)啦(??)。
各位請(qǐng)跟C羅一起來(lái)看這個(gè)看似簡(jiǎn)單的問(wèn)題阀趴,首先迟螺,下面給出一個(gè)目前項(xiàng)目中的場(chǎng)景,非常常見(jiàn)舍咖,基本滿大街的項(xiàng)目可能都會(huì)遇到的矩父。

姓名校驗(yàn)的表單

姓名校驗(yàn)的核心代碼如下

const validChineseName = (name) => {
    return /^[\u3400-\u9fa5]$/g.test(name)
}

問(wèn)題的轉(zhuǎn)折點(diǎn)往往從但是開(kāi)始的,上面簡(jiǎn)潔的實(shí)現(xiàn)貌似并不完美排霉,有一天窍株,來(lái)了一位名字為??(xian)的用戶,驗(yàn)證無(wú)法通過(guò)(??)攻柠。咱們的故事由此開(kāi)始講起球订,整個(gè)故事中涉及到2個(gè)關(guān)鍵的知識(shí)點(diǎn):

  • Unicode及其編碼算法的一些知識(shí)
  • JS正則中對(duì)于字符相關(guān)的規(guī)則判斷編寫(xiě)

先學(xué)習(xí)以下幾個(gè)基本概念

Coded Character Set: 編碼字符集,給字符表里的抽象字符編上一個(gè)數(shù)字瑰钮,這些數(shù)字對(duì)跟字符集中的字符一一映射冒滩。Unicode字符集是一種編碼字符集。

Character encoding form:浪谴,字符編碼表开睡,將編碼字符集中的字符對(duì)應(yīng)的碼點(diǎn)轉(zhuǎn)換成一定長(zhǎng)度的二進(jìn)制序列,便于計(jì)算機(jī)處理苟耻,此二進(jìn)制序列與碼點(diǎn)的映射關(guān)系稱為字符編碼表篇恒。我們經(jīng)常提到的utf8、utf16都是指字符編碼表的不同算法凶杖。

Code point:胁艰,碼點(diǎn),一個(gè)字符集一般 可以用一張或多張由多個(gè)行和多個(gè)列所構(gòu)成的二位表來(lái)表示智蝠。二維表中的行和列的交叉點(diǎn)腾么,稱之為碼點(diǎn),碼點(diǎn)擁有一個(gè)唯一的編號(hào)杈湾,稱之為碼點(diǎn)值或碼點(diǎn)編號(hào)解虱。

它們之間的關(guān)系可用下圖來(lái)籠統(tǒng)地表達(dá)

字符編碼表與編碼字符集的關(guān)系

經(jīng)常會(huì)有同學(xué)跟我討論的時(shí)候把utf8utf16unicode混為一談毛秘,結(jié)合上文的幾個(gè)概念和示意圖饭寺,不難發(fā)現(xiàn)阻课,所謂的utf8只是基于unicode字符集及其碼點(diǎn)的概念,提供一個(gè)碼點(diǎn)尋址的算法艰匙,將其轉(zhuǎn)換為計(jì)算機(jī)理解的二進(jìn)制串限煞,劃一下重點(diǎn)。

Unicode對(duì)于互聯(lián)網(wǎng)的巨大意義不言而喻员凝,堪稱信息互聯(lián)的基石署驻。Unicode流行起來(lái)之前,很多非英文字符國(guó)家會(huì)使用自己的一套玩法健霹。比如GB2312旺上,如果你沒(méi)有安裝相應(yīng)的解碼器,對(duì)不起糖埋,只能欣賞藝術(shù)感極強(qiáng)的亂碼符號(hào)宣吱。

回到上面說(shuō)到的生僻字校驗(yàn)的問(wèn)題,任何一家尊重用戶的企業(yè)瞳别,對(duì)于自己忠實(shí)的客戶都不能將之拒之門(mén)外征候,哪怕這樣的用戶在巨大的群體中零星的存在。

初始時(shí)祟敛,考慮到以下2個(gè)問(wèn)題

  • 用戶姓名的生僻字很難枚舉疤坝,不確定邊界
  • 生僻字和emoji表情均是使用高、低代理區(qū)的方式表示
    基于以上問(wèn)題馆铁,考慮使用用戶客訴后收集生僻字構(gòu)建平臺(tái)自有的特殊字符集的方案跑揉。對(duì)于發(fā)生客訴后,強(qiáng)烈要求系統(tǒng)解決校驗(yàn)問(wèn)題的客戶埠巨,我們認(rèn)為是忠誠(chéng)度或者信任度較高的用戶历谍,構(gòu)建此方案是利于我們留存這些用戶,雖然體驗(yàn)不是那么完美乖订,但至少能讓此類(lèi)用戶有一個(gè)途徑進(jìn)一步觸達(dá)產(chǎn)品的其他層面扮饶。
在配置平臺(tái)拉取生僻字符集

此方案的核心代碼如下

const validChineseName = (n) => {
    const excludedChars = ["??","??"];
    const excludedCharsStr = excludedChars.join('');
    const reg = new RegExp(`^([\u3400-\u9fa5${excludedCharsStr}]){2,15}$`, 'g');
    return reg.test(n);
};

使用mocha做一下單元測(cè)試具练,驗(yàn)證一下校驗(yàn)方法

// 功能示例代碼
const validChineseName = (n) => {
    const excludedChars = ["??","??"];
    const excludedCharsStr = excludedChars.join('');
    const reg = new RegExp(`^([\u3400-\u9fa5${excludedCharsStr}]){2,15}$`, 'g');
    return reg.test(n);
};

module.exports = {
    validChineseName
};
// 單元測(cè)試示例代碼
const expect = require('chai').expect;
const mocha = require('mocha');
const validator = require('./validator');

describe('中文姓名校驗(yàn)', function() {
  it('羅 應(yīng)該是 false', function() {
    expect(validator.validChineseName('羅')).to.be.equal(false);
  });
});

describe('中文姓名校驗(yàn)', function() {
  it('?? 應(yīng)該是 false', function() {
    expect(validator.validChineseName('??')).to.be.equal(false);
  });
});

describe('中文姓名校驗(yàn)', function() {
  it('羅?? 應(yīng)該是 true', function() {
    expect(validator.validChineseName('羅??')).to.be.equal(true);
  });
});

describe('中文姓名校驗(yàn)', function() {
  it('羅超 應(yīng)該是 true', function() {
    expect(validator.validChineseName('超')).to.be.equal(true);
  });
});

第一輪單元測(cè)試的結(jié)果截圖如下


單元測(cè)試結(jié)果截圖

第二個(gè)用例未通過(guò)單元測(cè)試乍构,從上面的代碼看出來(lái),第二個(gè)只有一個(gè)生僻字??扛点,理論上我們預(yù)期它的校驗(yàn)結(jié)果應(yīng)該是false哥遮,但實(shí)際這一個(gè)生僻字校驗(yàn)的結(jié)果居然是true,something went wrong陵究。

字符的正則校驗(yàn)眠饮,本質(zhì)上可以理解為使用unicode來(lái)做匹配,因此铜邮,通過(guò)線上unicode與漢字的轉(zhuǎn)算工具仪召,查看??對(duì)應(yīng)的unicode\ud855\udd84寨蹋。問(wèn)題看來(lái)是出在高低代理對(duì)上。

為了驗(yàn)證我們的猜測(cè)扔茅,可以使用如下的正則來(lái)類(lèi)比已旧,本質(zhì)上是一致的。

/[ab]{2,10}/g.test('ab')
// true

也就是說(shuō)召娜,在正則匹配的時(shí)候底層會(huì)把字符轉(zhuǎn)換為unicode的utf-16的編碼运褪,然后進(jìn)行匹配

定位到問(wèn)題后玖瘸,只需要把連續(xù)的生僻字的高低代理隊(duì)結(jié)合起來(lái)再動(dòng)態(tài)構(gòu)造正則表達(dá)式秸讹,JavaScript中字符串提供了一個(gè)方法charCodeAt,對(duì)于我們處理這個(gè)問(wèn)題是一個(gè)很重要的函數(shù)雅倒。

The charCodeAt() method returns an integer between 0 and 65535 representing the UTF-16 code unit at the given index.
The UTF-16 code unit matches the Unicode code point for code points that can be represented in a single UTF-16 code unit. If the Unicode code point cannot be represented in a single UTF-16 code unit (because its value is greater than 0x10000) then the code unit returned will be the first part of a surrogate pair for the code point. If you want the entire code point value, use codePointAt().
通過(guò)調(diào)用charCodeAt可以獲取對(duì)應(yīng)utf16編碼璃诀,其中原則如下

  • 可以用1個(gè)編碼單元表示的時(shí)候直接用1個(gè)編碼單元來(lái)表示字符的碼點(diǎn)
  • 如果1個(gè)編碼單元無(wú)法表示的時(shí)候,使用2個(gè)編碼單元蔑匣,構(gòu)成高低代理位的形式文虏,通過(guò)一定的算法來(lái)表示字符的碼點(diǎn)
    可以用代碼和相應(yīng)的結(jié)果來(lái)直觀感受
const aCharCode = 'a'.charCodeAt(0).toString(16).padStart(4, '0')
// aCharCode = \u0061
const hCode = '??'.charCodeAt(0).toString(16).padStart(4, '0')
const lCode = '??'.charCodeAt(1).toString(16).padStart(4, '0')
const code = `\\u${hCode}\\u${lCode}`
// code = \ud855\udd84

經(jīng)過(guò)萬(wàn)般折騰后,調(diào)整后的校驗(yàn)示例代碼如下

// 獲取給定字符的utf-16編碼
const getCharCode = (c) => {
    const h = c.charCodeAt(0);
    const l = c.charCodeAt(1);
    let hStr = '', lStr = '';
    if (h) {
        let hCode = h.toString(16).padStart(4, '0');
        hStr = `\\u${hCode}`;
    }
    if (l) {
        let lCode = l.toString(16).padStart(4, '0');
        lStr = `\\u${lCode}`;
    }
    const charCode = `${hStr}${lStr}`;
    return charCode;
}
// 校驗(yàn)
const validChineseName = (n) => {
    const excludedChars = ["??","??"];
    const charCodeArr = excludedChars.map(c => {
        return getCharCode(c)
    });
    const excludedCharsStr = charCodeArr.join(')|(');
    const reg = new RegExp(`^([\u3400-\u9fa5]|(${excludedCharsStr})){2,15}`, 'g');
    return reg.test(n);
};

運(yùn)行單元測(cè)試殖演,用例結(jié)果如下圖所示


單元測(cè)試結(jié)果截圖

到此氧秘,一個(gè)前端兼容生僻字的方案有了初步的實(shí)現(xiàn),方案不完美趴久,還有以下問(wèn)題需要同步考慮

  • 需要確認(rèn)后端校驗(yàn)規(guī)則同步修改
  • 需要確認(rèn)數(shù)據(jù)庫(kù)(mysql)相應(yīng)的字段是否為utf8mb4編碼方式丸相,mysql的utf8最多只支持3個(gè)字節(jié),這個(gè)坑不多說(shuō)彼棍,http://www.techug.com/post/in-mysql-never-use-utf8-use-utf8mb4.html參考這篇文章
  • 其他下游業(yè)務(wù)需要對(duì)此字符支持

歡迎留言討論 (by 前端cluo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末灭忠,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子座硕,更是在濱河造成了極大的恐慌弛作,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,997評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件华匾,死亡現(xiàn)場(chǎng)離奇詭異映琳,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)蜘拉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)萨西,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人旭旭,你說(shuō)我怎么就攤上這事谎脯。” “怎么了持寄?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,359評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵源梭,是天一觀的道長(zhǎng)娱俺。 經(jīng)常有香客問(wèn)我,道長(zhǎng)废麻,這世上最難降的妖魔是什么矢否? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,309評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮脑溢,結(jié)果婚禮上僵朗,老公的妹妹穿的比我還像新娘。我一直安慰自己屑彻,他們只是感情好验庙,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著社牲,像睡著了一般粪薛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上搏恤,一...
    開(kāi)封第一講書(shū)人閱讀 51,258評(píng)論 1 300
  • 那天违寿,我揣著相機(jī)與錄音,去河邊找鬼熟空。 笑死藤巢,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的息罗。 我是一名探鬼主播掂咒,決...
    沈念sama閱讀 40,122評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼迈喉!你這毒婦竟也來(lái)了绍刮?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,970評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤挨摸,失蹤者是張志新(化名)和其女友劉穎孩革,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體得运,經(jīng)...
    沈念sama閱讀 45,403評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡膝蜈,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了澈圈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片彬檀。...
    茶點(diǎn)故事閱讀 39,769評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖瞬女,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情努潘,我是刑警寧澤诽偷,帶...
    沈念sama閱讀 35,464評(píng)論 5 344
  • 正文 年R本政府宣布坤学,位于F島的核電站,受9級(jí)特大地震影響报慕,放射性物質(zhì)發(fā)生泄漏深浮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評(píng)論 3 327
  • 文/蒙蒙 一眠冈、第九天 我趴在偏房一處隱蔽的房頂上張望飞苇。 院中可真熱鬧,春花似錦蜗顽、人聲如沸布卡。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,705評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)忿等。三九已至,卻和暖如春崔挖,著一層夾襖步出監(jiān)牢的瞬間贸街,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,848評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工狸相, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留薛匪,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,831評(píng)論 2 370
  • 正文 我出身青樓脓鹃,卻偏偏與公主長(zhǎng)得像蛋辈,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子将谊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 前言 最先接觸編程的知識(shí)是在大學(xué)里面尊浓,大學(xué)里面學(xué)了一些基礎(chǔ)的知識(shí)逞频,c語(yǔ)言,java語(yǔ)言栋齿,單片機(jī)的匯編語(yǔ)言等苗胀;大學(xué)畢...
    oceanfive閱讀 3,070評(píng)論 0 7
  • 字符是用戶可以讀寫(xiě)的最小單位基协。計(jì)算機(jī)所能支持的字符組成的集合,就叫做字符集菇用。字符集通常以二維表的形式存在澜驮。二維表的...
    劉惜有閱讀 8,110評(píng)論 2 14
  • 轉(zhuǎn)載須注明出處:簡(jiǎn)書(shū)@Orca_J35 以下內(nèi)容直接翻譯自 Unicode 術(shù)語(yǔ)表:Glossary of Uni...
    import_hello閱讀 3,156評(píng)論 0 0
  • 堅(jiān)持二字說(shuō)著簡(jiǎn)單,做著確實(shí)很難惋鸥。 以前總說(shuō)要為小寶每天的成長(zhǎng)做些記錄杂穷,以便長(zhǎng)大后講給她聽(tīng)悍缠。但每天總...
    淘氣珂珂閱讀 238評(píng)論 0 0
  • 時(shí)常感到心情抑郁,我想這是孤獨(dú)導(dǎo)致的吧耐量。也或許是身體中某種激素的缺乏飞蚓,讓我激動(dòng)不起來(lái)。 最近老是想到死亡廊蜒,感覺(jué)我和...
    桃白白_0796閱讀 123評(píng)論 0 0