这么多年你还在怕正则吗?

发表于 2年以前  | 总阅读数:475 次

前言

不会吧不会吧,这么多年你还在怕正则?这就对了,相信你用不了几分钟看完本文,从此正则算个球。

为啥要用正则表达式

俗话说的好,万事开头难。面对火星文似的正则,就是这样的感觉。那么,我们一起来开个头先。 假设我们需要验证一个字符串是否遵循 8 位数字的电话号码格式:000-12345,该格式前面有 3 位数字,然后紧跟着连字符-,连字符后面再跟着 5 位数字。

不使用正则

function isTelephoneNumber(telephoneNumber) {
 if (typeof telephoneNumber !== 'string' || telephoneNumber.length !== 9) {
  return false
 }
 for (let i = 0; i < telephoneNumber.length; i++) {
  let c = telephoneNumber[i]
  switch (i) {
   case 0: case 1: case 2: case 4: case 5:
   case 6: case 7: case 8: case 9:
    if (c < '0' || c > '9') {
     return false
    }
    break
   case 3:
    if (c !== '-') {
     return false
    }
    break
  }
 }
 return true
}
复制代码

使用正则

function isTelephoneNumber(telephoneNumber) {
    return /^\d{3}-\d{5}$/.test(telephoneNumber)
}
复制代码

我的天,使用正则代码变得如此简洁优雅,老板再也不用担心我的代码。

正则的核心

正则就是匹配模式,要么匹配位置,要么匹配字符。划重点,牢记这个核心。 上图代表的是一个字符串:I Love U,箭头就表示我们要匹配的位置,框框里就表示我们要匹配的字符(包括空格)。相信小伙伴们一眼就看明白了。

匹配位置

正则中可以匹配位置的方式如下:

模式 重要说明
^ 匹配开头的位置,当正则有修饰符 m 时(多行文本),表示匹配行开头位置。
$ 匹配结尾的位置,当正则有修饰符 m 时(多行文本),表示匹配行结尾位置。
\b 匹配单词边界,即匹配上面示例中的 i、love、U 前后的位置
\B 匹配非单词边界,与 \b 相反,即匹配上面示例中的 o、v、e 前后的位置
(?=表达式) 正向先行断言(?=表达式),指在某个位置的右侧必须能匹配表达式。例如: 给定字符串我爱你 我爱他 我爱 爱你 我和你,匹配右边是“你”的位置。正则可以这么写: /(?=你)/g 如果非要加上“爱”,/爱(?=你)/g,就会匹配出
(?=表达式) 反向先行断言(?!表达式),指在某个位置的右侧不能匹配表达式。与 (?=表达式)正好相反。同样是上面的例子:/爱(?!你)/g,就会匹配出
(?=表达式) 正向后行断言:(?<=表达式),指在某个位置的左侧必须能匹配表达式。注意: 先行断言和后行断言只有一个区别,即先行断言从左往右看,后行断言从右往左看。
(?<!表达式) 反向后行断言:(?<=表达式),指在某个位置的左侧必须能匹配表达式。

就一张表格,so easy,位置匹配就介绍完了。接下来我们看几个问题巩固一下。请听题: test() 方法执行一个检索,用来查看正则表达式与指定的字符串是否匹配。返回 true 或 false。

  1. 第一题
  1. 匹配以 javascript 开头的字符串
  2. 匹配以 javascript 结尾的字符串
/^javascript/.test('javascript is my favorite'); // true

/javascript$/.test('this code in javascript'); // true
复制代码

2 . 第二题

仅匹配有边界的code单词

/\bcode\b/.test('bar code'); // true

/\bcode\b/.test('barcode') // false
复制代码

3 . 第三题

匹配姓“李”的名字。

/^李.+/.test('李逍遥');  // true

/^李.+/.test('慕容李逍遥');  // false

// "李" 左侧可以为空格不能为非空格字符
/(?<=[\s]?)(?<!\S)李.+/.test('李逍遥');  // true
复制代码

匹配字符

接下来我们看下正则中可以匹配字符的方式:

字符

模式 说明
字母、数字 匹配字符本身。比如/javascript/,匹配 "javascript";/123/,匹配 "123"。
\0 匹配 NUL 字符。
\t 匹配水平制表符。
\v 匹配垂直制表符。
\n 匹配换行符。
\r 匹配回车符。
\f 匹配换页符。
\xnn 匹配拉丁字符。比如 \xOA 等价于 \n。
\uxxxx 匹配 Unicode 字符。比如 \u2028 匹配行终止符,\u2029 匹配段终止符。
\cX 匹配 ctrl+X。比如 \cI 匹配 ctrl+I,等价于 \t。
[\b] 匹配 Backspace 键(特殊记忆)。

