粉絲群第27期JS小測直播答疑文字版

張鑫旭發表於2019-03-02

原文地址:www.zhangxinxu.com/wordpress/2…

一、題目

小測題目

就是寫一個檢驗方法,輸出不符合排版規則的內容。

翻譯規則地址:譯文排版規則指南

補充細節:

  1. 數字與單位之間需要增加空格只需要考慮後面是大寫字母的場景,因為程式碼中經常會有類似20px這樣的處理,中間不能有空格。
  2. 標點不能重複專指中文標點,英文標點不考慮,在程式程式碼中標點重複很常見。

對於字元內容的格式驗證,自然就是正規表示式了,因此,本期的題目主要目的之一是學習正規表示式。

正規表示式規則在所有的語言中都是通用的,除了細節上上有些差異,需要的是一模一樣的,所以學會了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,還有im。其中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]。我們如果想要匹配任意字元,可以使用[dD]這種寫法。
  • w表示匹配數字、字母和下劃線;W表示匹配數字、字母和下劃線以為的其他字元。
  • s表示匹配空格、製表符和換行符;換成大寫的S則表示除了以外空格、製表符和換行符其他字元。

  • 表示換行。

3. 數字與單位之間需要增加空格

/d[A-Za-z]+/g複製程式碼

題目這個需求是不合理的,有些數字和單位之間是不能加空格的,有趣技術文章翻譯,必定包含大量的程式碼,例如10px等,顯然不能加空格。因此,可以認為數字和大寫字母之間需要空格。

因此,正則可以調整為:

/d[A-Z]+/g複製程式碼

4. 全形標點與其他字元之間不加空格

/([sS]{2}[!|·|【|】|「|」|;|:|“|”|,|《|。|》|、|?]s+)|[s+(!|·|【|】|「|」|;|:|“|”|,|《|。|》|、|?)[sS]{2}]/g複製程式碼

這個就相當長了,很多人看了會覺得是天書一樣,其實很簡單,也有不少優化和改進空間。

首先,前後的[sS]{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-zs~|`|!|;|:|"|`|,|<|.|>|/|?]*[!|·|;|:|“|”|,。|、|?][^」]*」)|(《[A-Za-zs~|`|!|;|:|"|`|,|<|.|>|/|?]*[!|·|;|:|“|”|,。|、|?][^》]*》)/g複製程式碼

完整的英文整句的重要特徵是單詞與空格,考慮到標點之後可能會有空格,於是,優化了下:

new RegExp(`([a-z]+[${regPunct}|\s])+[a-z]*([${regPunct}|\s][a-z]+)+`, `gi`)複製程式碼

足以現在滿足大多數的場景。

三、線上驗證工具出爐

現在我們有了基礎技術,但還不足以作為工具,作為產品讓更多人使用,因為在控制檯輸出這種事情非程式設計師以外的人是做不來的。

所以,可以將其變成視覺化工具。

最後一個回答者@wingmeng參照了 @XboxYan 的一些思路除了輸出驗證結果,還輸出了處理後的正確排版。

輸出正確結果

於是,站在這兩位的肩膀上,我熬夜搞出了一個“翻譯內容格式檢驗工具” —— check.html

直接輸入內容,就可以高亮標記錯誤的翻譯排版,同時顯示正確的結果。

視覺化翻譯內容校驗工具

可以大大減輕校對時候的工作量,如果你也有參加掘金的翻譯計劃,或者自己平時翻譯文章什麼的,這個小工具可以試一試,雖不能100%完美解決各種排版問題,但至少可以解決大部分的問題,非常划算。

四、正則很爛也能實現的傻白甜方法

回到題目之外,在實際專案中遇到這樣的排版驗證需求,本質上就是用工程化的手段讓普通人也能發現一些翻譯排版的問題,因此,實際上,就算你正規表示式非常的爛,甚至一點也不會,你也能弄出一個可以使用的工具來解放生產力。

首先通過互動設計手段來降低我們實現的成本:

  1. 我們沒有必要一次性所有的規則一次性匹配,我可以讓使用者選擇具體哪條規則,到時候一個一個規則匹配就好了,使用者完全不care的;
  2. 沒有必要匹配所有的排版規則,比方說最後單詞英文後面是全形標點,整句英文還使用半形標點,有些難度,也有些蛋疼,我們大不了忽略。因為本來就是輔助工具,沒必要面面俱到。

然後,判斷什麼型別的字元,可以不用走高大上、學習成本較高的正規表示式,可以試試基於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,我拉你們進去,備註“入群”,然後附上你們的姓名,方便我備註。

(完)

相關文章