原文地址:www.zhangxinxu.com/wordpress/2…
一、題目
就是寫一個檢驗方法,輸出不符合排版規則的內容。
翻譯規則地址:譯文排版規則指南
補充細節:
- 數字與單位之間需要增加空格只需要考慮後面是大寫字母的場景,因為程式碼中經常會有類似20px這樣的處理,中間不能有空格。
- 標點不能重複專指中文標點,英文標點不考慮,在程式程式碼中標點重複很常見。
對於字元內容的格式驗證,自然就是正規表示式了,因此,本期的題目主要目的之一是學習正規表示式。
正規表示式規則在所有的語言中都是通用的,除了細節上上有些差異,需要的是一模一樣的,所以學會了JS中的正規表示式,其他開發語言也可以受用,包括CSS這麼語言(CSS中也有正規表示式)。
JS這麼語言要想基礎紮實,正規表示式一定要好好學習,學到滴水不漏,可以極大提升處理自己的開發效率。包括在其他一些場合解放生產力,比方說Sublime Text這種編輯器在替換的時候是支援正規表示式的,如果你會正規表示式,則簡單幾個字元就能完成複雜替換,而且絕不會遺漏。那種解放生產力的感覺,會讓你覺得,會程式碼真的好棒,好high!感覺人生已經達到了高潮。
二、幾個驗證的實現
CSS題目收到近50份回答,這次的正則小測,只有4個實現,人雖少,但都是精英。
其中第一位@XboxYan的回答幾乎直接大結局,我就以這個人回答作為案例講講正規表示式相關的一些東西。
1. 中英文之間需要增加空格
/([\u4e00-\u9fa5]+[A-Za-z]+|[A-Za-z]+[\u4e00-\u9fa5]+)/g複製程式碼
JS中正規表示式書寫有兩種方式,一種是直接兩個斜槓,還有一種是使用RegExp物件構建。
上面這個例子啊,就是使用的斜槓。
舉個最簡單的例子,/1/
可以匹配字串裡面是否有字元'1'
。
我們分解下這個正規表示式:
[\u4e00-\u9fa5]
表示中文字元匹配;[A-Za-z]
表示全部的英文字母。於是這個正規表示式可以理解為:
/(中文+英文+|英文+中文+)/g複製程式碼
是不是要更好理解了,剩下的一些符合是什麼意思呢?
括號()
表示分組,這裡可以去掉,浪費,白白佔用匹配資源,直接下面這樣既可:
/中文+英文+|英文+中文+/g複製程式碼
這裡的豎著的管道符|
在正規表示式中表示或者的意思,也就是匹配中文後面直接跟著英文,或者英文後面直接帶著中文這兩個場景。
加號+
表示數量,表示1個或多個。正規表示式中還有其他一些表示數量的方法,例如:
- 加號
+
表示1個或多個。 - 問號
?
表示1個或0個。 - 星號
*
表示任意數量。 - 花括號
{}
可以指定數量,例如{2}
表示2個,{2, 6}
表示2-6個,{2,}
表示2個或2個以上。
在本例中,加號也是可以去掉的,不影響匹配。因此,此正則可以進一步簡化:
/中文英文|英文中文/g複製程式碼
也就是:
/[\u4e00-\u9fa5][A-Za-z]|[A-Za-z][\u4e00-\u9fa5]/g複製程式碼
斜槓後面的g
表示全域性匹配,除了g
,還有i
和m
。其中i
表示不區分大小寫,m
表示支援多行匹配。
因此,這裡的正則可以進一步簡化:
/[\u4e00-\u9fa5][a-z]|[a-z][\u4e00-\u9fa5]/gi複製程式碼
2. 中文與數字之間需要增加空格
/([\u4e00-\u9fa5]+\d+|\d+[\u4e00-\u9fa5]+)/g複製程式碼
和第一個驗證,中文和英文之間加空格類似,括號和加號都可以簡化掉:
/[\u4e00-\u9fa5]\d|\d[\u4e00-\u9fa5]/g複製程式碼
然後這裡有個\d
,表示的是匹配數字0-9
,這裡的正則也可以寫成下面這樣:
/[\u4e00-\u9fa5][0-9]|[0-9][\u4e00-\u9fa5]/g複製程式碼
正規表示式中有很多指代專屬類別字元的寫法,例如:
\d
表示的是匹配數字;換成大寫的\D
則表示匹配數字以外其他字元,等同於[^0-9]
。我們如果想要匹配任意字元,可以使用[\d\D]
這種寫法。\w
表示匹配數字、字母和下劃線;\W
表示匹配數字、字母和下劃線以為的其他字元。\s
表示匹配空格、製表符和換行符;換成大寫的\S
則表示除了以外空格、製表符和換行符其他字元。\n
表示換行。
3. 數字與單位之間需要增加空格
/\d[A-Za-z]+/g複製程式碼
題目這個需求是不合理的,有些數字和單位之間是不能加空格的,有趣技術文章翻譯,必定包含大量的程式碼,例如10px
等,顯然不能加空格。因此,可以認為數字和大寫字母之間需要空格。
因此,正則可以調整為:
/\d[A-Z]+/g複製程式碼
4. 全形標點與其他字元之間不加空格
/([\s\S]{2}[\!|\·|\【|\】|\「|\」|\;|\:|\“|\”|\,|\《|\。|\》|\、|\?]\s+)|[\s+(\!|\·|\【|\】|\「|\」|\;|\:|\“|\”|\,|\《|\。|\》|\、|\?)[\s\S]{2}]/g複製程式碼
這個就相當長了,很多人看了會覺得是天書一樣,其實很簡單,也有不少優化和改進空間。
首先,前後的[\s\S]{2}
是多餘的,可以刪掉,沒有必要再額外匹配任意兩個字元;
然後,最外面的分組括號()
也是多餘的;
最後,全形符號在正規表示式中是沒有必要使用反斜槓\
進行轉義的,因此\【|\】|\「|\」|\;|\:
可以寫作【|】|「|」|;|:
,這樣閱讀更方便些。
當前,這裡最好的表示方法還是使用RegExp物件,可以大大簡化我們的正規表示式,同時更利於維護。如下:
// 全形標點字元們
var strPunct = '!()【】『』「」《》“”‘’;:,。?、';
// 使用管道符連線
var regPunct = strPunct.split('').join('|');
// 此時的正規表示式
new RegExp('['+ regPunct +'] +| +['+ regPunct +']', 'g');複製程式碼
對吧,是不是簡單也易讀多了。
其中,空格我直接用的普通空格字元進行匹配的,而不是\s
,因為,我不想把換行符也過濾掉。
當我們的正規表示式內容包含變數的時候,可以藉助new RegExp()
來實現。
5. 不重複使用標點符號
這裡的標點指中文標點,因為英文標點不重複,有些不切實際,例如空字串''
,就是合法的重複標點。
原本的實現:
/(\~|\`|\!|\[|\]|\{|\}|\;|\:|\"|\'|\,|\<|\.|\>|\/|\?|\!|\·|\【|\】\「\」|\;|\:|\“|\”|\,|\《|\。|\》|\、|\?)\1+/g複製程式碼
洋洋灑灑好長,我們可以簡化下:
new RegExp(`(${regPunct})\\1+`, 'g')複製程式碼
就是不使用重複中文標點了。其中,這裡的\1
有必要好好說下。
\1
表示捕獲匹配,表示捕獲第一個分組括號中匹配的值,你可以理解為代稱。在正規表示式中,每一個分組括號()
都自帶一個看不見的序號,從前往後依次是分組一,分組二,分組三……
這裡的\1
就表示匹配的第一個標點,後面跟了個+
則表示,這裡重複標點2個或多個都匹配。
捕獲分組不僅存在於正規表示式中,當我們使用replace
方法進行正則替換的時候,也存在與替換方法中,使用美元符號$
外加數字表示,例如前後空格過濾trim()
方法的簡易polyfill:
if (!''.trim) {
String.prototype.trim = function () {
// $1表示第一個()中匹配的值
return this.replace(/^\s*(.*?)\s*$/, '$1');
};
}複製程式碼
其中'$1'
並不是替換成字串$1
意思,而是替換成第一個()
中匹配的值,在這裡表示首尾空格以外的值。
如果我們需要對捕獲分組內容進行額外處理,可以把第二個引數作為function
處理,例如:
this.replace(/^\s*(.*?)\s*$/, function (matches, $1) {
// matches表示完整匹配內容(包括前後空格)
// $1則表示第一個()中匹配的值
// 此時就可以對$1進行處理,返回我們需要的值
})複製程式碼
6. 破折號前後需要增加一個空格
這個超easy:
/(\S(——)|(——)\S)/g複製程式碼
這裡幾個括號都是多餘的,直接下面這樣既可:
/\S——|——\S/g複製程式碼
7. 使用全形中文標點
/([^A-Za-z][\~|\`|\!|\[|\]|\{|\}|\;|\:|\"|\'|\,|\<|\.|\>|\/|\?][^A-Za-z])/g複製程式碼
實際匹配要比這個複雜,因為這個和最後一個應為整句需要使用半形標點大量衝突。所以這裡規則要細化,前後至少需要出現中文,半形標點才轉換為全形,否則認為是英文整句,不處理,保持忽略。
於是,我經過修改變成下面這樣:
var strPunctHalf = '!()[]"\';:,.?';
// 不同於全形字元,半形字元需要加轉義
var regPunctHalf = strPunctHalf.split('').join('|\\');
// 此時的正規表示式
new RegExp(`[\u4e00-\u9fa5][a-z]*( *[${regPunctHalf}] *)|( *[${regPunctHalf}] *)[a-z]*[\u4e00-\u9fa5]`, 'gi');複製程式碼
8. 數字使用半形
也就是需要匹配10個全形數字,鬆鬆的,沒什麼好說。
/[\uFF10-\uFF19]+/g複製程式碼
9. 遇到完整的英文整句,其內容使用半形標點
/(\「[A-Za-z\s\~|\`|\!|\;|\:|\"|\'|\,|\<|\.|\>|\/|\?]*[\!|\·|\;|\:|\“|\”|\,\。|\、|\?][^\」]*\」)|(\《[A-Za-z\s\~|\`|\!|\;|\:|\"|\'|\,|\<|\.|\>|\/|\?]*[\!|\·|\;|\:|\“|\”|\,\。|\、|\?][^\》]*\》)/g複製程式碼
完整的英文整句的重要特徵是單詞與空格,考慮到標點之後可能會有空格,於是,優化了下:
new RegExp(`([a-z]+[${regPunct}|\\s])+[a-z]*([${regPunct}|\\s][a-z]+)+`, 'gi')複製程式碼
足以現在滿足大多數的場景。
三、線上驗證工具出爐
現在我們有了基礎技術,但還不足以作為工具,作為產品讓更多人使用,因為在控制檯輸出這種事情非程式設計師以外的人是做不來的。
所以,可以將其變成視覺化工具。
最後一個回答者@wingmeng參照了 @XboxYan 的一些思路除了輸出驗證結果,還輸出了處理後的正確排版。
於是,站在這兩位的肩膀上,我熬夜搞出了一個“翻譯內容格式檢驗工具” —— check.html
直接輸入內容,就可以高亮標記錯誤的翻譯排版,同時顯示正確的結果。
可以大大減輕校對時候的工作量,如果你也有參加掘金的翻譯計劃,或者自己平時翻譯文章什麼的,這個小工具可以試一試,雖不能100%完美解決各種排版問題,但至少可以解決大部分的問題,非常划算。
四、正則很爛也能實現的傻白甜方法
回到題目之外,在實際專案中遇到這樣的排版驗證需求,本質上就是用工程化的手段讓普通人也能發現一些翻譯排版的問題,因此,實際上,就算你正規表示式非常的爛,甚至一點也不會,你也能弄出一個可以使用的工具來解放生產力。
首先通過互動設計手段來降低我們實現的成本:
- 我們沒有必要一次性所有的規則一次性匹配,我可以讓使用者選擇具體哪條規則,到時候一個一個規則匹配就好了,使用者完全不care的;
- 沒有必要匹配所有的排版規則,比方說最後單詞英文後面是全形標點,整句英文還使用半形標點,有些難度,也有些蛋疼,我們大不了忽略。因為本來就是輔助工具,沒必要面面俱到。
然後,判斷什麼型別的字元,可以不用走高大上、學習成本較高的正規表示式,可以試試基於charCode值判斷,例如:
// 判斷字元型別
String.prototype.kind = function () {
if (strPunct.indexOf(this) != -1) {
return 'punct';
}
var code = this.charCodeAt(0);
if (code >= 65296 && code <= 65305) {
return 'num-full';
}
if (code > 256) {
return 'zh';
}
if (code >= 48 && code <= 57) {
return 'num';
} else if (code >= 65 && code <= 90) {
return 'en-up';
} else if (code >= 97 && code <= 133) {
return 'en-low';
}
return 'unknown';
};複製程式碼
這個要好理解的多,不同型別的字串是有著特定的charCode區間範圍的。
接下來事件就很簡單了,我們只要遍歷需要檢測的文字內容,判斷一下當前字元和下一個字元是否不符合要求就可以了。比方說“中英文之間需要增加空格”,遍歷的時候,如果當前字元是中文,同時上一個字元和或者下一個字母是英文,則返回並高亮標記。
驗證就結束了,一個迴圈+字元判斷,就算只學習一個月的JavaScript也能夠實現,這就是“傻白甜”實現方式。
眼見為實,為了方便大家學習,我專門做了個demo頁面:check-foo.html
例如,我點選第一個檢查按鈕,成功高亮的不合要求的排版內容:
就一個迴圈外加一大堆if語句,一丁點正規表示式都沒實現,就實現了看上去很難的翻譯排版校驗工具,而且多半比正則實現更穩健。
頁面原始碼可以直接在這個專案的docs目錄中找到:github.com/zhangxinxu/…
五、升職加薪與技術強弱沒有直接關係
接下來要引出本次直播答疑最有價值的一個議題,是有關職業發展的,那就是升職加薪與技術強弱沒有直接關係。
很多人都有這樣一個錯誤的認識,因為自己的技術越強,薪資就越高,職位就越高,實際上不是這樣子的,並沒有直接的關係。職位的高低是與你對團隊,對公司產生的價值相呼應的。作為一個技術人員,就算你的技術能力並不是非常的強,也能產生非常高的價值,關鍵在於認知與意識。
舉個例子,某公司某團隊打算加入掘金的翻譯計劃,來提高團隊的影響力。
其中有個很重要的環節,那就是校驗,而校驗這種工作往往都是團隊的負責人來做這個事情,負責最後的把關,免得出現一些意外的風險。這就問題來了,通常團隊的負責人都是很忙的,要靠肉眼去識別那些翻譯中出現的小錯誤,那是非常費心費力費神費時的事情,久而久之,體驗會變得非常糟糕。
這個的團隊裡面有兩個前端,一個技術非常紮實,正則玩得666,但總是沉浸在自己的技術世界裡,專注於手頭上的事情,以自己程式碼質量世界第一為自豪。另外一個技術一般般,正則玩得233,但是,其敏銳發現翻譯排版校驗走人工是非常低效的一件事情,於是當機立斷決定做了一個工具,可以幫助大家快速的發現一些排版上的問題,解放生產力。雖然技術一般般,但他活用自己已經掌握的一些知識,通過良好的互動設計降低實現成本,用“傻白甜”的方式把這個東西給做出來了,別人一用,嘿,還行。
很顯然,這件事情上,那個技術一般般的人創造的價值更大,而且大的非常明顯。一個人技術再強,那解放只是你一個人的生產力,團隊還有其他好幾十號人並沒有任何提升;但是如果你做出一個可以讓大家都能提高生產力的工具,就算你技術一般般,但是你對這個團隊產生的價值是非常深遠的。翻譯這種事情,全國有那麼多人蔘加,如果你把這個工具開源出去,對團隊帶來的影響力要遠比翻譯一兩個文章更高。
對比下:技術強的人自己生產力很高,然後沒有然後;技術一般般的人讓團隊其他人生產力提高,同時通過開源工具給團隊帶來了影響力。如果你是領導,如果你是boss,你會提拔哪一個人?顯然,只要領導不是智障,都會升職加薪是後面那個技術一般的人!
很多技術人員一直沒有意識到這個問題,經常會抱怨,那個人技術那麼爛,為什麼這次升職晉升的是他?拜託,升職晉升是看貢獻,不是看你一個人的技術水平,這個和搞科研是不一樣的,企業是商業機構。
所以,大家一定要扭轉意識,敏銳捕捉可以產生巨大價值的場景,不要只盯著自己的一畝三分地。工作中所有同事遇到的不爽、不悅,所有那些重複人力的場合,都是一次很好的提高你績效的機會,就算你技術一般般,也能產生非常大的價值;如果你本身技術就很強,那更要抓住這樣的機會,否則機會留給了別人,最後怎麼被踩下去都不知道,那更慘!
六、關於本次直播
本次直播有錄播,因為加班錯過的小夥伴可以去圍觀,地址是:www.bilibili.com/video/av411…
歡迎提出各種意見。
關於群小測
每週三下班後會在微信粉絲群公佈一道小測題,每週六上午10:00-11:00會以直播形式對大家的解答進行答疑。
目前一群已滿,二群還有坑我,想入的可以加我微信好友 zhangxinxu-job,我拉你們進去,備註“入群”,然後附上你們的姓名,方便我備註。
(完)