上面表格内的乍一看特别多,但是别怕,我们记住常见的匹配字母、数字、换行、回车 等等即可。 至此,你已经学废使用正则了,是不是很简单。别高兴的太早,我们还要进阶一下。

字符组

模式 说明
[abc] 匹配 "a"、"b"、"c" 其中任何一个字符。
[a-d1-4] 匹配 "a"、"b"、"c"、"d"、"1"、"2"、"3"、"4" 其中任何一个字符。
[^abc] 匹配除了 "a"、"b"、"c" 之外的任何一个字符。
[^a-d1-4] 匹配除了 "a"、"b"、"c"、"d"、"1"、"2"、"3"、"4" 之外的任何一个字符。
. 通配符,匹配除了少数字符(\n)之外的任意字符。
\d 匹配数字,等价于 [0-9]。
\D 匹配非数字,等价于 [^0-9]。
\w 匹配单词字符,等价于 [a-zA-Z0-9_]。
\W 匹配非单词字符,等价于 [^a-zA-Z0-9_]。
\s 匹配空白符,等价于 [ \t\v\n\r\f]。
\S 匹配非空白符,等价于 [^ \t\v\n\r\f]。

[...]字符组语法类似 javascript 中的数组,可能有些小聪明会疑惑。简单的理解就是正则表达式会匹配[...]中的某个字符/表达式,相当于将字符组内的字符遍历匹配。\d \D \w \W \s \S 等都是一些简写,先记个大概以后熟能生巧。接下来看几个实例理解一下。

1 . 第一题

使用字符组匹配 Javascript 和 javascript

const regex = /[Jj]avascript/

regex.test('Javascript');  // true
regex.test('javascript');  // true
复制代码

2 . 第二题

匹配“我爱你”或“我想你”或“我”+数字+“你”

const regex = /我[\d爱想]你/

regex.test('我爱你');  // true
regex.test('我想你');  // true
regex.test('我打你');  // false
regex.test('520, 我2你');  // true
复制代码

3 . 第三题

匹配爱后面不包含你的数据

/我爱[^你]/.test('我爱你'); // false
复制代码

4 . 第四题

匹配数据所有的数字、小写字母和大写字母。

/[a-z0-9A-Z]/.test('b'); // true

/[a-zA-Z\d]/.test('999'); // true

/[a-zA-Z\d]/.test('A'); // true

/[a-zA-Z\d]/.test('A4'); // true
复制代码

到目前为止,我们只是学习了关于仅出现一次的字符串匹配,在实际开发中,肯定不能满足需求,比如要匹配电话号码、身份证的时候就无法满足需求了。接下来我们看一下如何匹配多个重复类型的字符

量词

模式 说明
{n,m} 连续出现 n 到 m 次。贪婪模式。
{n,} 至少连续出现 n 次。贪婪模式。
{n,} 至少连续出现 n 次。贪婪模式。
{n} 连续出现 n 次。贪婪模式。
+ 等价于 {1,}。贪婪模式。
* 等价于 {0,}。贪婪模式。
{n,m}? 连续出现 n 到 m 次。惰性模式。最多匹配到 n 个
{n,}? 至少连续出现 n 次。惰性模式。最多匹配到 n 个
{n}? 连续出现 n 次。惰性模式。
?? 等价于 {0,1}?。惰性模式。
+? 等价于 {1,}?。惰性模式。
*? 等价于 {0,}?。惰性模式。
  • 贪婪模式――在匹配成功的前提下,尽可能多的去匹配
  • 惰性模式――在匹配成功的前提下,尽可能少的去匹配

这是小伙伴们肯定又有点懵逼,我知道你很急,但你先别急。{n,m}简单来说就是需要重复的次数,我们继续来举个栗子。

  1. 第一题

匹配手机号码,假设手机号码规则如下:

  • 必须是 11 位的数字;
  • 第一位数字必须以 1 开头,第二位数字可以是 [3,4,5,7,8] 中的任意一个,后面 9 个数是 [0-9] 中的任意一个数字。
const regex = new RegExp(/^1[34578]\d{9}/);

regex.test('18711001111') // true
regex.test('13712345678') // true
regex.test('12345678911') // false
复制代码

括号的作用

