关于前端:面试官我来考一下正则吧

13次阅读

共计 8814 个字符,预计需要花费 23 分钟才能阅读完成。

不晓得大家是不是跟我一样,对正则有一种莫名微妙的恐怖,日常开发遇到要正则匹配,百度 google 一把梭哈,然而面试的时候面试官问这个问题的时候,心里就开始懊悔为什么当初不好好学。这也是大家在筹备面试的过程中最容易疏忽的一个知识点,所以面试成功率很低,明天,就让咱们来克服心田的恐怖,看看口试面试中,面试官能把正则问出什么花来!

RegExp 对象

RegExp 是 js 内置的对象,用来进行正则匹配。
有两种办法实例化 RegExp 对象,别离是:

字面量:

const reg = /hello/
const a = 'helloworld'.replace(reg,'HELLO')  // HELLOworld 

构造函数

const reg = new RegExp('hello')
const a = 'helloworld'.replace(reg,'HELLO')   //HELLOworld

应用 replace 办法,如果被第二个参数字符串替换掉,那阐明匹配上了,而且前端日常开发很多时候就是应用 replace 对正则匹配到的字符进行操作的,正则多跟着例子打一打,就会变得越来越纯熟啦!

修饰符

i 执行对大小写不敏感的匹配。

g 执行全局匹配(查找所有匹配而非在找到第一个匹配后进行)。

m 执行多行匹配。

例如:

const reg = /hello/g
const a = 'helloworldhello'.replace(reg,'HELLO')  // HELLOworldHELLO 

const reg1 = /hello/i
const b = 'hellOworld'.replace(reg1,'HELLO')  // HELLOworld 

元字符

正则表达式由两种根本字符类型组成

  • 原义文本字符 a,b,c…(就代表字母自身的含意,例如下面举例的 hello, 指的就是要匹配字符串中的 hello, 没有其余特地含意)
  • 元字符 含有非凡含意的字符

最简略的正则表达式只有字母数字就能够实现了,比方下面的那个例子,然而咱们晓得,往往没有那么简略。

看看上面这道题:

const reg = /\d\B\D\w{2}\W{2}\s\S\b/
const a = '1a_a(& s'.replace(reg,'匹配胜利')

你能说出下面那条表达式的后果吗??
说不上来?没关系,让咱们来好好说道说道。

单字符

除了字母和数字,咱们想要匹配特殊字符时,就得请出咱们第一个元字符 \,它是转义字符字符,顾名思义,就是让其后续的字符失去其原本的含意。举个例子:

我想匹配 * 这个符号,因为 * 这个符号自身是个特殊字符,所以我要利用本义元字符 \ 来让它失去其原本的含意:

所以, 应用 /*/ 就是单纯的要匹配 * 这个字符

const reg = /\*/g 
const a = '*world*'.replace(reg,'HELLO')  //HELLOworldHELLO

如果原本这个字符不是特殊字符,应用本义符号就会让它领有非凡的含意。咱们经常须要匹配一些特殊字符,比方空格,制表符,回车,换行等, 而这些就须要咱们应用转义字符来匹配。这个就须要咱们去记忆啦!记住本义符号的形式是 \,不要记反啦!

字符类和范畴类

那如果我想匹配 123 其中的一个字符都能够,那么我就须要应用中括号 [ ], 在正则表达式里,汇合的定义形式是应用中括号 []。如 /[123]/ 这个正则就能同时匹配 1,2,3 三个字符。

const reg = /[123]/
const a = '1'.replace(reg,'HELLO')  //HELLO
const b = '2'.replace(reg,'HELLO')  //HELLO

那如果我想匹配所有的数字怎么办呢?从 0 写到 9 显然太过低效,所以元字符 - 就能够用来示意区间范畴,利用 /[0-9]/ 就能匹配所有的数字, /[a-z]/则能够匹配所有的英文小写字母。留神,正则表达式是严格辨别大小写的。

const reg = /[0-9]/
const reg1 = /[a-z]/
const a = '8'.replace(reg,'HELLO')  //HELLO
const b = 't'.replace(reg1,'HELLO') //HELLO
const c = 'T'.replace(reg1,'HELLO') //T, 因为匹配不到,所以 replace 没有替换

那如果咱们就想匹配数字和 - 呢?很简略,用 \ 来本义 - 使他失去他本人的非凡含意就能够啦

const reg = /[0-9|\-]/g
const d = '2020-01'.replace(reg,'H') //HHHHHHH

请问你学废了吗?

除了在汇合中用 - 对要匹配的字符范畴进行定义,咱们还有更简便的形式,应用正则表达式中的元字符。

