06月16, 2017

JavaScript: 为初学者介绍正则表达式

原文:https://hackernoon.com/javascript-learn-regular-expressions-for-beginners-bb6107015d91

alt

不久前,正规表达式对我来说是挺可怕的 - 对我未经训练的眼睛来说,它们只不过是一堆乱七八糟的字符串。不幸的是,我让那些“乱七八糟”的字符串吓倒我太久了。当我终于决定冒险学习正则表达式时,我很惊讶要学习的基本概念有多简单。

不要犯同样的错误。是的,正则表达式可能非常复杂,但实际上学习基础知识很容易。下面我一起学习基本的正则表达式吧...

在 JavaScript 中,正则表达式只是一种用于匹配字符串中字符组合的对象。

创建第一个正则表达式

构建正则表达式有两种方法:通过使用正则表达式字面量,或者使用正则表达式构造器。下面,我们会看到每种方法的一个示例。两个例子都表示相同的模式 - 字符 'c' 后跟 'a' 后跟 't'。

// 正则表达式字面量 - 使用斜线(/)包住
var option1 = /cat/;

// 正则表达式构造器
var option2 = new RegExp("cat");

基本规则是,如果你的正则表达式将保持不变,即表达式将不会改变,最好使用正则表达式字面量;如果你的正则表达式会改变,或者依赖于其他变量,最好使用构造器方法。

RegExp.prototype.test()

记得我说过正则表达式是对象吗?就是说它们有很多我们可以用的方法。最基础的方法就是 test,该方法会返回一个布尔值:

  • True:该字符串包含正则表达式模式的一个匹配
  • False:找不到匹配
console.log(/cat/.test("the cat says meow"));
// true

console.log(/cat/.test("the dog says bark"));
// false

基础正则表达式速查表

老实说,学习正则表达式的诀窍就是记住常用的字符组和符号。我强烈建议大家花几个小时记住下表,然后回来继续学习。如果你喜欢在阅读时学习,我依然会在后文中介绍一切,只是你可能得不时地回到这里。

符号

  • . — 匹配除换行符之外的任何单个字符。
  • * — 匹配符号之前的表达式 0 到多次。
  • + — 匹配符号之前的表达式 1 到多次。
  • ? — 符号之前的表达式是可选的(匹配 0 次或 1 次)。
  • ^ — 匹配字符串的开头。
  • $ — 匹配字符串的结尾。

字符组

  • \d — 匹配任何单个数字。
  • \w — 匹配任何单个字符(数字、字母和下划线)
  • [XYZ] — 字符集:匹配中括号内字符的任何单个字符。可以设置 [A-Z] 这样的范围。
  • [XYZ]+ — 匹配集合中任何 1 到多个字符。
  • [^A-Z] — 在字符集内,^ 用于否定。本例中,匹配任何非大小字母的字符。

标志

有 5 个可选标志。它们可以单独使用,或者一起使用,放在关闭斜线之后。例如: /[A-Z]/ g。这里我只介绍两种。

  • g — 全局搜索
  • i — 不区分大小写搜索

高级

  • (x) — 捕获括号:匹配 x,并且记住它,以便之后使用。
  • (?:x) — 不捕获括号:匹配 x,并且不记住它。
  • x(?=y) — 向前看:匹配 x,不过是只在 x 后跟 y 时才匹配。

测试学习

在进入下面的项目前,我们先来测试一下上面列出的一些概念。假如我们想测试一个字符串是否带有任何数字,可以用 \d 来完成。

console.log(/\d/.test('12-34'));
// true

只要字符串中至少有一个数字,上面的代码就会返回 true。不过,如果我们想匹配格式该怎么办呢?我们可以用多个 \d 字符来定义一个格式:

console.log(/\d\d-\d\d/.test('12-34'));
// true

console.log(/\d\d-\d\d/.test('1234'));
// false

如果我们不关心 - 前后有多少个数字,只要至少有一个就行,又该怎么办呢?我们可以用 + 来匹配 \d 1 到多次:

console.log(/\d+-\d+/.test('12-34'));
// true

console.log(/\d+-\d+/.test('1-234'));
// true

console.log(/\d+-\d+/.test('-34'));
// false

为简化起见,我们可以用括号把表达式组合在一起。假设有一只猫在叫,我们想匹配猫叫:

console.log(/me+(ow)+w/.test('meeeeowowoww'));
// true

哇,搞定了。下面我们来分解一下。

/me+(ow)+w/

m     => 匹配单个字母 'm'
e+    => 匹配字母 'e' 一到多次
(ow)+ => 匹配字母 'ow' 一到多次
w     => 匹配字母 'w' 一次

'm' + 'eeee' +'owowow' + 'w'

如上所见,当括号之后立即使用像 + 这样的运算符时,会影响这些括号的全部内容。

在进入项目之前,我们要复习的最后一件事情是 ? 运算符。这个运算符让符号之前的字符变成可选的。如下所见,两个测试例子都返回 true,因为 s 已被认为是可选的。

console.log(/cats? says?/i.test('the Cat says meow'));
// true

console.log(/cats? says?/i.test('the Cats say meow'));
// true

上面我还抛出了 /i 标志。这让搜索变得不区分大小写,这就是为什么 cats 还会匹配 cats 的原因。