模式 说明
(ab) 捕获型分组。把 "ab" 当成一个整体,比如 (ab)+ 表示 "ab" 至少连续出现一次。
(?:ab) 非捕获型分组。与 (ab) 的区别是,它不捕获数据。
(good nice) 捕获型分支结构。匹配 "good" 或 "nice"。
(?:good nice) 非捕获型分支结构。与 (good nice) 的区别是,它不捕获数据。
\num 反向引用。比如 \2,表示引用的是第二个括号里的捕获的数据。

括号主要是用来分组作用。同样的,我们举个栗子。

1 . 第一题

视频文件的后缀名有 .mp4、.avi、.wmv、.rmvb 用正则表达式提取所有的视频文件的后缀

const regex = new RegExp(/.+(.mp4|.avi|.wmv|.rmvb)/);

regex.exec('海贼王.avi')
// ['海贼王.avi', '.avi', index: 0, input: '海贼王.avi', groups: undefined]
regex.test('朋友.mp3')
// null
regex.test('学习资料.rmvb')
// ['学习资料.rmvb', '.rmvb', index: 0, input: '学习资料.rmvb', groups: undefined]
复制代码

正则表达式相关 API

Javascript 中可以通过以下两种方式写正则:

  1. 正则表达式字面量
  2. 通过构造函数 RegExp 的实例

例如,创建一个正则用于精确匹配字符串 'test'。

let regExp = /test/

let regExp = new RegExp('test') 
// 或者
let regExp = new RegExp(/test/)
复制代码

我们还可以在正则上添加一些修饰符,常见的修饰符如下

修饰符

模式 说明
g global 简写,全局匹配,找到所有满足匹配的子串,而不是默认只匹配首次结果。
i ignore case 简写,匹配过程中,忽略英文字母大小写,如 /test/i 可以匹配 Test、teSt、TesT 等。
m multiline 简写,多行匹配,把 ^ 和 $ 变成行开头和行结尾。比如可以匹配文本域(textarea)元素中的值。
u unicode 简写,允许使用 Unicode 点转义符。
y sticky 简写,开启粘连匹配,正则表达式执行粘连匹配时试图从最后一个匹配位置开始。

RegExp 相关实例方法

模式 说明
test[1] 判断目标字符串中是否有满足正则匹配的子串。返回布尔值。
exec[2] 比 match[3] 更强大的正则匹配操作。如果正则表达式不包含 g 标志,str.match()[4] 将返回与 RegExp.exec()[5]. 相同的结果。

以下是简单的用法,具体用法大伙可以上 mdn 查看相关资料。

new RegExp(/love*/).test('I love you, I love him')
// true

new RegExp(/love*/).exec('I love you, I love him')
// ['love', index: 2, input: 'I love you, I love hime', groups: undefined]
复制代码

RegExp 静态属性

模式 说明
1,...,1,...,1,...,9 最近一次第 1-9 个分组捕获的数据。
input 最近一次目标字符串,可以简写成 $_ 。
lastMatch 最近一次匹配的文本,可以简写成 $& 。
lastParen 最近一次捕获的文本,可以简写成 $+ 。
leftContext 目标字符串中 lastMatch 之前的文本,可以简写成 $` 。
rightContext 目标字符串中 lastMatch 之后的文本,可以简写成 $' 。

String 相关实例方法

模式 说明
search[6] 返回正则匹配到的第一个子串在目标字符串中的下标位置。
split[7] 以正则匹配到的子串,对目标字符串进行切分。返回一个数组。
match[8] 对目标字符串执行正则匹配操作,返回的匹配结果数组中包含具体的匹配信息。
  • groups: 一个命名捕获组对象,其键是捕获组名称,值是捕获组,如果未定义命名捕获组,则为 undefined[9]。有关详细信息,请参阅组和范围[10]。
  • index: 匹配的结果的开始位置
  • input: 搜索的字符串。 | | replace[11] | 对目标字符串进行替换操作。正则是其第一个参数。返回替换后的字符串。 |

replace 第二个参数中的特殊字符

模式 说明
1,1,1,2,...,$99 匹配第 1-99 个分组里捕获的文本
$& 匹配到的子串文本
$` 匹配到的子串的左边文本
$' 匹配到的子串的右边文本
$$ 美元符号

replace 第二个参数为函数

你可以指定一个函数作为第二个参数。在这种情况下,当匹配执行后,该函数就会执行。 函数的返回值作为替换字符串。 (注意:上面提到的特殊替换参数在这里不能被使用。) 另外要注意的是,如果第一个参数是正则表达式,并且其为全局匹配模式,那么这个方法将被多次调用,每次匹配都会被调用。

