一道小小的题目引发对javascript支持正则表达式相关方法的探讨

本文发布在我的博客一道小小的题目引发对javascript支持正则表达式相关方法的探讨
许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。、


以前对于正则是非常惧怕的,因为看不懂和学不会。但最近项目中频繁的使用到了正则,因此强迫自己去学习了解,慢慢的体会到了他的魅力与强大。当然学习正则初入门的时候有些枯燥难懂,但越学越觉得轻松。本文不准备说关于正则本身的事儿,而是说一说关于javascript中关于正则的几个方法中被很多人忽略的地方。

工具

说到正则,很多人都是从抄到改到自己写,这个过程可能有时候很漫长。如一些工具能帮助你快速分析和学习正则,那么学习的过程你肯定要轻松得多。下面我推荐两个我经常使用的正则在线可视化工具,正则可视化工具图解符合铁路图规律(其实不明白什么是铁路一样很容易看懂,只是一些细微的地方和我们的常规思维有点差别)。

  • regexper 我最常用的一个,个人觉得UI做得比其他好
  • regulex 备选,他有一个很舒心的功能,可以提供一段js,嵌套到你的网站,生成正则可视化图

一道小小的题目

这道题目是在群里日常闲聊时,公司同事抛出来的,具体是出自哪里本人没去考察。先先说说题目:

写一个方法把一个数字末尾的连续0变成9,如1230000变成1239999

一道很简单的题目,直接正则就能搞定,也许你会写:

function zoreToNine(num){
    return (num + '').replace(/0/g,9);
}
//或者
function zoreToNine(num){
    return (num + '').replace(/[1-9]0+$/,9);
}

这也是此题的陷阱所在,按照上面的方法,1023000就会被转化成1923999,这样是不符合要求的,所以改进一下:

function zoreToNine(num){
    return (num + '').replace(/[1-9]0+$/,function($1){
        return $1.replace(/0/g,9);
    });
}
zoreToNine(1223000); //1223999
zoreToNine(1023000); //1023999

关于这个问题的解决方案@微醺岁月同学提供了一种,位置匹配的方法,简单了很多,厉害!

"12300100000".replace(/0(?=(0+$)|\b)/g,9); //12300199999

当然解决问题的方法很多,不一定非要用正则,还完全可以使用纯算术的方法实现,大家有兴趣可以尝试,闲话少说进入这次的主题:javascript支持正则表达式相关方法,注意并不是正则对象的方法。
上述方法使用了正则,有趣的是在回调函数里有一个$1,这个$1到底是什么?所有的匹配规则匹配后都有$1这个变量么?...一连串的问题,以前我从来没有去追探过,趁着昨个比较空闲,去追探了一番,并在今天整理了一下,写下此文记录。

主角

javascript中正则对象有三个方法:testexeccompile,但是此次的主角并不是它们!我们讨论的是能够使用正则表示的相关方法:searchmatchreplacesplit,注意它们都是String对象的方法,使用它们必须要是String类型.

replace(rule[regexp/substr], replacement)

replace是一个用于替换字符串的方法,虽然看似简单,但是它隐藏的机关也是常常被人忽略。具体分析一下它的特点:
它接收两个参数
无副作用不影响原始变量
返回被改变的字符串(一定是字符串类型)

定义一些变量,方便全文取用。

let a = '12309800', b = '12309800[object Object]', b = '12309800{}';

参数rule

在一般情况,rule参数一般是正则、字符串、数字。
如果是字符串,将会在匹配到第一个符合条件的目标,结束方法;
如果是正则,则按照正则的规则进行匹配

//匹配第一个0替换成5
a.replace(0,5); //'12359800'
//匹配所有的0替换成5
a.replace(/0/g,5); //'12359855'

参数replacement

在一般情况,replacement参数是字符串、数字、者回调。

包含$的字符串