注意事项和提示

  • 因为正则表达式是被包含在斜杠中,所以如果想搜索斜杠,则需要使用反斜杠将其转义。 对于具有特殊含义的问号等字符也是如此。以下是一个如何搜索这两个字符的示例:
var slashSearch = /\//;
var questionSearch = /\?/;
  • \d[0-9] 是一样的:都是匹配一个数字
  • \w[A-Za-z0-9_] 是一样的:都是匹配任何单个字母、数字或下划线。

项目:在驼峰命名法格式的字符串中添加空格

在本例中,我们确实越来越厌倦了驼峰命名法格式的字符串,需要一种方式来在单词之间添加空格。 以下是例子:

removeCc('camelCase') // => 应该返回 'camel Case'

用正则表达式的话,解决方案很简单。首先,需要搜索所有大写字母。用字符集搜索和全局标志可以很容易做到这点:

这会匹配 'camelCase' 中的 C
/[A-Z]/g

但是现在,怎么在 C 之前加一个空格呢?

我们需要用捕获括号!捕获括号允许匹配一个值,并且记住它,这样之后就可以用它!

用捕获括号来记住匹配到的大写字母
`/([A-Z])/`

之后用 $1 访问捕获到的值

上面大家会看到我用 $1 来访问捕获到的值。顺便说一句,如果有两套捕获括号,就可以用 $1$2 来引用捕获到的值,根据捕获括号的数量以此类推。

注意,如果需要用圆括号,但是不需要捕获值,可以用不捕获的括号:(?:x)。本例中欧给,x 被匹配,但是不会被记住。

好了,回到手头的任务上。我们如何实现捕获括号呢?用字符串的 .replace() 方法!我们插入 '$1' 为第二个参数(注意这里一定要用引号):

function removeCc(str){
  return str.replace(/([A-Z])/g, '$1');  
}

啊,等等,结果不对啊?我们再看看代码。我们正在捕获大写字母,然后只是用相同的捕获到的字母替换它!我们还需要添加空格!在引号内,我们插入一个空格,后跟我们的 $1 变量。这样每个大写字母后就加上了一个空格:

function removeCc(str){
  return str.replace(/([A-Z])/g, ' $1' );  
}

removeCc('camelCase') // 'camel Case'
removeCc('helloWorldItIsMe') // 'hello World It Is Me'

项目:干掉大写字母

现在我们有一个带有一堆时髦的大写字母的字符串。能不能搞清楚如何把所有大写字母变成小写字母呢?这一个项目跟上一个相似的,但稍微要麻烦点。花点时间试着搞清楚,然后再读。

首先,我们需要选择所有的大写字母。这和上面类似,用一个字符集搜索,加全局标志即可:

/[A-Z]/g

我们还会再次用 replace 方法,不过这次如何让字符变成小写呢?

function lowerCase(str){
  return str.replace(/[A-Z]/g, ???);  
}

提示一下:replace() 中可以指定一个函数作为第二个参数。

我们打算用箭头函数来取消大写匹配的值。当在 replace() 中使用函数时,该函数会在执行匹配后被调用,函数的结果将被用作替换字符串。更好的是,如果匹配是全局的,并且找到多个匹配,则将为每个被找到的匹配调用该函数。

function lowerCase(str){
  return str.replace(/[A-Z]/g, (u) => u.toLowerCase());
}

lowerCase('camel Case') // 'camel case'
lowerCase('hello World It Is Me') // 'hello world it is me'

项目:大写第一个字母

好了,现在大家已经具备完成这任务的所有知识了。看看下面的代码片段,试着在读之前自己搞定:

capitalize('camel case') // => 应该返回 'Camel case'

你搞定了吗?如果没有,没关系!我会向你展示如何搞定...

这里我们打算再次在 replace() 方法中用一个箭头函数。不过这一次,我们只需要搜索字符串中第一个字符。回想一下,这是 ^ 的用处。

下面我们深入看看 ^ 一会儿。回忆一下之前的这个示例:

console.log(/cat/.test('the cat says meow'));
// true

当我们加上 ^ 时,函数就不再返回 true,因为 'cat' 不在字符串的开头:

console.log(/^cat/.test('the cat says meow'));
// false

我们想让 ^ 应用到字符串开头的任何小写字符上,所以要直接把它添加到字符集 [a-z] 前面。这样就会只命中第一个字符是小写字母:

/^[a-z]/

注意这里我们不再用全局标志,因为我们只想匹配一次。现在,我们可以把正则表达式插到 replace 方法中,加入一个箭头函数为方法的第二个参数:

function capitalize(str){
  return str.replace(/^[a-z]/, (u) => u.toUpperCase());
}

capitalize('camel case') // 'Camel case'
capitalize('hello world it is me') // 'Hello world it is me'

项目:继续学习

这是本文的结尾,不过要继续学习。这里有一些项目的想法:

  1. 能不能将先前的三个函数组合成一个将驼峰格式书写的字符串转成普通句子的函数呢?
  2. 能在字符串末尾加上一个圆点吗?
  3. 反过来做!把一个普通句子转成一个驼峰格式书写的字符串。

请在评论中发表你的项目和解决方案!并且不要忘记,这仅仅是正则表达式的开始,用它还能做更多的事情!

本文链接:http://www.xiaojichao.com/post/regex.html

-- EOF --

Comments