摘要:非貪婪模式盡可能少的匹配所搜索的字符串,而默認的貪婪模式則盡可能多的匹配所搜索的字符串。
導讀
你有沒有在搜索文本的時候絞盡腦汁, 試了一個又一個表達式, 還是不行.
你有沒有在表單驗證的時候, 只是做做樣子(只要不為空就好), 然后燒香拜佛, 虔誠祈禱, 千萬不要出錯.
你有沒有在使用sed 和 grep 命令的時候, 感覺莫名其妙, 明明應該支持的元字符, 卻就是匹配不到.
甚至, 你壓根沒遇到過上述情況, 你只是一遍又一遍的調用 replace 而已 (把非搜索文本全部替換為空, 然后就只剩搜索文本了), 面對別人家的簡潔高效的語句, 你只能在心中吶喊, replace 大法好.
為什么要學正則表達式. 有位網友這么說: 江湖傳說里, 程序員的正則表達式和醫生的處方, 道士的鬼符齊名, 曰: 普通人看不懂的三件神器. 這個傳說至少向我們透露了兩點信息: 一是正則表達式很牛, 能和醫生的處方, 道士的鬼符齊名, 并被大家提起, 可見其江湖地位. 二是正則表達式很難, 這也從側面說明了, 如果你可以熟練的掌握并應用它, 在裝逼的路上, 你將如日中天 (別問我中天是誰……) !
顯然, 有關正則表達的介紹, 無須我多言. 這里就借助 Jeffrey Friedl 的《精通正則表達式》一書的序言正式拋個磚.
? "如果羅列計算機軟件領域的偉大發明, 我相信絕對不會超過二十項, 在這個名單當中, 當然應該包括分組交換網絡, Web, Lisp, 哈希算法, UNIX, 編譯技術, 關系模型, 面向對象, XML這些大名鼎鼎的家伙, 而正則表達式也絕對不應該被漏掉.
? 對很多實際工作而言, 正則表達式簡直是靈丹妙藥, 能夠成百倍的提高開發效率和程序質量, 正則表達式在生物信息學和人類基因圖譜的研究中所發揮的關鍵作用, 更是被傳為佳話. CSDN的創始人蔣濤先生在早年開發專業軟件產品時, 就曾經體驗過這一工具的巨大威力, 并且一直印象深刻."
因此, 我們沒有理由不去了解正則表達式, 甚至是熟練掌握并運用它.
本文以正則基礎語法開篇, 結合具體實例, 逐步講解正則表達式匹配原理. 代碼實例使用語言包括 js, php, python, java(因有些匹配模式, js并未支持, 需要借助其他語言講解). 內容包括初階技能和高階技能, 適合新手學習和進階. 本文力求簡單通俗易懂, 同時為求全面, 涉及知識較多, 共計12k字, 篇幅較長, 請耐心閱讀, 如有閱讀障礙請及時聯系我.
回顧歷史要論正則表達式的淵源, 最早可以追溯至對人類神經系統如何工作的早期研究. Warren McCulloch 和 Walter Pitts 這兩位神經大咖 (神經生理學家) 研究出一種數學方式來描述這些神經網絡.
1956 年, 一位叫 Stephen Kleene 的數學家在 McCulloch 和 Pitts 早期工作的基礎上, 發表了一篇標題為"神經網事件的表示法"的論文, 引入了正則表達式的概念.
隨后, 發現可以將這一工作應用于使用 Ken Thompson 的計算搜索算法的一些早期研究中. 而 Ken Thompson 又是 Unix 的主要發明人. 因此半個世紀以前的Unix 中的 qed 編輯器(1966 qed編輯器問世) 成了第一個使用正則表達式的應用程序.
至此之后, 正則表達式成為家喻戶曉的文本處理工具, 幾乎各大編程語言都以支持正則表達式作為賣點, 當然 JavaScript 也不例外.
正則表達式的定義正則表達式是由普通字符和特殊字符(也叫元字符或限定符)組成的文字模板. 如下便是簡單的匹配連續數字的正則表達式:
/[0-9]+/ /d+/
"d" 就是元字符, 而 "+" 則是限定符.
元字符元字符 | 描述 |
---|---|
. | 匹配除換行符以外的任意字符 |
d | 匹配數字, 等價于字符組[0-9] |
w | 匹配字母, 數字, 下劃線或漢字 |
s | 匹配任意的空白符(包括制表符,空格,換行等) |
匹配單詞開始或結束的位置 | |
^ | 匹配行首 |
$ | 匹配行尾 |
元字符 | 描述 |
---|---|
D | 匹配非數字的任意字符, 等價于[^0-9] |
W | 匹配除字母,數字,下劃線或漢字之外的任意字符 |
S | 匹配非空白的任意字符 |
B | 匹配非單詞開始或結束的位置 |
[^x] | 匹配除x以外的任意字符 |
可以看出正則表達式嚴格區分大小寫.
重復限定符限定符共有6個, 假設重復次數為x次, 那么將有如下規則:
限定符 | 描述 |
---|---|
* | x>=0 |
+ | x>=1 |
? | x=0 or x=1 |
{n} | x=n |
{n,} | x>=n |
{n,m} | n<=x<=m |
[...] 匹配中括號內字符之一. 如: [xyz] 匹配字符 x, y 或 z. 如果中括號中包含元字符, 則元字符降級為普通字符, 不再具有元字符的功能, 如 [+.?] 匹配 加號, 點號或問號.
排除性字符組[^…] 匹配任何未列出的字符,. 如: [^x] 匹配除x以外的任意字符.
多選結構| 就是或的意思, 表示兩者中的一個. 如: a|b 匹配a或者b字符.
括號括號 常用來界定重復限定符的范圍, 以及將字符分組. 如: (ab)+ 可以匹配abab..等, 其中 ab 便是一個分組.
轉義字符即轉義字符, 通常 * + ? | { [ ( ) ] }^ $ . # 和 空白 這些字符都需要轉義.
操作符的運算優先級轉義符
(), (?:), (?=), [] 圓括號或方括號
*, +, ?, {n}, {n,}, {n,m} 限定符
^, $ 位置
| "或" 操作
測試我們來測試下上面的知識點, 寫一個匹配手機號碼的正則表達式, 如下:
(+86)?1d{10}
① "+86" 匹配文本 "+86", 后面接元字符問號, 表示可匹配1次或0次, 合起來表示 "(+86)?" 匹配 "+86" 或者 "".
② 普通字符"1" 匹配文本 "1".
③ 元字符 "d" 匹配數字0到9, 區間量詞 "{10}" 表示匹配 10 次, 合起來表示 "d{10}" 匹配連續的10個數字.
以上, 匹配結果如下:
修飾符javaScript中正則表達式默認有如下五種修飾符:
g (全文查找), 如上述截圖, 實際上就開啟了全文查找模式.
i (忽略大小寫查找)
m (多行查找)
y (ES6新增的粘連修飾符)
u (ES6新增)
常用的正則表達式漢字: ^[u4e00-u9fa5]{0,}$
Email: ^w+([-+.]w+)*@w+([-.]w+)*.w+([-.]w+)*$
URL: ^https?://([w-]+.)+[w-]+(/[w-./?%&=]*)?$
手機號碼: ^1d{10}$
身份證號: ^(d{15}|d{17}(d|X))$
中國郵政編碼: [1-9]d{5}(?!d) (郵政編碼為6位數字)
密碼驗證密碼驗證是常見的需求, 一般來說, 常規密碼大致會滿足規律: 6-16位, 數字, 字母, 字符至少包含兩種, 同時不能包含中文和空格. 如下便是常規密碼驗證的正則描述:
var reg = /(?!^[0-9]+$)(?!^[A-z]+$)(?!^[^A-z0-9]+$)^[^su4e00-u9fa5]{6,16}$/;正則的幾大家族 正則表達式分類
在 linux 和 osx 下, 常見的正則表達式, 至少有以下三種:
基本的正則表達式( Basic Regular Expression 又叫 Basic RegEx 簡稱 BREs )
擴展的正則表達式( Extended Regular Expression 又叫 Extended RegEx 簡稱 EREs )
Perl 的正則表達式( Perl Regular Expression 又叫 Perl RegEx 簡稱 PREs )
正則表達式比較字符 | 說明 | Basic RegEx | Extended RegEx | python RegEx | Perl regEx |
---|---|---|---|---|---|
轉義 | |||||
^ | 匹配行首,例如"^dog"匹配以字符串dog開頭的行(注意:awk 指令中,"^"則是匹配字符串的開始) | ^ | ^ | ^ | ^ |
$ | 匹配行尾,例如:"^、dog$" 匹配以字符串 dog 為結尾的行(注意:awk 指令中,"$"則是匹配字符串的結尾) | $ | $ | $ | $ |
^$ | 匹配空行 | ^$ | ^$ | ^$ | ^$ |
^string$ | 匹配行,例如:"^dog$"匹配只含一個字符串 dog 的行 | ^string$ | ^string$ | ^string$ | ^string$ |
< | 匹配單詞,例如:"< |
< |
不支持 |
不支持(但可以使用b來匹配單詞,例如:"bfrog") |
|
> | 匹配單詞,例如:"frog>"(等價于"frogb "),匹配以 frog 結尾的單詞 | > | > | 不支持 | 不支持(但可以使用b來匹配單詞,例如:"frogb") |
匹配一個單詞或者一個特定字符,例如:" |
不支持 | 不支持(但可以使用b來匹配單詞,例如:"bfrogb" | |||
() | 匹配表達式,例如:不支持"(frog)" | 不支持(但可以使用,如:dog | () | () | () |
匹配表達式,例如:不支持"(frog)" | 不支持(同()) | 不支持(同()) | 不支持(同()) | ||
? | 匹配前面的子表達式 0 次或 1 次(等價于{0,1}),例如:where(is)?能匹配"where" 以及"whereis" | 不支持(同?) | ? | ? | ? |
? | 匹配前面的子表達式 0 次或 1 次(等價于"{0,1}"),例如:"whereis? "能匹配 "where"以及"whereis" | ? | 不支持(同?) | 不支持(同?) | 不支持(同?) |
? | 當該字符緊跟在任何一個其他限制符(*, +, ?, {n},{n,}, {n,m}) 后面時,匹配模式是非貪婪的。非貪婪模式盡可能少的匹配所搜索的字符串,而默認的貪婪模式則盡可能多的匹配所搜索的字符串。例如,對于字符串 "oooo","o+?" 將匹配單個"o",而 "o+" 將匹配所有 "o" | 不支持 | 不支持 | 不支持 | 不支持 |
. | 匹配除換行符("n")之外的任意單個字符(注意:awk 指令中的句點能匹配換行符) | . | .(如果要匹配包括“n”在內的任何一個字符,請使用: [sS] | . | .(如果要匹配包括“n”在內的任何一個字符,請使用:" [.n] " |
* | 匹配前面的子表達式 0 次或多次(等價于{0, }),例如:zo* 能匹配 "z"以及 "zoo" | * | * | * | * |
+ | 匹配前面的子表達式 1 次或多次(等價于"{1, }"),例如:"whereis+ "能匹配 "whereis"以及"whereisis" | + | 不支持(同+) | 不支持(同+) | 不支持(同+) |
+ | 匹配前面的子表達式 1 次或多次(等價于{1, }),例如:zo+能匹配 "zo"以及 "zoo",但不能匹配 "z" | 不支持(同+) | + | + | + |
{n} | n 必須是一個 0 或者正整數,匹配子表達式 n 次,例如:zo{2}能匹配 | 不支持(同{n}) | {n} | {n} | {n} |
{n,} | "zooz",但不能匹配 "Bob"n 必須是一個 0 或者正整數,匹配子表達式大于等于 n次,例如:go{2,} | 不支持(同{n,}) | {n,} | {n,} | {n,} |
{n,m} | 能匹配 "good",但不能匹配 godm 和 n 均為非負整數,其中 n <= m,最少匹配 n 次且最多匹配 m 次 ,例如:o{1,3}將配"fooooood" 中的前三個 o(請注意在逗號和兩個數之間不能有空格) | 不支持(同{n,m}) | {n,m} | {n,m} | {n,m} |
x l y | 匹配 x 或 y | 不支持(同x l y | x l y | x l y | x l y |
[0-9] | 匹配從 0 到 9 中的任意一個數字字符(注意:要寫成遞增) | [0-9] | [0-9] | [0-9] | [0-9] |
[xyz] | 字符集合,匹配所包含的任意一個字符,例如:"[abc]"可以匹配"lay" 中的 "a"(注意:如果元字符,例如:. *等,它們被放在[ ]中,那么它們將變成一個普通字符) | [xyz] | [xyz] | [xyz] | [xyz] |
[^xyz] | 負值字符集合,匹配未包含的任意一個字符(注意:不包括換行符),例如:"[^abc]" 可以匹配 "Lay" 中的"L"(注意:[^xyz]在awk 指令中則是匹配未包含的任意一個字符+換行符) | [^xyz] | [^xyz] | [^xyz] | [^xyz] |
[A-Za-z] | 匹配大寫字母或者小寫字母中的任意一個字符(注意:要寫成遞增) | [A-Za-z] | [A-Za-z] | [A-Za-z] | [A-Za-z] |
[^A-Za-z] | 匹配除了大寫與小寫字母之外的任意一個字符(注意:寫成遞增) | [^A-Za-z] | [^A-Za-z] | [^A-Za-z] | [^A-Za-z] |
d | 匹配從 0 到 9 中的任意一個數字字符(等價于 [0-9]) | 不支持 | 不支持 | d | d |
D | 匹配非數字字符(等價于 1) | 不支持 | 不支持 | D | D |
S | 匹配任何非空白字符(等價于2) | 不支持 | 不支持 | S | S |
s | 匹配任何空白字符,包括空格、制表符、換頁符等等(等價于[ fnrtv]) | 不支持 | 不支持 | s | s |
W | 匹配任何非單詞字符 (等價于3) | W | W | W | W |
w | 匹配包括下劃線的任何單詞字符(等價于[A-Za-z0-9_]) | w | w | w | w |
B | 匹配非單詞邊界,例如:"erB" 能匹配 "verb" 中的"er",但不能匹配"never" 中的"er" | B | B | B | B |
匹配一個單詞邊界,也就是指單詞和空格間的位置,例如: "erb" 可以匹配"never" 中的 "er",但不能匹配 "verb" 中的"er" | |||||
匹配一個橫向制表符(等價于 x09和 cI) | 不支持 | 不支持 | |||
v | 匹配一個垂直制表符(等價于 x0b和 cK) | 不支持 | 不支持 | v | v |
匹配一個換行符(等價于 x0a 和cJ) | 不支持 | 不支持 | |||
f | 匹配一個換頁符(等價于x0c 和cL) | 不支持 | 不支持 | f | f |
匹配一個回車符(等價于 x0d 和cM) | 不支持 | 不支持 | |||
匹配轉義字符本身"" | |||||
cx | 匹配由 x 指明的控制字符,例如:cM匹配一個Control-M 或回車符,x 的值必須為A-Z 或 a-z 之一,否則,將 c 視為一個原義的 "c" 字符 | 不支持 | 不支持 | cx | |
xn | 匹配 n,其中 n 為十六進制轉義值。十六進制轉義值必須為確定的兩個數字長,例如:"x41" 匹配 "A"。"x041" 則等價于"x04" & "1"。正則表達式中可以使用 ASCII 編碼 | 不支持 | 不支持 | xn | |
num | 匹配 num,其中 num是一個正整數。表示對所獲取的匹配的引用 | 不支持 | num | num | |
[:alnum:] | 匹配任何一個字母或數字([A-Za-z0-9]),例如:"[[:alnum:]] " | [:alnum:] | [:alnum:] | [:alnum:] | [:alnum:] |
[:alpha:] | 匹配任何一個字母([A-Za-z]), 例如:" [[:alpha:]] " | [:alpha:] | [:alpha:] | [:alpha:] | [:alpha:] |
[:digit:] | 匹配任何一個數字([0-9]),例如:"[[:digit:]] " | [:digit:] | [:digit:] | [:digit:] | [:digit:] |
[:lower:] | 匹配任何一個小寫字母([a-z]), 例如:" [[:lower:]] " | [:lower:] | [:lower:] | [:lower:] | [:lower:] |
[:upper:] | 匹配任何一個大寫字母([A-Z]) | [:upper:] | [:upper:] | [:upper:] | [:upper:] |
[:space:] | 任何一個空白字符: 支持制表符、空格,例如:" [[:space:]] " | [:space:] | [:space:] | [:space:] | [:space:] |
[:blank:] | 空格和制表符(橫向和縱向),例如:"[[:blank:]]"ó"[stv]" | [:blank:] | [:blank:] | [:blank:] | [:blank:] |
[:graph:] | 任何一個可以看得見的且可以打印的字符(注意:不包括空格和換行符等),例如:"[[:graph:]] " | [:graph:] | [:graph:] | [:graph:] | [:graph:] |
[:print:] | 任何一個可以打印的字符(注意:不包括:[:cntrl:]、字符串結束符"0"、EOF 文件結束符(-1), 但包括空格符號),例如:"[[:print:]] " | [:print:] | [:print:] | [:print:] | [:print:] |
[:cntrl:] | 任何一個控制字符(ASCII 字符集中的前 32 個字符,即:用十進制表示為從 0 到31,例如:換行符、制表符等等),例如:" [[:cntrl:]]" | [:cntrl:] | [:cntrl:] | [:cntrl:] | [:cntrl:] |
[:punct:] | 任何一個標點符號(不包括:[:alnum:]、[:cntrl:]、[:space:]這些字符集) | [:punct:] | [:punct:] | [:punct:] | [:punct:] |
[:xdigit:] | 任何一個十六進制數(即:0-9,a-f,A-F) | [:xdigit:] | [:xdigit:] | [:xdigit:] | [:xdigit:] |
注意
js中支持的是EREs.
當使用 BREs ( 基本正則表達式 ) 時,必須在下列這些符號(?,+,|,{,},(,))前加上轉義字符 .
上述[[:xxxx:]] 形式的正則表達式, 是php中內置的通用字符簇, js中并不支持.
linux/osx下常用命令與正則表達式的關系我曾經嘗試在 grep 和 sed 命令中書寫正則表達式, 經常發現不能使用元字符, 而且有時候需要轉義, 有時候不需要轉義, 始終不能摸清它的規律. 如果恰好你也有同樣的困惑, 那么請往下看, 相信應該能有所收獲.
grep , egrep , sed , awk 正則表達式特點grep 支持:BREs、EREs、PREs 正則表達式
grep 指令后不跟任何參數, 則表示要使用 "BREs"
grep 指令后跟 ”-E" 參數, 則表示要使用 "EREs"
grep 指令后跟 “-P" 參數, 則表示要使用 "PREs"
egrep 支持:EREs、PREs 正則表達式
egrep 指令后不跟任何參數, 則表示要使用 "EREs"
egrep 指令后跟 “-P" 參數, 則表示要使用 "PREs"
sed 支持: BREs、EREs
sed 指令默認是使用 "BREs"
sed 指令后跟 "-r" 參數 , 則表示要使用“EREs"
awk 支持 EREs, 并且默認使用 "EREs"
正則表達式初階技能 貪婪模式與非貪婪模式默認情況下, 所有的限定詞都是貪婪模式, 表示盡可能多的去捕獲字符; 而在限定詞后增加?, 則是非貪婪模式, 表示盡可能少的去捕獲字符. 如下:
var str = "aaab", reg1 = /a+/, //貪婪模式 reg2 = /a+?/;//非貪婪模式 console.log(str.match(reg1)); //["aaa"], 由于是貪婪模式, 捕獲了所有的a console.log(str.match(reg2)); //["a"], 由于是非貪婪模式, 只捕獲到第一個a
實際上, 非貪婪模式非常有效, 特別是當匹配html標簽時. 比如匹配一個配對出現的div, 方案一可能會匹配到很多的div標簽對, 而方案二則只會匹配一個div標簽對.
var str = ""; var reg1 = /test/; //方案一,貪婪匹配 var reg2 = / /;//方案二,非貪婪匹配 console.log(str.match(reg1));//" " console.log(str.match(reg2));//"testtest"區間量詞的非貪婪模式
一般情況下, 非貪婪模式, 我們使用的是"*?", 或 "+?" 這種形式, 還有一種是 "{n,m}?".
區間量詞"{n,m}" 也是匹配優先, 雖有匹配次數上限, 但是在到達上限之前, 它依然是盡可能多的匹配, 而"{n,m}?" 則表示在區間范圍內, 盡可能少的匹配.
需要注意的是:
能達到同樣匹配結果的貪婪與非貪婪模式, 通常是貪婪模式的匹配效率較高.
所有的非貪婪模式, 都可以通過修改量詞修飾的子表達式, 轉換為貪婪模式.
貪婪模式可以與固化分組(后面會講到)結合,提升匹配效率,而非貪婪模式卻不可以.
分組正則的分組主要通過小括號來實現, 括號包裹的子表達式作為一個分組, 括號后可以緊跟限定詞表示重復次數. 如下, 小括號內包裹的abc便是一個分組:
/(abc)+/.test("abc123") == true那么分組有什么用呢? 一般來說, 分組是為了方便的表示重復次數, 除此之外, 還有一個作用就是用于捕獲, 請往下看.
捕獲性分組捕獲性分組, 通常由一對小括號加上子表達式組成. 捕獲性分組會創建反向引用, 每個反向引用都由一個編號或名稱來標識, js中主要是通過 $+編號 或者 +編號 表示法進行引用. 如下便是一個捕獲性分組的例子.
var color = "#808080"; var output = color.replace(/#(d+)/,"$1"+"~~");//自然也可以寫成 "$1~~" console.log(RegExp.$1);//808080 console.log(output);//808080~~以上, (d+) 表示一個捕獲性分組, "RegExp.$1" 指向該分組捕獲的內容. $+編號 這種引用通常在正則表達式之外使用. +編號 這種引用卻可以在正則表達式中使用, 可用于匹配不同位置相同部分的子串.
var url = "www.google.google.com"; var re = /([a-z]+).1/; console.log(url.replace(re,"$1"));//"www.google.com"以上, 相同部分的"google"字符串只被替換一次.
非捕獲性分組非捕獲性分組, 通常由一對括號加上"?:"加上子表達式組成, 非捕獲性分組不會創建反向引用, 就好像沒有括號一樣. 如下:
var color = "#808080"; var output = color.replace(/#(?:d+)/,"$1"+"~~"); console.log(RegExp.$1);//"" console.log(output);//$1~~以上, (?:d+) 表示一個非捕獲性分組, 由于分組不捕獲任何內容, 所以, RegExp.$1 就指向了空字符串.
命名分組
同時, 由于$1 的反向引用不存在, 因此最終它被當成了普通字符串進行替換.
實際上, 捕獲性分組和無捕獲性分組在搜索效率方面也沒什么不同, 沒有哪一個比另一個更快.語法: (?
...) 命名分組也是捕獲性分組, 它將匹配的字符串捕獲到一個組名稱或編號名稱中, 在獲得匹配結果后, 可通過分組名進行獲取. 如下是一個python的命名分組的例子.
import re data = "#808080" regExp = r"#(?Pd+)" replaceString = "g " + "~~" print re.sub(regExp,replaceString,data) # 808080~~ python的命名分組表達式與標準格式相比, 在 ? 后多了一大寫的 P 字符, 并且python通過“g<命名>"表示法進行引用. (如果是捕獲性分組, python通過"g<編號>"表示法進行引用)
與python不同的是, javaScript 中并不支持命名分組.
固化分組固化分組, 又叫原子組.
語法: (?>...)
如上所述, 我們在使用非貪婪模式時, 匹配過程中可能會進行多次的回溯, 回溯越多, 正則表達式的運行效率就越低. 而固化分組就是用來減少回溯次數的.
實際上, 固化分組(?>…)的匹配與正常的匹配并無分別, 它并不會改變匹配結果. 唯一的不同就是: 固化分組匹配結束時, 它匹配到的文本已經固化為一個單元, 只能作為整體而保留或放棄, 括號內的子表達式中未嘗試過的備用狀態都會被放棄, 所以回溯永遠也不能選擇其中的狀態(因此不能參與回溯). 下面我們來通過一個例子更好地理解固化分組.
假如要處理一批數據, 原格式為 123.456, 因為浮點數顯示問題, 部分數據格式會變為123.456000000789這種, 現要求只保留小數點后2~3位, 但是最后一位不能為0, 那么這個正則怎么寫呢?
var str = "123.456000000789"; str = str.replace(/(.dd[1-9]?)d*/,"$1"); //123.456以上的正則, 對于"123.456" 這種格式的數據, 將白白處理一遍. 為了提高效率, 我們將正則最后的一個"*"改為"+". 如下:
var str = "123.456"; str = str.replace(/(.dd[1-9]?)d+/,"$1"); //123.45此時, "dd[1-9]?" 子表達式, 匹配是 "45", 而不是 "456", 這是因為正則末尾使用了"+", 表示末尾至少要匹配一個數字, 因此末尾的子表達式"d+" 匹配到了 "6". 顯然 "123.45" 不是我們期望的匹配結果, 那我們應該怎么做呢? 能否讓 "[1-9]?" 一旦匹配成功, 便不再進行回溯, 這里就要用到我們上面說的固化分組.
"(.dd(?>[1-9]?))d+" 便是上述正則的固化分組形式. 由于字符串 "123.456" 不滿足該固化分組的正則, 所以, 匹配會失敗, 符合我們期望.
下面我們來分析下固化分組的正則 (.dd(?>[1-9]?))d+ 為什么匹配不到字符串"123.456".
很明顯, 對于上述固化分組, 只存在兩種匹配結果.
情況①: 若 [1-9] 匹配失敗, 正則會返回 ? 留下的備用狀態. 然后匹配脫離固化分組, 繼續前進到[d+]. 當控制權離開固化分組時, 沒有備用狀態需要放棄(因固化分組中根本沒有創建任何備用狀態).
情況②: 若 [1-9] 匹配成功, 匹配脫離固化分組之后, ? 保存的備用狀態仍然存在, 但是, 由于它屬于已經結束的固化分組, 所以會被拋棄.
對于字符串 "123.456", 由于 [1-9] 能夠匹配成功, 所以它符合情況②. 下面我們來還原情況②的執行現場.
匹配所處的狀態: 匹配已經走到了 "6" 的位置, 匹配將繼續前進;==>
子表達式 d+ 發現無法匹配, 正則引擎便嘗試回溯;==>
查看是否存在備用狀態以供回溯?==>
"?" 保存的備用狀態屬于已經結束的固化分組, 所以該備用狀態會被放棄;==>
此時固化分組匹配到的 "6", 便不能用于正則引擎的回溯;==>
嘗試回溯失敗;==>
正則匹配失敗.==>
文本 "123.456" 沒有被正則表達式匹配上, 符合預期.
相應的流程圖如下:
遺憾的是, javaScript, java 和 python中并不支持固化分組的語法, 不過, 它在php和.NET中表現良好. 下面提供了一個php版的固化分組形式的正則表達式, 以供嘗試.
$str = "123.456"; echo preg_replace("/(.dd(?>[1-9]?))d+/","1",$str); //固化分組不僅如此, php還提供了占有量詞優先的語法. 如下:
$str = "123.456"; echo preg_replace("/(.dd[1-9]?+)d+/","1",$str); //占有量詞優先雖然java不支持固化分組的語法, 但java也提供了占有量詞優先的語法, 同樣能夠避免正則回溯. 如下:
String str = "123.456"; System.out.println(str.replaceAll("(.dd[1-9]?+)d+", "$1"));// 123.456值得注意的是: java中 replaceAll 方法需要轉義反斜杠.
正則表達式高階技能-零寬斷言如果說正則分組是寫輪眼, 那么零寬斷言就是萬花筒寫輪眼終極奧義-須佐能乎(這里借火影忍術打個比方). 合理地使用零寬斷言, 能夠能分組之不能, 極大地增強正則匹配能力, 它甚至可以幫助你在匹配條件非常模糊的情況下快速地定位文本.
零寬斷言, 又叫環視. 環視只進行子表達式的匹配, 匹配到的內容不保存到最終的匹配結果, 由于匹配是零寬度的, 故最終匹配到的只是一個位置.
環視按照方向劃分, 有順序和逆序兩種(也叫前瞻和后瞻), 按照是否匹配有肯定和否定兩種, 組合之, 便有4種環視. 4種環視并不復雜, 如下便是它們的描述.
字符 描述 示例 (?:pattern) 非捕獲性分組, 匹配pattern的位置, 但不捕獲匹配結果.也就是說不創建反向引用, 就好像沒有括號一樣. "abcd(?:e)匹配"abcde (?=pattern) 順序肯定環視, 匹配后面是pattern 的位置, 不捕獲匹配結果. "Windows (?=2000)"匹配 "Windows2000" 中的 "Windows"; 不匹配 "Windows3.1" 中的 "Windows" (?!pattern) 順序否定環視, 匹配后面不是 pattern 的位置, 不捕獲匹配結果. "Windows (?!2000)"匹配 "Windows3.1" 中的 "Windows"; 不匹配 "Windows2000" 中的 "Windows" (?<=pattern) 逆序肯定環視, 匹配前面是 pattern 的位置, 不捕獲匹配結果. "(?<=Office)2000"匹配 " Office2000" 中的 "2000"; 不匹配 "Windows2000" 中的 "2000" (?pattern) 逆序否定環視, 匹配前面不是 pattern 的位置, 不捕獲匹配結果. "(? 非捕獲性分組由于結構與環視相似, 故列在表中, 以做對比. 以上4種環視中, 目前 javaScript 中只支持前兩種, 也就是只支持 順序肯定環視 和 順序否定環視. 下面我們通過實例來幫助理解下:
var str = "123abc789",s; //沒有使用環視,abc直接被替換 s = str.replace(/abc/,456); console.log(s); //123456789 //使用了順序肯定環視,捕獲到了a前面的位置,所以abc沒有被替換,只是將3替換成了3456 s = str.replace(/3(?=abc)/,3456); console.log(s); //123456abc789 //使用了順序否定環視,由于3后面跟著abc,不滿意條件,故捕獲失敗,所以原字符串沒有被替換 s = str.replace(/3(?!abc)/,3456); console.log(s); //123abc789下面通過python來演示下 逆序肯定環視 和 逆序否定環視 的用法.
import re data = "123abc789" # 使用了逆序肯定環視,替換左邊為123的連續的小寫英文字母,匹配成功,故abc被替換為456 regExp = r"(?<=123)[a-z]+" replaceString = "456" print re.sub(regExp,replaceString,data) # 123456789 # 使用了逆序否定環視,由于英文字母左側不能為123,故子表達式[a-z]+捕獲到bc,最終bc被替換為456 regExp = r"(?需要注意的是: python 和 perl 語言中的 逆序環視 的子表達式只能使用定長的文本. 比如將上述 "(?<=123)" (逆序肯定環視)子表達式寫成 "(?<=[0-9]+)", python解釋器將會報錯: "error: look-behind requires fixed-width pattern".
場景回顧 獲取html片段假如現在, js 通過 ajax 獲取到一段 html 代碼如下:
var responseText = "";現我們需要替換img標簽的src 屬性中的 "dev"字符串 為 "test" 字符串.
① 由于上述 responseText 字符串中包含至少兩個子字符串 "dev", 顯然不能直接 replace 字符串 "dev"為 "test".
② 同時由于 js 中不支持逆序環視, 我們也不能在正則中判斷前綴為 "src="", 然后再替換"dev".
③ 我們注意到 img 標簽的 src 屬性以 ".png" 結尾, 基于此, 就可以使用順序肯定環視. 如下:
var reg = /dev(?=[^"]*png)/; //為了防止匹配到第一個dev, 通配符前面需要排除單引號或者是尖括號 var str = responseText.replace(reg,"test"); console.log(str);//當然, 以上不止順序肯定環視一種解法, 捕獲性分組同樣可以做到. 那么環視高級在哪里呢? 環視高級的地方就在于它通過一次捕獲就可以定位到一個位置, 對于復雜的文本替換場景, 常有奇效, 而分組則需要更多的操作.
千位分割符千位分隔符, 顧名思義, 就是數字中的逗號. 參考西方的習慣, 數字之中加入一個符號, 避免因數字太長難以直觀的看出它的值. 故而數字之中, 每隔三位添加一個逗號, 即千位分隔符.
那么怎么將一串數字轉化為千位分隔符形式呢?
var str = "1234567890"; (+str).toLocaleString();//"1,234,567,890"如上, toLocaleString() 返回當前對象的"本地化"字符串形式.
如果該對象是Number類型, 那么將返回該數值的按照特定符號分割的字符串形式.
如果該對象是Array類型, 那么先將數組中的每項轉化為字符串, 然后將這些字符串以指定分隔符連接起來并返回.
toLocaleString 方法特殊, 有本地化特性, 對于天朝, 默認的分隔符是英文逗號. 因此使用它恰好可以將數值轉化為千位分隔符形式的字符串. 如果考慮到國際化, 以上方法就有可能會失效了.
我們嘗試使用環視來處理下.
function thousand(str){ return str.replace(/(?!^)(?=([0-9]{3})+$)/g,","); } console.log(thousand(str));//"1,234,567,890" console.log(thousand("123456"));//"123,456" console.log(thousand("1234567879876543210"));//"1,234,567,879,876,543,210"上述使用到的正則分為兩塊. (?!^) 和 (?=([0-9]{3})+$). 我們先來看后面的部分, 然后逐步分析之.
"[0-9]{3}" 表示連續3位數字.
"([0-9]{3})+" 表示連續3位數字至少出現一次或更多次.
"([0-9]{3})+$" 表示連續3的正整數倍的數字, 直到字符串末尾.
那么 (?=([0-9]{3})+$) 就表示匹配一個零寬度的位置, 并且從這個位置到字符串末尾, 中間擁有3的正整數倍的數字.
正則表達式使用全局匹配g, 表示匹配到一個位置后, 它會繼續匹配, 直至匹配不到.
將這個位置替換為逗號, 實際上就是每3位數字添加一個逗號.
當然對于字符串"123456"這種剛好擁有3的正整數倍的數字的, 當然不能在1前面添加逗號. 那么使用 (?!^) 就指定了這個替換的位置不能為起始位置.
千位分隔符實例, 展示了環視的強大, 一步到位.
正則表達式在JS中的應用 ES6對正則的擴展ES6對正則擴展了又兩種修飾符(其他語言可能不支持):
y (粘連sticky修飾符), 與g類似, 也是全局匹配, 并且下一次匹配都是從上一次匹配成功的下一個位置開始, 不同之處在于, g修飾符只要剩余位置中存在匹配即可, 而y修飾符確保匹配必須從剩余的第一個位置開始.
var s = "abc_ab_a"; var r1 = /[a-z]+/g; var r2 = /[a-z]+/y; console.log(r1.exec(s),r1.lastIndex); // ["abc", index: 0, input: "abc_ab_a"] 3 console.log(r2.exec(s),r2.lastIndex); // ["abc", index: 0, input: "abc_ab_a"] 3 console.log(r1.exec(s),r1.lastIndex); // ["ab", index: 4, input: "abc_ab_a"] 6 console.log(r2.exec(s),r2.lastIndex); // null 0如上, 由于第二次匹配的開始位置是下標3, 對應的字符串是 "_", 而使用y修飾符的正則對象r2, 需要從剩余的第一個位置開始, 所以匹配失敗, 返回null.
正則對象的 sticky 屬性, 表示是否設置了y修飾符. 這點將會在后面講到.
u 修飾符, 提供了對正則表達式添加4字節碼點的支持. 比如 "?" 字符是一個4字節字符, 直接使用正則匹配將會失敗, 而使用u修飾符后, 將會等到正確的結果.
var s = "?"; console.log(/^.$/.test(s));//false console.log(/^.$/u.test(s));//trueUCS-2字節碼有關字節碼點, 稍微提下. javaScript 只能處理UCS-2編碼(js于1995年5月被Brendan Eich花費10天設計出來, 比1996年7月發布的編碼規范UTF-16早了一年多, 當時只有UCS-2可選). 由于UCS-2先天不足, 造成了所有字符在js中都是2個字節. 如果是4個字節的字符, 將會默認被當作兩個雙字節字符處理. 因此 js 的字符處理函數都會受到限制, 無法返回正確結果. 如下:
var s = "?"; console.log(s == "uD834uDF06");//true ?相當于UTF-16中的0xD834DF06 console.log(s.length);//2 長度為2, 表示這是4字節字符幸運的是, ES6可以自動識別4字節的字符.因此遍歷字符串可以直接使用for of循環. 同時, js中如果直接使用碼點表示Unicode字符, 對于4字節字符, ES5里是沒辦法識別的. 為此ES6修復了這個問題, 只需將碼點放在大括號內即可.
console.log(s === "u1D306");//false ES5無法識別? console.log(s === "u{1D306}");//true ES6可以借助大括號識別?附: ES6新增的處理4字節碼的函數String.fromCodePoint():從Unicode碼點返回對應字符
String.prototype.codePointAt():從字符返回對應的碼點
String.prototype.at():返回字符串給定位置的字符
有關js中的unicode字符集, 請參考阮一峰老師的 Unicode與JavaScript詳解 .
以上是ES6對正則的擴展. 另一個方面, 從方法上看, javaScript 中與正則表達式有關的方法有:
方法名 compile test exec match search replace split 所屬對象 RegExp RegExp RegExp String String String String 由上, 一共有7個與js相關的方法, 這些方法分別來自于 RegExp 與 String 對象. 首先我們先來看看js中的正則類 RegExp.
RegExpRegExp 對象表示正則表達式, 主要用于對字符串執行模式匹配.
語法: new RegExp(pattern[, flags])
參數 pattern 是一個字符串, 指定了正則表達式字符串或其他的正則表達式對象.
參數 flags 是一個可選的字符串, 包含屬性 "g"、"i" 和 "m", 分別用于指定全局匹配、區分大小寫的匹配和多行匹配. 如果pattern 是正則表達式, 而不是字符串, 則必須省略該參數.
var pattern = "[0-9]"; var reg = new RegExp(pattern,"g"); // 上述創建正則表達式對象,可以用對象字面量形式代替,也推薦下面這種 var reg = /[0-9]/g;以上, 通過對象字面量和構造函數創建正則表達式, 有個小插曲.
"對于正則表達式的直接量, ECMAscript 3規定在每次它時都會返回同一個RegExp對象, 因此用直接量創建的正則表達式的會共享一個實例. 直到ECMAScript 5才規定每次返回不同的實例."
所以, 現在我們基本不用擔心這個問題, 只需要注意在低版本的非IE瀏覽器中盡量使用構造函數創建正則(這點上, IE一直遵守ES5規定, 其他瀏覽器的低級版本遵循ES3規定).
RegExp 實例對象包含如下屬性:
compile
實例屬性 描述 global 是否包含全局標志(true/false) ignoreCase 是否包含區分大小寫標志(true/false) multiline 是否包含多行標志(true/false) source 返回創建RegExp對象實例時指定的表達式文本字符串形式 lastIndex 表示原字符串中匹配的字符串末尾的后一個位置, 默認為0 flags(ES6) 返回正則表達式的修飾符 sticky(ES6) 是否設置了y(粘連)修飾符(true/false) compile 方法用于在執行過程中改變和重新編譯正則表達式.
語法: compile(pattern[, flags])
參數介紹請參考上述 RegExp 構造器. 用法如下:
var reg = new RegExp("abc", "gi"); var reg2 = reg.compile("new abc", "g"); console.log(reg);// /new abc/g console.log(reg2);// undefined可見 compile 方法會改變原正則表達式對象, 并重新編譯, 而且它的返回值為空.
testtest 方法用于檢測一個字符串是否匹配某個正則規則, 只要是字符串中含有與正則規則匹配的文本, 該方法就返回true, 否則返回 false.
語法: test(string), 用法如下:
console.log(/[0-9]+/.test("abc123"));//true console.log(/[0-9]+/.test("abc"));//false以上, 字符串"abc123" 包含數字, 故 test 方法返回 true; 而 字符串"abc" 不包含數字, 故返回 false.
如果需要使用 test 方法測試字符串是否完成匹配某個正則規則, 那么可以在正則表達式里增加開始(^)和結束($)元字符. 如下:
console.log(/^[0-9]+$/.test("abc123"));//false以上, 由于字符串"abc123" 并非以數字開始, 也并非以數字結束, 故 test 方法返回false.
實際上, 如果正則表達式帶有全局標志(帶有參數g)時, test 方法還受正則對象的lastIndex屬性影響,如下:
var reg = /[a-z]+/;//正則不帶全局標志 console.log(reg.test("abc"));//true console.log(reg.test("de"));//true var reg = /[a-z]+/g;//正則帶有全局標志g console.log(reg.test("abc"));//true console.log(reg.lastIndex);//3, 下次運行test時,將從索引為3的位置開始查找 console.log(reg.test("de"));//false該影響將在exec 方法講解中予以分析.
execexec 方法用于檢測字符串對正則表達式的匹配, 如果找到了匹配的文本, 則返回一個結果數組, 否則返回null.
語法: exec(string)
exec 方法返回的數組中包含兩個額外的屬性, index 和 input. 并且該數組具有如下特點:
第 0 個項表示正則表達式捕獲的文本
第 1~n 項表示第 1~n 個反向引用, 依次指向第 1~n 個分組捕獲的文本, 可以使用RegExp.$ + "編號1~n" 依次獲取分組中的文本
index 表示匹配字符串的初始位置
input 表示正在檢索的字符串
無論正則表達式有無全局標示"g", exec 的表現都相同. 但正則表達式對象的表現卻有些不同. 下面我們來詳細說明下正則表達式對象的表現都有哪些不同.
假設正則表達式對象為 reg , 檢測的字符為 string , reg.exec(string) 返回值為 array.
若 reg 包含全局標示"g", 那么 reg.lastIndex 屬性表示原字符串中匹配的字符串末尾的后一個位置, 即下次匹配開始的位置, 此時 reg.lastIndex == array.index(匹配開始的位置) + array[0].length(匹配字符串的長度). 如下:
var reg = /([a-z]+)/gi, string = "World Internet Conference"; var array = reg.exec(string); console.log(array);//["World", "World", index: 0, input: "World Internet Conference"] console.log(RegExp.$1);//World console.log(reg.lastIndex);//5, 剛好等于 array.index + array[0].length隨著檢索繼續, array.index 的值將往后遞增, 也就是說, reg.lastIndex 的值也會同步往后遞增. 因此, 我們也可以通過反復調用 exec 方法來遍歷字符串中所有的匹配文本. 直到 exec 方法再也匹配不到文本時, 它將返回 null, 并把 reg.lastIndex 屬性重置為 0.
接著上述例子, 我們繼續執行代碼, 看看上面說的對不對, 如下所示:
array = reg.exec(string); console.log(array);//["Internet", "Internet", index: 6, input: "World Internet Conference"] console.log(reg.lastIndex);//14 array = reg.exec(string); console.log(array);//["Conference", "Conference", index: 15, input: "World Internet Conference"] console.log(reg.lastIndex);//25 array = reg.exec(string); console.log(array);//null console.log(reg.lastIndex);//0以上代碼中, 隨著反復調用 exec 方法, reg.lastIndex 屬性最終被重置為 0.
問題回顧
在 test 方法的講解中, 我們留下了一個問題. 如果正則表達式帶有全局標志g, 以上 test 方法的執行結果將受 reg.lastIndex影響, 不僅如此, exec 方法也一樣. 由于 reg.lastIndex 的值并不總是為零, 并且它決定了下次匹配開始的位置, 如果在一個字符串中完成了一次匹配之后要開始檢索新的字符串, 那就必須要手動地把 lastIndex 屬性重置為 0. 避免出現下面這種錯誤:
var reg = /[0-9]+/g, str1 = "123abc", str2 = "123456"; reg.exec(str1); console.log(reg.lastIndex);//3 var array = reg.exec(str2); console.log(array);//["456", index: 3, input: "123456"]以上代碼, 正確執行結果應該是 "123456", 因此建議在第二次執行 exec 方法前, 增加一句 "reg.lastIndex = 0;".
若 reg 不包含全局標示"g", 那么 exec 方法的執行結果(array)將與 string.match(reg) 方法執行結果完全相同.
Stringmatch, search, replace, split 方法請參考 字符串常用方法 中的講解.
如下展示了使用捕獲性分組處理文本模板, 最終生成完整字符串的過程:
var tmp = "An ${a} a ${b} keeps the ${c} away"; var obj = { a:"apple", b:"day", c:"doctor" }; function tmpl(t,o){ return t.replace(/${(.)}/g,function(m,p){ console.log("m:"+m+" p:"+p); return o[p]; }); } tmpl(tmp,obj);上述功能使用ES6可這么實現:
var obj = { a:"apple", b:"day", c:"doctor" }; with(obj){ console.log(`An ${a} a ${b} keeps the ${c} away`); }正則表達式在H5中的應用H5中新增了 pattern 屬性, 規定了用于驗證輸入字段的模式, pattern的模式匹配支持正則表達式的書寫方式. 默認 pattern 屬性是全部匹配, 即無論正則表達式中有無 "^", "$" 元字符, 它都是匹配所有文本.
注: pattern 適用于以下 input 類型:text, search, url, telephone, email 以及 password. 如果需要取消表單驗證, 在form標簽上增加 novalidate 屬性即可.
正則引擎目前正則引擎有兩種, DFA 和 NFA, NFA又可以分為傳統型NFA和POSIX NFA.
DFA?Deterministic finite automaton 確定型有窮自動機
NFA Non-deterministic finite automaton 非確定型有窮自動機
Traditional NFA
POSIX NFA
DFA引擎不支持回溯, 匹配快速, 并且不支持捕獲組, 因此也就不支持反向引用. 上述awk, egrep命令均支持 DFA引擎.
POSIX NFA主要指符合POSIX標準的NFA引擎, 像 javaScript, java, php, python, c#等語言均實現了NFA引擎.
有關正則表達式詳細的匹配原理, 暫時沒在網上看到適合的文章, 建議選讀 Jeffrey Friedl 的 <精通正則表達式>[第三版] 中第4章-表達式的匹配原理(p143-p183), Jeffrey Friedl 對正則表達式有著深刻的理解, 相信他能夠幫助您更好的學習正則.
有關NFA引擎的簡單實現, 可以參考文章 基于ε-NFA的正則表達式引擎 - twoon.
總結在學習正則的初級階段, 重在理解 ①貪婪與非貪婪模式, ②分組, ③捕獲性與非捕獲性分組, ④命名分組, ⑤固化分組, 體會設計的精妙之處. 而高級階段, 主要在于熟練運用⑥零寬斷言(或環視)解決問題, 并且熟悉正則匹配的原理.
實際上, 正則在 javaScript 中的功能不算強大, js 僅僅支持了①貪婪與非貪婪模式, ②分組, ③捕獲性與非捕獲性分組 以及 ⑥零寬斷言中的順序環視. 如果再稍微熟悉些 js 中7種與正則有關的方法(compile, test, exec, match, search, replace, split), 那么處理文本或字符串將游刃有余.
正則表達式, 在文本處理方面天賦異稟, 它的功能十分強大, 很多時候甚至是唯一解決方案. 正則不局限于js, 當下熱門的編輯器(比如Sublime, Atom) 以及 IDE(比如WebStorm, IntelliJ?IDEA) 都支持它. 您甚至可以在任何時候任何語言中, 嘗試使用正則解決問題, 也許之前不能解決的問題, 現在可以輕松的解決.
附其他語言正則資料:
Python正則表達式操作指南
java正則表達式
本文作者: louis
本文簡介: 本文斷斷續續歷時兩個月而成, 共計12k字, 為求簡潔全面地還原前端場景中正則的使用規律, 搜集了大量正則相關資料, 并剔除不少冗余字句, 碼字不易, 喜歡的請點個贊?或者收藏, 我將持續保持更新.
原文地址: http://louiszhai.github.io/20...參考文章
Jeffrey Friedl 的 <精通正則表達式>[第三版]
linux shell 正則表達式(BREs,EREs,PREs)差異比較
正則表達式之捕獲組/非捕獲組介紹_正則表達式_腳本之家
正則表達式(一) -- 元字符 - 逆心 - 博客園
正則表達式詳解 - guisu,程序人生。 逆水行舟,不進則退。 - 博客頻道 - CSDN.NET
正則表達式之固化分組 - taek - 博客園
正則表達式之 貪婪與非貪婪模式詳解(概述)_正則表達式_腳本之家
JAVASCRIPT 正則表達式學習-->基礎與零寬斷言(轉自司徒正美) - 隨風之羽 - 博客頻道 - CSDN.NET
Unicode與JavaScript詳解 - 阮一峰的網絡日志
0-9 ?
fnrtv ?
A-Za-z0-9_ ?
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/88131.html
摘要:特意對前端學習資源做一個匯總,方便自己學習查閱參考,和好友們共同進步。 特意對前端學習資源做一個匯總,方便自己學習查閱參考,和好友們共同進步。 本以為自己收藏的站點多,可以很快搞定,沒想到一入匯總深似海。還有很多不足&遺漏的地方,歡迎補充。有錯誤的地方,還請斧正... 托管: welcome to git,歡迎交流,感謝star 有好友反應和斧正,會及時更新,平時業務工作時也會不定期更...
摘要:好多編輯器例如等都支持這樣的語法來快速的編寫代碼如何優雅地使用把標簽放在結束標簽之后結束標簽之前的差別什么是響應式設計怎樣進行 書籍 《JavaScriptDOM編程藝術》《JavaScript高級程序設計》《JavaScript框架設計》《JavaScript專家編程》《JavaScript Ninjia》《JavaScript語言精粹(修訂版)》《JavaScript設計模式》《J...
摘要:好多編輯器例如等都支持這樣的語法來快速的編寫代碼如何優雅地使用把標簽放在結束標簽之后結束標簽之前的差別什么是響應式設計怎樣進行 書籍 《JavaScriptDOM編程藝術》《JavaScript高級程序設計》《JavaScript框架設計》《JavaScript專家編程》《JavaScript Ninjia》《JavaScript語言精粹(修訂版)》《JavaScript設計模式》《J...
摘要:好多編輯器例如等都支持這樣的語法來快速的編寫代碼如何優雅地使用把標簽放在結束標簽之后結束標簽之前的差別什么是響應式設計怎樣進行 書籍 《JavaScriptDOM編程藝術》《JavaScript高級程序設計》《JavaScript框架設計》《JavaScript專家編程》《JavaScript Ninjia》《JavaScript語言精粹(修訂版)》《JavaScript設計模式》《J...
閱讀 1660·2021-09-28 09:35
閱讀 1131·2019-08-30 15:54
閱讀 1656·2019-08-30 15:44
閱讀 3363·2019-08-30 14:09
閱讀 488·2019-08-29 14:05
閱讀 2662·2019-08-28 17:53
閱讀 1978·2019-08-26 13:41
閱讀 1710·2019-08-26 13:26