这里有新鲜出炉的 Javascript 教程,程序狗速度看过来!
Javascript 是一种由 Netscape 的 LiveScript 发展而来的原型化继承的基于对象的动态类型的区分大小写的客户端脚本语言,主要目的是为了解决服务器端语言,比如 Perl,遗留的速度问题,为客户提供更流畅的浏览效果。
我接触过不少语言, 我很看重一门语言的正则表达式是否强大, 还有正则与语法的结合是否紧密. 在这一点上, JavaScript 做的还不错, 至少有正则字面量. 当然, 最强大的还是 Perl. 但最近发现 JavaScript 中的正则有几个不同于其他语言的地方,下面一起来看下。
前言
最近发现 JavaScript 中的正则在某些地方的表现和其他语言或工具中的正则有些不同, 比较另类. 虽然你几乎不可能写出也几乎用不到下面我讲的这些正则, 但是了解一下毕竟是好的.
本文中的代码示例都是在兼容 ES5 的 JavaScript 环境中执行的, 也就是说, IE9 之前版本, Fx4 左右的版本, 等, 中的表现很有可能和我下面讲的不一样.
1. 空字符类
不包含任何字符的字符类
称之为空字符类 (
- []
), 我相信你没听别人这么叫过, 因为在其他语言中, 这种写法是非法的, 所有的文档和教程都不会讲一种非法的语法. 下面我演示一下其他语言或工具都是怎么报这个错的:
- empty char class
- $echo | grep '[]'grep: Unmatched[or[ ^ $echo | sed '/[]/'sed: - e表达式#1,字符4:未终止的地址正则表达式$echo | awk '/[]/'awk: cmd.line: 1 : /[]/awk: cmd.line: 1 : ^unterminated regexp awk: cmd.line: 1 : error: Unmatched[or[ ^ :/[]/ / $echo | perl - ne '/[]/'Unmatched[ in regex; marked by < --HERE in m / [ < --HERE] / at - e line 1.$echo | ruby - ne '/[]/' - e: 1 : empty char - class: /[]/$python - c 'import re;re.match("[]","")'Traceback(most recent call last) : File "<string>", line 1, in<module > File "E:\Python\lib\re.py", line 137, inmatch
- return _compile(pattern, flags).match(string) File "E:\Python\lib\re.py", line 244, in_compile raise error, v#invalid expression sre_constants.error: unexpected end of regular expression
而在 JavaScript 中, 空字符类是合法的正则组成部分, 不过它的效果是 "永不匹配", 也就是匹配什么都会失败. 相当于一个空否定正向环视
的效果:
- (empty negative lookahead)(?!)
- js > "whatever\n".match(/[]/g) //空字符类,永不匹配
- null js > "whatever\n".match(/(?!)/g) //空否定正向环视,永不匹配
- null
很显然, 这种东西在 JavaScript 中没什么用.
2. 否定空字符类
不包含任何字符的否定字符类 [^] 称之为否定空字符类 (negative empty char class) 或者叫空否定字符类(empty negative char class), 都可以, 因为这个名词是我 "自创" 的, 和上面讲的空字符类类似, 这种写法在其他语言中也是非法的:
- $echo | grep '[^]'grep: Unmatched[or[ ^ $echo | sed '/[^]/'sed: - e表达式#1,字符5:未终止的地址正则表达式$echo | awk '/[^]/'awk: cmd.line: 1 : /[^]/awk: cmd.line: 1 : ^unterminated regexp awk: cmd.line: 1 : error: Unmatched[or[ ^ :/[^]/ / $echo | perl - ne '/[^]/'Unmatched[ in regex; marked by < --HERE in m / [ < --HERE ^ ] / at - e line 1.$echo | ruby - ne '/[^]/' - e: 1 : empty char - class: /[^]/$python - c 'import re;re.match("[^]","")'Traceback(most recent call last) : File "<string>", line 1, in<module > File "E:\Python\lib\re.py", line 137, inmatch
- return _compile(pattern, flags).match(string) File "E:\Python\lib\re.py", line 244, in_compile raise error, v#invalid expression sre_constants.error: unexpected end of regular expression $
而在 JavaScript 中, 否定空字符类是合法的正则组成部分, 它的效果和空字符类的效果刚刚相反, 它可以匹配任意的字符, 包括换行符
, 也就是说, 等同于常见的
- "\n"
和
- [\s\S]
:
- [\w\W]
- js > "whatever\n".match(/[^]/g) //否定空字符类,匹配任意字符
- ["w", "h", "a", "t", "e", "v", "e", "r", "\n"] js > "whatever\n".match(/[\s\S]/g) //互补字符类,匹配任意字符
- ["w", "h", "a", "t", "e", "v", "e", "r", "\n"]
需要注意的是, 它不能称之为是 "永匹配正则", 因为字符类必须要有一个字符才可能匹配, 如果目标字符串是空的, 或者已经被左边的正则消耗完了, 则匹配会失败, 比如:
- js > /abc[^]/.test("abc") //c后面没有字符了,匹配失败.
- false
想要了解真正的 "永匹配正则", 可以看看我以前翻译的一篇文章:"空" 正则
3.[]] 和 [^]]
这个讲起来比较简单, 就是: 在 Perl 和其他一些 linux 命令的正则表达式中, 字符类
中如果包含了一个紧跟着左方括号的右方括号
- []
, 则这个右方括号会被当作一个普通字符, 即只能匹配 "]", 而在 JavaScript 中, 这种正则会被识别成一个空字符类后跟一个右方括号, 空字符类什么都不匹配
- []]
也类似: 在 JavaScript 中, 它匹配的是一个任意字符 (否定空字符类) 后跟一个右中括号, 比如
- .[^]]
, 而在其他语言中, 匹配的是任何非] 的字符.
- "a]","b]"
- $perl - e 'print "]" =~ /[]]/'1 $js - e 'print(/[]]/.test("]"))'false $perl - e 'print "x" =~ /[^]]/'1 $js - e 'print(/[^]]/.test("x"))'false
4.$ 锚点
有些初学者认为 $ 匹配的是换行符
, 这是大错特错的,$ 是一个零宽断言 (zero-width assertion), 它是不可能匹配到一个真正的字符的, 它只能匹配一个位置. 我要的讲的区别发生在非多行模式中: 你也许会认为, 在非多行模式中,$ 匹配的不就是最后一个字符后面的位置吗? 实际上没那么简单, 在其他大部分语言中, 如果目标字符串中的最后一个字符是换行符
- "\n"
, 则 $ 还会匹配那个换行符之前的位置, 也就是匹配了末尾的换行符左右两边的两个位置. 很多语言中都有 \ Z 和 \ z 这两个表示法, 如果你知道它们之间的区别, 那你应该就明白了, 在其他语言中 (Perl,Python,php,Java,c#...), 非多行模式下的 $ 相当于 \ Z, 而在 JavaScript 中, 非多行模式下的 $ 相当于 \ z(只会匹配最末尾的那个位置, 不管最后一个字符是否是换行符).Ruby 是个特例, 因为它默认就是多行模式, 多行模式下 $ 会匹配每个换行符前面的位置, 当然也会包括结尾处可能出现的那个换行符. 余晟著的《正则指引》一书中也讲到了这几点.
- "\n"
- $perl - e 'print "whatever\n" =~ s/$/替换字符/rg' //全局替换
- whatever替换字符 //换行符前面的那个位置被替换
- 替换字符 //换行符后面的那个位置被替换
- $js - e 'print("whatever\n".replace(/$/g,"替换字符"))' //全局替换
- whatever替换字符 //换行符后面的那个位置被替换
5. 点号元字符 "."
在 JavaScript 中的正则表达式中, 点号元字符 "." 可以匹配四个行终止符 (\r - 回车符,\n - 换行符,\u2028 - 行分隔符,\u2029 - 段落分隔符) 之外的所有字符, 而在其他常用语言中, 只会排除掉换行符 \ n.
6. 向前引用
我们都知道正则中有反向引用 (back reference), 也就是用一个反斜杠 + 数字的形式引用到前面的某个捕获分组已经匹配到的字符串, 目的是用来再次匹配或作为替换结果 (\ 变成 $). 但有种特殊情况是, 如果那个被引用的捕获分组还没开始 (左括号为界), 就使用了反向引用, 会怎样. 比如正则
, (a)是第二个捕获分组, 但在它的左边使用了引用它的匹配结果的 \ 2, 我们知道正则是从左向右进行匹配的, 这就是本节的标题向前引用 (forwards reference) 的来历, 它并不是一个严格的概念. 那么现在你想想, 下面的这句 JavaScript 代码将返回什么:
- /(\2(a)){2}/
- js > /(\2(a)){2}/.exec("aaa") ? ??
在回答这个问题之前, 先看看其他语言中的表现. 同样, 在其他语言中, 这么写也基本上是无效的:
- $echo aaa | grep '(\2(a)){2}'grep: Invalid back reference $echo aaa | sed - r '/(\2(a)){2}/'sed: - e表达式#1,字符12:非法回引用$echo aaa | awk '/(\2(a)){2}/'$echo aaa | perl - ne 'print /(\2(a)){2}/'$echo aaa | ruby - ne 'print $_ = ~/(\2(a)){2}/'$python - c 'import re;print re.match("(\2(a)){2}","aaa")'None
在 awk 中没有报错, 是因为 awk 不支持这种反向引用, 其中的 \ 2 被解释成了 ASCII 码为 2 的字符. 而在 Perl Ruby Python 中没报错, 我不知道为什么这样设计, 应该都是学 Perl 的, 但效果都一样, 就是这种情况下是不可能匹配成功的.
而在 JavaScript 中, 不仅不报错, 还能匹配成功, 看看和你刚才想的答案一样不一样:
- js > /(\2(a)){2}/.exec("aaa")["aa", "a", "a"]
防止你忘了
方法返回的结果是什么, 我说一下. 第一个元素是完整的匹配字符串, 也就是
- exec
, 后面的是每个捕获分组匹配的内容, 也就是
- RegExp["$&"]
和
- RegExp.$1
为什么能匹配成功呢, 匹配过程是怎样的? 我的理解是:
- RegExp.$2.
首先进入了第一个捕获分组 (最左边的左括号), 其中第一个有效匹配项是 \ 2, 然而这时第二个捕获分组(a) 还没轮上, 因此
的值还是
- RegExp.$2
, 所以 \ 2 匹配了目标字符串中第一个 a 左边的一个空字符, 或者说 "位置", 就像 ^ 和其他零宽断言一样. 重点是匹配成功了. 继续走, 这时第二个捕获分组 (a) 匹配到了目标字符串中的第一个 a,
- undefined
的值也被赋值为 "a", 然后是第一个捕获分组结束 (最右边的右括号),
- RegExp.$2
的值也是 "a". 然后是量词 {2}, 也就是说, 要从目标字符串中的第一个 a 之后, 开始进行正则
- RegExp.$1
的新的一轮匹配, 很关键的一点在这里: 就是
- (\2(a))
的值也就是 \ 2 匹配的值还是不是第一轮匹配结束时的被赋的值 "a", 答案是:"不是",
- RegExp.$2
和
- RegExp.$1
的值都会被清空为
- RegExp.$2
,\1 和 \ 2 又会和第一次一样, 成功匹配一个空字符 (相当于无任何效果, 写不写都一样). 成功匹配了目标字符串中的第二个 a, 这时
- undefined
和
- RegExp.$1
的值又一次成为了 "a",
- RegExp.$2
的值成为了完整的匹配字符串, 前两个 a:"aa".
- RegExp["$&"]
在 Firefox 的早期版本 (3.6) 中, 量词的重新一轮匹配不会清空已有的捕获分组的值, 那么也就是说, 在第二轮匹配的时候,\2 会匹配上第二个 a, 从而:
- js > /(\2(a)){2}/.exec("aaa")["aaa", "aa", "a"]
另外, 一个捕获分组的结束要看右括号是否闭合, 比如 /(a\1){3}/, 虽然用到 \ 1 的时候, 第一个捕获分组已经开始匹配了, 但还没结束, 这同样是向前引用, 所以 \ 1 匹配的仍然是空:
- js > /(a\1){3}/.exec("aaa")["aaa", "a"]
再解释一个例子:
- js > /(?:(f)(o)(o)|(b)(a)(r))*/.exec("foobar")["foobar", undefined, undefined, undefined, "b", "a", "r"]
* 号是量词, 第一轮匹配过后:$1 为 "f",$2 为 "o",$3 为 "o",$4 为 undefined,$5 为
,$6 为
- undefined
.
- undefined
第二轮匹配开始时: 捕获到的值全部重置为
.
- undefined
第二轮匹配过后:$1 为
,$2 为
- undefined
,$3 为
- undefined
,$4 为 "b",$5 为 "a",$6 为 "r".
- undefined
& 被赋值为 "foobar", 匹配结束.
总结
以上就是总结 JavaScript 的正则与其他语言不同之处的全部内容,希望本文的内容对大家的学习和工作能带来帮助。
来源: http://www.phperz.com/article/17/0515/332338.html