JavaScript中使用正则表达式的一点笔记(未完)

前言

前几天在空隙时间在读 Mastering Regular Expressions这本书。不愧是关于正则表达式的一本好书,我也应该买一本的。现在是暂时借了阿威买的第三版的中文版来读。中文版翻译得还不错。有不少地方感觉翻译得挺精彩的,虽然马上就能猜到原文是怎么写的,但读起来中文一点都不觉得拗口。

现在才读到第76页,第二章,比较入门的部分。以前也不是没用过正则表达式,不过一边读这书一边就觉得以前真的中了很多陷阱。而且我居然一直不知道正则表达式里面有lookaround(positive/negative lookahead/lookbehind)这种好用的东西……真糟糕。不过现在学到也还不算迟。
比较麻烦的是第二章里几乎全部代码例子都是 perl写的,而我以前虽然没少用别人写的perl脚本,自己却几乎没写过perl代码。这下是领略到了不少perl的威力啊。
简单来说,像这样:
$input = <STDIN>; # get a line of input from stdin
chomp( $input );  # cut the trailing newline character

# verify the input as a decimal number
if ( $input =~ /^([-+]?[0-9]+(?:\.[0-9]*)?)$/ ) {
    # verified
    $result = $input * 2;
    print "input multiplied by 2 is: $result";
} else {
    # verification failed
    print "Expecting a number, so I don't understand \"$input\".\n";
}

在我看来就是语言的动态特性与正则表达式的优美结合啊。($input的类型可以自动在String与number之间转换,而正则表达式可以作用在String版本的类型上来检查输入的一行内容里是不是一个合法的数字。)

回头想想,我最初接触正则表达式的时候是在Java上用的。虽然编译原理课上也有讲到DFA/NFA与正则表达式,但那个时候没用在C/C++里用过正则表达式而只是实现过些简单的DFA。
Java里要用正则表达式颇不顺手。最痛苦的莫过于转义字符的问题。那时我对正则表达式里常用的一些预定义字符组之类的很不熟悉,只好阅读Java标准库的JavaDoc来学习。看看 java.util.regex.Pattern类的JavaDoc,可以看到很多与转义字符相关的预定义字符组之类的东西,但我当时花了很长时间才理解到Java本身字符串中的转义字符与正则表达式中的转义字符不是一回事——它们是叠加的。为了表达正则表达式里的\b,用Java应该写成"\\b"的字符串;为了匹配一个斜杠和句点“\.”而要写出正则表达式\\\.,在Java中却得写成"\\\\\\."。结果我经常在大量的“\”中迷茫,到底写了多少个“\”都数不清了……

接触到 有verbatim string的C#和有 Wysiwyg string的D之后这转义的问题减轻了不少,不过要是能直接写正则表达式的字面量而不是用字符串来表示正则表达式就更舒服了。在阅读一些JavaScript的相关文章时留意到有不少人提到“perl风格的正则表达式记法”,查了下,发现这种以operation/regex/modifier为形式的记法确实好用。不过当时也是随便写了几个JavaScript的小测试玩了玩就没管了,也没去看看这记法的来源——perl里它到底长什么样。这次就多记点JavaScript里的perl-style regex吧。

====================================================================

笔记

WebRef上有一套关于JavaScript的教程,不过比较老了,是1997年12月4日最后编辑的。虽然文章比较老了,其中 关于正则表达式的部分还是可以参考的;毕竟 ECMAScript在v3之后就一直没正式发布更新的版本嘛。JavaScript 1.8要跟随FireFox 3.0才会出现,而JavaScript 2.0得等ECMAScript最终确定。漫长啊。JavaScript 1.8的技巧似乎在 这里有些记载,有空的时候要去看看。
等我买了新的犀牛书之后再结合ECMA-262来检查一下有没有些什么更新的地方好了……

Mozilla MDC有对应JavaScript 1.5(带有1.8更新)的 正则表达式文档

一些关于lookaround的介绍

先来看点简单的例子吧。
<script>
var str = "<STRONG>text here</STRONG>123"
var regex = /^<([^>]*)>.*<\/\1>(?=\d+)/

// EXAMPLE 1
// validate a string with given regex, returning a boolean value
var result = regex.test(str)
document.write(result + "<BR />") // true