罕用元字符定义如下:

感激掘友 @scq000 提供的记忆图 文章传送门:https://juejin.cn/post/684490…

上面例子没有全局匹配,只匹配到第一个符合条件的就进行匹配

const reg = /\d/
const a = '8'.replace(reg,'HELLO')  //HELLO 
const reg1 = /\D/
const b = '8'.replace(reg1,'HELLO')  //8   
const reg2 = /\w/
const c = 't'.replace(reg2,'HELLO') //HELLO
const reg3 = /\W/
const d = 't'.replace(reg3,'HELLO') //t

量词

当初如果须要匹配一个间断呈现 10 次数字的字符串,依照咱们下面的学过的,能够用

const reg = /\d\d\d\d\d\d\d\d\d\d/

来进行匹配,那如果要匹配呈现 10000 次呢?那岂不是要咱们写到手酸眼花,这个时候就能够应用正则表达式的量词了。

感激掘友 @scq000 提供的记忆图 文章传送门:https://juejin.cn/post/684490…

所以依据这些量词的定义,咱们来实操一下怎么应用吧!

const reg = /\d{3}/
const a = '1234'.replace(reg,'HELLO')  //HELLO4
const reg1 = /\D?/
const b = '8'.replace(reg1,'HELLO')  //HELLO8   
const c = 'w'.replace(reg1,'HELLO')  //HELLO
const reg2 = /\d*/
const d = '77782837464838'.replace(reg2,'HELLO') //HELLO
const e = 'w'.replace(reg2,'HELLO') //HELLOw
const reg3 = /\d+/
const f = '77782837464838'.replace(reg3,'HELLO') //HELLO
const g = 'w'.replace(reg3,'HELLO') //w

边界

当初有这么一个字符串,'This is a student'
咱们想要匹配字符串中的所有的 is, 那这个时候如果用 /is/ 你会发现 This 中的 is 也被匹配到了

const reg = /is/g
const a = 'This is a student'.replace(reg,'HELLO') //ThHELLO HELLO a student

这个时候就须要咱们的边界字符出场了,常见的边界字符有

举个例子,咱们要匹配 is,那这个 is 左右都是单词的边界,所以

const reg = /\bis\b/g
const a = 'This is a student'.replace(reg,'HELLO') //This HELLO a student

那如果咱们要匹配 This 中的 is 呢?

const reg = /\Bis\b/g
const a = 'This is a student'.replace(reg,'HELLO') //ThHELLO is  a student

所以你能够了解 \b 和 \B 就是来限定要匹配的字符是不是在咱们单词的最边边就能够了。

如果有这么一个字符串 thisisthebest, 咱们只想匹配 this 中的 th, 你能够用 ^ 来限定匹配的字符就是整个字符串的结尾

const reg = /^th/
const a = 'thisisthebest'.replace(reg,'HELLO') //HELLOisisthebest

$ 也是相似的,限定匹配的字符在整个字符串的结尾

const reg = /test$/
const a = 'testisatest'.replace(reg,'HELLO') //testisaHELLO

const reg2 = /^test./
const b = 'testisatest'.replace(reg2,'HELLO') //HELLOsatest

最初看下咱们下面的例题:

const reg = /\d\B\D\w{2}\W{2}\s\S\b/
const a = '1a_a(& s'.replace(reg,'匹配胜利')

上述的正则表达式翻译过去就是:数字 1 个 非单词边界 非数字 1 个 字母 2 个 非字母 2 个 空白字符 1 个 非空白字符一个 单词边界

后果就是匹配胜利~~~

子表达式

那么正则最根本的咱们曾经大略都学完了,是不是须要来点面试题开开胃?
请写出一个正则表达式,反对匹配如下三种日期格局:

2016-06-12

2016/06/12

2016.06.12

这个时候可能就会有人写出了相似这样的正则

const reg = /\d{4}[\-\/\.]\d{2}[\-\/\.]\d{2}/

很遗憾的通知你,这个答案是错的。

const reg = /\d{4}[\-\/\.]\d{2}[\-\/\.]\d{2}/
const a = '2020-02-02'.replace(reg,true)  //true
const b = '2020-02/02'.replace(reg,true)  //true

题目中显著要求是要有对立的分隔字符,所以不合乎题目要求。既然要有对立的分隔字符,就要求能有一种机制来捕捉第一个分隔符,而后第二个分隔符与其保持一致。此时就须要反向援用了。

分组

() 能够达到分组的性能,使量词作用于分组,()包含的表达式就能够认为是一个组,例如:

const reg = /(is){3}/g
const a = 'isisisis'.replace(reg,true)  //trueis