当参数rule为正则,并且正则至少包含有一对完整的()时,如果replacement包含有$的字符串,那么对于$n(n为大于0的整数,n的长度取决于正则中括号的对数),会被解析成一个变量。但是也仅仅只是作为一个变量,无法在字符串中进行计算,此时更类似特别的字符串模板变量。

一般情况下,$n中n的长度取决于正则中括号的对数,$1表示第1对括号匹配的结果,$2表示第2对匹配的结果...在正则所有的括号对中,左括号出现在第几个位置(或者说从左往右),则它就是第几对括号,以此类推。姑且我们把这种规则成为正则匹配分割规则(ps:这完全是我自己取的一个名字,方便文章后面使用和记忆)。

a.replace(0,'$0'); //'123$09800'
a.replace(/00/g,'$0'); //'123098$0'
a.replace(/[1-9]0+$/,'$1'); //'12309$1'
a.replace(/([1-9](0+$))/,'$1'); //'12309800',此时$1为[1-9](0+$)匹配到的内容,$2为0+$匹配到的内容
a.replace(/([1-9])(0+$)/,'$1'); //'123098',此时$1为[1-9]匹配到的内容,$2为0+$匹配到的内容
a.replace(/([1-9])(0+$)/,'$1*$2'); //'123098*00',此处的$1和$2不会安照期待的情况进行乘法计算,要进行计算可以用回调

请注意:虽然目前参数replacement中携带有$n仍然能正常使用,但是这种方式已经不被规范所推荐,更应该使用回调来完成这个操作。这一点谢谢@lucky4同学的指出

如果正则中包含有全局匹配标志(g),那么每次匹配的都符合上述规则

回调函数

先看例子:

a.replace(/[1-9]0+$/,function(){
    console.log(arguments); //["800",5,"12309800"]、
});
a.replace(/([1-9])0+$/,function(){
    console.log(arguments); //["800","8",5,"12309800"]
});
a.replace(/([1-9])(0+$)/,function(){
    console.log(arguments); //["800","8","00",5,"12309800"]
});
a.replace(/(([1-9])(0+$))/,function(){
    console.log(arguments); //["800","800","8","00",5,"12309800"]
});

回调函数的arguments数组部分组成:[完整匹配的字符串,$1,$2,...,$n,匹配的开始位置,原始字符串],$1...$n表示每个括号对的匹配,规则和前面的相同。
所以有一下规律:

let arr = [...arguments], len = arr.length;
(len >= 3) === true;
arr[0] = 完整匹配的字符串;
arr[len-2] = 匹配的开始位置;
arr[len-1] = 原始字符串;

注意:除了匹配的开始位置是Number类型外,其余的都是String类型

非常规类型参数

如果参数类型不是上述两种情况,会发生什么呢?看看下面的例子:

a.replace(0,null); //123null9800
a.replace(0,undefined); //123null9800
a.replace(0,[]); //1239800
a.replace(0,Array); //1230,3,123098009800
b.replace({},5); //123098005
c.replace({},5); //'12309800{}'
a.replace(0,{}); //123[object Object]9800
a.replace(0,Object); //12309800

由上面的例子可以看出,如果非正则也非字符串,则有以下规则:
null变量,则会转换成'null'字符串;
undefined变量,则会转换成'undefined'字符串;
[]变量,则会调用join()方法转换成字符串,默认以,分割,值得注意的是空数组将会被转换成空字符串(没有任何字符),通常会被匹配源字符串的开始位置(默认开始位置为空字符串);
'Array'变量,则会先转成成一个匹配的数组,形如[完整匹配的字符串,$1,$2,...,$n,匹配的开始位置,原始字符串],然后对它调用join()方法转换成字符串,默认以,分割;
{}变量,则会调用Object.protype.toString.call()方法把{}转换成[object Object];
Object变量,则貌似什么都没做