// EXAMPLE 2
// match a string with given regex, returning the matches in an array
result = /(?=\w)(?:[^>]*) (?=\w)/.exec(str)
document.write(result + "<BR />")
// text

for (var i in result) {
    document.write(i + ": " + result[i] + "<BR />")
}
// input: text here123
// index: 8
// lastIndex: 13
// 0: text

// EXAMPLE 3
// match a string with given regex, returning the matches in an array
result = str.match(regex)
for (var i in result) {
    document.write(i + ": " + result[i] + "<BR />")
}
// input: <STRONG>text here</STRONG>123
// index: 0
// lastIndex: 26
// 0: <STRONG>text here</STRONG>
// 1: STRONG

// EXAMPLE 4
// substitute a matched string with some other string
result = "vaarbcaAdbcae".replace(/(a)+(?:.*)(bc)/gi, "_$2-$1_")
document.write(result + "<BR />")
// v_bc-a_ae

// EXAMPLE 5
// substitute a matched string with some other string
result = "vaarbcaAdbcae".replace(/(a)+(?:.*)(bc)/gi, function(str, p1, p2) {
  return "_" + p2 + "+" + p1 + "_"
})
document.write(result + "<BR />")
// v_bc+a_ae
</script>


在关注如何在JavaScript操作正则表达式之前,先看看如何定义一个正则表达式。
与perl一样,JavaScript中可以在一头一尾的两个斜杠(“/”)中间写一个正则表达式的字面量。但具体的状况与perl又不完全相同:
perl:
引用
m/ PATTERN/ cgimosx
/ PATTERN/ cgimosx
q/ STRING /
' STRING'
qq/ STRING/
" STRING"
qr/ STRING/ imosx
qx/ STRING/
` STRING`
qw/ STRING/
s/ PATTERN/ REPLACEMENT/ egimosx
tr/ SEARCHLIST/ REPLACEMENT/ cds
y/ SEARCHLIST/ REPLACEMENTLIST/ cds

JavaScript:
引用
/ PATTERN/ gimy


JavaScript中的正则表达式字面量只能以斜杠开头,而不能像perl里一样在斜杠前指定操作。默认的操作就是m/(match)。在结尾的斜杠后面可以像perl里一样,指定一些修饰符;但不及perl所提供的修饰符号多,只支持以下几种:
g
全局匹配
i
忽略大小写
m
多行匹配
y
FireFox 3/JavaScript 1.8中添加的新修饰符。只从该正则表达式实例的lastIndex属性开始搜索。

在ECMAScript 4的草案中,有提议采纳/x修饰符以支持perl风格的多行正则表达式记法,并允许在正则表达式中使用单行注释来提高可阅读性。不知道最终是否会被采纳呢。

注意:JavaScript的正则表达式中,“.”是不匹配换行符的。要匹配包括换行符在内的所有字符的话可以用“[\s\S]”

当正则表达式的内容需要改变,或需要从用户输入获取等的时候,也可以采用构造器的方式来定义正则表达式:
var regex = new RegExp("pattern" [, "flags"]);

其中代表修饰符的第二个参数是可选的。注意到表示要匹配的模式的第一个参数是用双引号而不是斜杠括起来的。

EXAMPLE 1

这个例子中用到了一个包含了许多特性的正则表达式,与一个字符串进行匹配。
这些特性包括锚,字符组,预定义字符组,量词,捕获型括号,反向引用,环视等。

使用<.*>来匹配标签的话挺容易出问题的,所以这里采用了<[^>]*>来匹配。

EXAMPLE 2

这个例子主要展示了exec()方法的使用。
值得注意的是,JavaScript中只支持顺序环视(lookahead)而不支持逆序环视(lookbehind),所以当需要逆向环视的时候应当想办法将其转换为正向环视。
一段否定正向环视的代码:
js> regex = /\b(?!FX)\b/ig
/\b(?!FX)\b/gi
js> "Fx's not FX".replace(regex, "Alpha")
FxAlpha'AlphasAlpha AlphanotAlpha FXAlpha


EXAMPLE 3

这个例子用EXAMPLE 1里的字符串与正则表达式进行匹配(和捕获),并演示了如何在正则表达式执行过后引用捕获到的匹配。