如果分组只用来做匹配,那只能施展他性能的百分之十,更大的作用的用来做反向援用。

反向援用

回到咱们的题目,匹配相似于 2016-06-12,2016/06/12 这样的日期字符串,还有一个暗藏的条件,就是我月和日之间的分隔符须要和第一个分隔符保持一致,不然就会呈现 2016-02/09 这样的字符串也被匹配上,那这个时候就应该应用到反向援用,也就是在咱们的正则的前面局部须要援用后面曾经匹配到的子字符串。你能够把它设想成是变量,反向援用的语法像 \1,\2,…., 其中 \1 示意援用的第一个子表达式(第一个分组匹配到的),\2 示意援用的第二个子表达式(第二个分组匹配到的),以此类推。而 \0 则示意整个表达式。

所以匹配 2016-06-12,2016/06/12,2016.06.12 这三种日期的正则表达式咱们能够写成:

| 示意或,示意匹配其中的一个

/\d{4}(-|\/|\.)\d{2}\1\d{2}/

记住,反向援用的前提是先用 () 进行分组

很多时候,分组 反向援用 也常被用在字符串替换中,此时就别离用 $1,$2… 来代替匹配到的表达式

例如,咱们须要将 2020-02-10 这样的格局转换为 10/02/2020 这样的格局,咱们就能够这么写👇

const reg = /(\d{4})-(\d{2})-(\d{2})/g
const a = '2020-02-10'.replace(reg,'$3/$2/$1')  //10/02/2020

再来一道类似的题

写一个函数,将驼峰类型的字符串转换为 - 连贯的字符串,例如 getElementById –> get-element-by-id

先思考一分钟再看答案吧!!!

function convert(str){const reg = /([A-Z])/g
    return str.replace(reg,'-$1').toLowerCase()}


convert('getElementById')  //get-element-by-id

写一个函数,将 - 连贯的字符串转换为驼峰类型的字符串,例如 get-element-by-id –>
getElementById

先思考一分钟再看答案吧!!!

function conversion(str) {return str.replace(/-(\w{1})/g, ($1,$2)=>
       $2.toUpperCase())
}
conversion('this-is-good')

先一个判断电话号码的正则表达式

先思考一分钟再看答案吧!!!

const reg = /^1[34578]\d{9}$/

前瞻

有这么一个场景, happyhappily 字符串中我要匹配副词 happily,此时我能够应用前瞻来满足我的需要

const reg = /happ(?=ily)/
const a = 'happyhappily'.replace(reg,'HELLO') //'happyHELLOily'


const reg2 = /\d+(?=%)/
const b = '50%'.replace(reg2,'XX') //XX%

正则表达式从文本头部向尾部开始解析,文本尾部方向,称为‘前’

前瞻就是正则表达式匹配到规定的时候,向文本尾部查看是否合乎断言,断言局部不算进匹配的字符之内。

看不太懂?没关系,咱们看一道题,这道题也是面试常见题:

如何给数字加上千分位分隔符?

有一种 toLocaleString 的办法跟正则无关,大家能够去理解下,咱们来看看用正则如何秒杀。

vartoThousands = function(number) {return (number + '').replace(/(\d)(?=(\d{3})+$)/g,'$1,');
}

vartoThousands(123456789)

依据前瞻,/(\d)(?=(\d{3})+)/ 就是匹配合乎左边有 3 的倍数个数字以上的右边这个数字的字符串,然而咱们发现,它能把 11231234 替换成 1,1231234。

这显著不是咱们要的后果,咱们只想要左边刚好是 3 的倍数个,所以咱们加上 $,$ 限定了合乎前瞻这个断言的最初一个字符肯定是整个字符串的最末端,也就是说 11231234 从头部开始匹配,判断 1 的时候,发现前面尽管有 3 ×2 个数字,但并不是字符串的最开端,所以持续往后面的第二个 1 匹配,此时前面有 3 ×2 个数字,并且是字符串的最开端,所以应用 /(\d)(?=(\d{3})+$)/,这个能把 11231234 替换成 11,231234,这个后果咱们比起上一步更靠近咱们想要的了,但左边的还没替换实现。
所以接下来咱们以同样的匹配规定持续匹配左边的 231234。加上 g 修饰符,于是有 /(\d)(?=(\d{3})+$)/g,最初就是咱们要的后果了。

看懂了吗?

面试真题

验证身份证号码

规定
身份证号码可能为 15 位或 18 位,15 位为全数字,18 位中前 17 位为数字,最初一位为数字或者 X

function isCardNo(number) {var regx = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
    return regx.test(number);
}

给 javascript 的 string 原生对象增加一个名为 trim 的原型办法,用于截取字符串前后的空白字符