虽然可以传入这些非正常参数,但大多数情况下这些类型的参数对实际是毫无意义的,所以不建议传入以上类型的参数。同上面的正则匹配分割规则一样,为了方便使用称呼,姑且我把上面的转换规则称为正则匹配参数转换规则

match(rule[regex/substr])

match方法可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。
该方法类似indexOflastIndexOf,但是它返回指定的值,而不是字符串的位置;

参数

参数的传递除了常规的正则和字符串以外,其余所有类型的参数都会按照上述的正则匹配参数转换规则转换成字符串形式来匹配。

返回值

返回值根据传入的参数类型和规则的不同,返回的内容不同,但总体来说,它是返回一个对象,而不是索引,如果没匹配到任何符合条件的字符串,则返回null

非全局匹配正则

如果匹配规则是一个非全局匹配规则,那么,它此时的返回值是一个伪数组对象(likeArr),形如:[一个展开的匹配到的字符串数组, 匹配到的字符串位置, 原始字符串],它有如下规律:

var likeArr = a.match(regex);
likeArr[0] = 匹配到的字符串;
likeArr[1...n] = 正则匹配分割规则匹配的字符串;
likeArr.index = 匹配到字符串的位置
likeArr.inupt = 原始字符串

看例子:

a.match(/[1-9]0+$/); //[0:'800',index:5,input:'12309800']
a.match(/([1-9])0+$/); //[0:'800',1:'8',index:5,input:'12309800']
a.match(/[1-9](0+$)/); //[0:'800',1:'00',index:5,input:'12309800']
a.match(/([1-9])(0+$)/); //[0:'800',1:'8',2:'00',index:5,input:'12309800']

全局匹配正则

如果匹配规则是一个全局匹配规则(正在携带有g标志),那么,它此时的返回值是一个数组对象(arr),形如:[匹配到的字符串数1,匹配到的字符串数2,匹配到的字符串数3];
看例子:

a.match(/[1-9]0/); //[0:'30',index:2,input:'12309800']
a.match(/[1-9]0/g); //[0:'30',1:'80']

search(rule[regex/substr])

search方法用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串。
stringObject中第一个与rule相匹配的子串的起始位置。如果没有找到任何匹配的子串,则返回-1
注意:

  • search方法不执行全局匹配,它将忽略标志g
  • 忽略regexplastIndex属性,总是从字符串的开始进行检索,这意味着它总是返回stringObject的第一个匹配的位置

同样,search可以传入任何参数类型,它会遵循正则匹配参数转换规则进行转换

split(rule[regex/substr],len)

这个方法就不用多说,很常用的字符串分割方法。
第二个参数的作用就是限制返回值的长度,表示返回值的最大长度

当然,它依然可以传入任何参数类型,会遵循正则匹配参数转换规则进行转换

有一段加密的后的密码,我们需要分离出字符串'12a344gg333tt445656ffa6778ii99'中的前三组数字,通过某种计算才能得出正确的密码

'12a344gg333tt445656ffa6778ii99'.split(/[a-zA-Z]+/g,3);//['12','334','333']

最后

写了这么多,突然发现以前仅仅是在用这些方法,了解得很不够深入。越是学习才发现其中的奥秘!学无止境,与诸君共勉!
以上内容如有错误之处,希望诸君不吝指出!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容

  • 从匹配中返回值 Match 对象 成功的匹配总是返回一个 Match 对象, 这个对象通常也被放进 $/ 中, (...
    焉知非鱼阅读 1,772评论 0 1
  • 第5章 引用类型(返回首页) 本章内容 使用对象 创建并操作数组 理解基本的JavaScript类型 使用基本类型...
    大学一百阅读 3,204评论 0 4
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,566评论 18 139
  • 作者:尹木子 十点五十五分,我提前推开门,进了一...
    尹木子阅读 218评论 0 1
  • 根据目前执行状况做一些调整 规则: 周 期:本期至2017年12月31日 监督媒介:早睡早起微信群 使用工具:各...
    书童阿雷阅读 409评论 0 1