阅读 928

粉丝群第27期JS小测直播答疑文字版

原文地址: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]。我们如果想要匹配任意字符,可以使用[\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%完美解决各种排版问题,但至少可以解决大部分的问题,非常划算。

四、正则很烂也能实现的傻白甜方法

回到题目之外,在实际项目中遇到这样的排版验证需求,本质上就是用工程化的手段让普通人也能发现一些翻译排版的问题,因此,实际上,就算你正则表达式非常的烂,甚至一点也不会,你也能弄出一个可以使用的工具来解放生产力。

首先通过交互设计手段来降低我们实现的成本:

  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,我拉你们进去,备注“入群”,然后附上你们的姓名,方便我备注。

(完)


关注下面的标签,发现更多相似文章
评论