String.prototype.trim = function(){return this.replace(/^\s*|\s*$/g,'');
} 
console.log('love  love'.trim())   //love  love

url 的 params

获取 url 中的参数

指定参数名称,返回该参数的值 或者 空字符串
不指定参数名称,返回全副的参数对象 或者 {}
如果存在多个同名参数,则返回数组

function getUrlParam(url, key) {var arr = {};
    url.replace(/\??(\w+)=(\w+)&?/g, function(match, matchKey, matchValue) {if (!arr[matchKey]) {arr[matchKey] = matchValue;
       } else {var temp = arr[matchKey];
           arr[matchKey] = [].concat(temp, matchValue);
       }
    });
    if (!key) {return arr;} else {for (ele in arr) {if (ele = key) {return arr[ele];
            }
        }
        return '';
    }
}

替换空格

来自 leetcode : https://leetcode-cn.com/probl…

请实现一个函数,把字符串 s 中的每个空格替换成 ”%20″。

输出:s = "We are happy."
输入:"We%20are%20happy."

能够间接上 leetcode 下面写下你的代码验证吧!

/**
 * @param {string} s
 * @return {string}
 */
var replaceSpace = function(s) {
    // 正则.,每个空格都用一个 %20 来代替
    return s.replace(/\s/g,'%20')
};

验证是否是电子邮箱

规定定义:

• 以大写字母 [A-Z]、小写字母 [a-z]、数字 [0-9]、下滑线 _、减号 -及点号 .结尾,并须要反复一次至屡次+

• 两头必须包含 @ 符号。

• @ 之后须要连贯大写字母 [A-Z]、小写字母 [a-z]、数字 [0-9]、下滑线_、减号- 及点号.,并须要反复一次至屡次+

• 结尾必须是点号 . 连贯廏至噕位的大小写字母[A-Za-z]{2,4}

var pattern = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;

pattern.test('cn42du@163.com')  //true;
pattern.test('ifat3@sina.com.cn') // true;
pattern.test('ifat3.it@163.com') // true;
pattern.test('ifat3_-.@42du.cn') // true;
pattern.test('ifat3@42du.online') // false;
pattern.test('邮箱 @42du.cn') // false;

填充函数代码使其性能残缺

var str = "您好,<%=name%>。欢送来到 <%=location%>";
function template(str) {// your code}
var compiled = template(str);
// compiled 的输入值为:“您好,张三。欢送来到 xxx”compiled({name: "张三", location: "xxx"});

思考一分钟之后再看答案

var str = "您好,<%=name%>。欢送来到 <%=location%>";

function template(str) {return data => str.replace(/<%=(\w+)%>/g, (match, p) => data[p] || '')
}
var compiled = template(str);
compiled({
    name: "张三",
    location: "xxx"
});  // compiled 的输入值为:“您好,张三。欢送来到 xxx”

实现一个 render(template, context) 办法,将 template 中的占位符用 context 填充。

示例:

var template = "{{name}}很厉害,才 {{age}} 岁"
var context = {name:"bottle",age:"15"}
输出:template context
输入:bottle 很厉害,才 15 岁
要求:级联的变量也能够开展
分隔符与变量之间容许有空白字符

当正则表达式中蕴含能承受反复的限定符时,通常的行为是(在使整个表达式能失去匹配的前提下)匹配尽可能多的字符。

以这个表达式为例:a.*b,它将会匹配最长的以 a 开始,以 b 完结的字符串。

如果用它来搜寻 aabab 的话,它会匹配整个字符串 aabab。这被称为贪心匹配

如果是这样子的话,就会匹配到 {{name}}很厉害,才{{age}} 这整一段内容,不合乎咱们的需要

咱们须要的是 {{name}}和 {{age}} 这段,所以非贪心匹配来匹配到所有的 {{}} 模板

也就是用 .*? 来进行匹配

const render = (template, context) => {const reg = /{{(.*?)}}/g
  return template.replace(reg, (match, p) => {return context[p.trim()] || ''
  })
}

render("{{  name}}很厉害,才 {{age}} 岁", {name: "bottle", age: "15"})

当然,咱们不必非贪心匹配也能够写

const convertTemplate = (template, context) => {const reg = /{{\s*(\w+)\s*}}/g
  return template.replace(reg, (match, p) => {return context[p] || ''
  })
}

convertTemplate("{{  name}}很厉害,才 {{age}} 岁", {name: "bottle", age: "15"})

参考文章

https://juejin.cn/post/684490…

https://juejin.cn/post/684490…

https://juejin.cn/post/684490…

正文完
 0