变量名 代表的值
match 匹配的子串。(对应于上述的$&。)
p1,p2, ... 假如 replace() 方法的第一个参数是一个RegExp[12] 对象,则代表第 n 个括号匹配的字符串。(对应于上述的1,1,1,2 等。)例如,如果是用 /(\a+)(\b+)/ 这个来匹配,p1 就是匹配的 \a+,p2 就是匹配的 \b+。
offset 匹配到的子字符串在原字符串中的偏移量。(比如,如果原字符串是 'abcd',匹配到的子字符串是 'bc',那么这个参数将会是 1)
string 被匹配的原字符串。
NamedCaptureGroup 命名捕获组匹配的对象

小试牛刀

第一题

匹配所有符合 XML 规则的标签

const regex = new RegExp(/<(\w+)>.+<\/(\1)>/);

regex.test('<div>code</div>')  // true
regex.test('<span>I Love U</span>') // true
regex.test('<h1>This is title</p>') // false
regex.test('<p></p>') // false
复制代码

还是比较简单的,主要是用到了括号捕获 <> 中的单词,注意 /为关键字,需要用 \转义。

第二题

请用正则表达式匹配所有的小数

const regex = new RegExp(/(?<!\.)\d+\.\d+$/);
// const regex = new RegExp(/^\d+(?<=\d)\.\d+$/);

regex.test(0.1)  // true
regex.test(1.30) // true
regex.test(13.14) // true
regex.test('1.3.1.4') // false
regex.test(1)   // false
复制代码

(?<!\.)反向后行断言,匹配一个位置其左边不为“.”;接着 \d+\.\d+匹配一位以上的数字 + “.” + 一位以上的数字。

第三题

提取下列数据中所有人的生日,使用两个分组,第一个分组提取“月”,第二个分组提取“日”。 王伟 1993年1月2日 张伟 1996.8.24 李伟 1996.3.21 李秀 1994-7-5

const regex = new RegExp(/((?<=[年.-])\d{1,2})[月.-](\d{1,2}))/);

regex.exec('王伟 1993年1月2日')
// ['1月2', '1', '2', index: 8, input: '王伟 1993年1月2日', groups: undefined]

regex.exec('李伟 1996.3.21')
// ['3.21', '3', '21', index: 8, input: '李伟 1996.3.21', groups: undefined]
复制代码

主要还是 (?<=[年.-])反向先行断言,匹配一个位置其左边为 [年.-] 中的一个,其余的就比较容易理解了,最后用 exec 提取捕获组即可。

第四题

编写正则表达式进行密码强度的验证,规则如下:

  • 至少一个大写字母
  • 至少一个小写字母
  • 至少一个数字
  • 至少 8 个字符
const regex = new RegExp(/(?=.*?[a-z])(?=.*?[A-Z])(?=.*?[0-9]).{8,}/);

regex.test('123456789') // false
regex.test('12ABab') // false
regex.test('12345ABCabc') // true
regex.test('ADMIN1234()') // false
regex.test('Hmm5201314') // true
复制代码

这段正则看着非常长,其实都是重复的逻辑,我们来拆解一下: (?=.*?[a-z]) 这段正则表达式规定了匹配的字符串中必须存在一个右边为任意字符和小写字母的位置,(?=.*?[a-z])(?=.*?[A-Z])(?=.*?[0-9])那这一整串连起来就是必须存在小写、大写、数字。

第五题

实现一个模板引擎,能够满足如下场景使用 let template = '我是{{name}},年龄{{age}},性别{{sex}}'; let data = { name: '姓名', age: 18 } render(template, data); // 我是姓名,年龄18,性别undefined

function render(template, data) {
  if(typeof template !== 'string' || typeof data !== 'object') {
   return null
  }
 return template.replace(/{{(.*?)}}/g, (match, $1) => data[$1])
}
复制代码

最后这题没啥难度,主要是理解捕获组和字符串的 replace 方法就能解决了。

第六题

写一个方法把下划线命名转成大驼峰命名

function strToCamel(str) {
    return str.replace(/(^|_)(\w)/g, (m, $1, $2) => $2.toUpperCase());
}
复制代码

同上题,巩固一下凑个数而已 (^0^)。

结束语

老铁们,学废了吗?

本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/w_vXjJqCh9n4bpPIfmZBLg

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237231次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8065次阅读
 目录