EXAMPLE 4

这个例子展示了如何使用正则表达式进行替换。

EXAMPLE 4展示了JavaScript中量词的“贪婪”特性——它一定会在 整个字符串中找到符合正则表达式要求的 最长匹配。更准确的说,是遵循"left-most, longest match"的原则。
在这个例子中,虽然"aarbc"、"aAdbc"与"aarbcaAdbc"都能够匹配,但只有"aarbcaAdbc"才是最长的,因而结果是把整个"aarbcaAdbc"作为匹配,交给替换的逻辑去处理。

EXAMPLE 5

这个例子展示了如何使用正则表达式进行替换,传入一个函数来计算替换的字符串。
传入的函数可能接受的参数为:
str: 整个匹配的子串
p1, p2, ...: 每个捕获分组匹配到的子串
offset: 整个匹配的子串在原字符串中的偏移量
s: 匹配的原字符串

正则表达式相关方法(包括字符串的几个方法)
参考: http://www.webreference.com/js/column5/methods.html

compile
正则表达式的成员方法。compile()方法可以改变一个正则表达式实例的匹配模式及其修饰符。它的参数与RegExp构造器一样。
regex.compile("pattern" [, "flags" ]);


exec
正则表达式的成员方法。exec()的作用某种意义上与perl中的=~运算符相同,都是将一个正则表达式应用在一个字符串上。但是JavaScript中只能定义match功能的正则表达式,所以exec也只能匹配而不能替换。
若存在匹配,调用该方法会更新全局的RegExp对象的属性。
perl:
$str =~ m/PATTERN/;
# - or -
$str =~ s/PATTERN/REPLACEMENT/;
# - and the like -

JavaScript:
var result = regex.exec(str);

调用exec()会返回一个数组,其中包含有下列属性:
input:用于匹配的输入字符串。
index:开始匹配的位置的索引。
lastIndex:最后一次匹配后的最后位置的索引。
0:最后一次匹配的整个字符串内容。
1 ... n:捕获型括号匹配到的字符串内容。
参照上面的EXAMPLE 2的使用方法。

test
正则表达式的成员方法。test()与perl中的=~运算符也可以说相似,用在判断是否存在匹配时。
调用test()会返回一个boolean值来表明是否存在匹配。
调用该方法 不会影响全局的RegExp对象的属性。
var existMatch = regex.test(str);


match
字符串的成员方法。match()与正则表达式的exec()作用一样,返回值也一样。
若存在匹配,调用该方法会更新全局的RegExp对象的属性。
var result = str.match(regex);

参见上面的EXAMPLE 3的使用方法。

replace
字符串的成员方法。这个方法是JavaScript中对于perl的s/PATTERN/REPLACEMENT/的对应物。与perl一样,REPLACEMENT里可以有$1、$2...等变量来引用捕获到的匹配。
若存在匹配,调用该方法会更新全局的RegExp对象的属性。
str.replace(regex, replace);

参见上面的EXAMPLE 4的使用方法。

search
字符串的成员方法。这个方法与正则表达式的test()方法一样,用于检验是否存在匹配。
调用该方法 不会影响全局的RegExp对象的属性。
var existMatch = str.search(regex);


split
字符串的成员方法。用正则表达式(或普通字符串)指定分隔符,将原字符串分割为多个子串后以数组形式返回。
若存在匹配,调用该方法会更新全局的RegExp对象的属性。
document.write("a string".split(/ */).join(", ")); // a, s, t, r, i, n, g


================================================================

把一个JavaScript里用整数表示、分为单位的值转换为用字符串表示、以元为单位、每三位一组加上逗号分隔的函数:
// 把分转换到元,并在每三位添加一个逗号分隔
function fenToYuan(n) {
  return String(n).replace(/(^\d$)|(^\d{2}$)|(\d{2}$)/, function (_, p1, p2, p3) {
    return p1 && '0.0' + p1 || p2 && '0.' + p2 || p3 && '.' + p3;
  }).replace(/(\d+?)(?=(?:\d{3})+\.\d{2}$)/g, '$1,');
}

你可能感兴趣的:(JavaScript,正则表达式)