摘要:使用構(gòu)造器時(shí),需要將模式書寫成普通的字符串,因此反斜杠的使用規(guī)則與往常相同。構(gòu)造器的后四個(gè)參數(shù)小時(shí)分鐘秒毫秒是可選的,如果用戶沒有指定這些參數(shù),則參數(shù)的值默認(rèn)為。
來源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:Regular Expressions
譯者:飛龍
協(xié)議:CC BY-NC-SA 4.0
自豪地采用谷歌翻譯
部分參考了《JavaScript 編程精解(第 2 版)》
一些人遇到問題時(shí)會(huì)認(rèn)為,“我知道了,我會(huì)用正則表達(dá)式?!爆F(xiàn)在它們有兩個(gè)問題了。
Jamie Zawinski
Yuan-Ma said, "When you cut against the grain of the wood, much strength is needed. When you program against the grain of the problem, much code is needed."
Master Yuan-Ma,《The Book of Programming》
程序設(shè)計(jì)工具技術(shù)的發(fā)展與傳播方式是在混亂中不斷進(jìn)化。在此過程中獲勝的往往不是優(yōu)雅或杰出的一方,而是那些瞄準(zhǔn)主流市場(chǎng),并能夠填補(bǔ)市場(chǎng)需求的,或者碰巧與另一種成功的技術(shù)集成在一起的工具技術(shù)。
本章將會(huì)討論正則表達(dá)式(regular expression)這種工具。正則表達(dá)式是一種描述字符串?dāng)?shù)據(jù)模式的方法。它們形成了一種小而獨(dú)立的語言,也是 JavaScript 和許多其他語言和系統(tǒng)的一部分。
正則表達(dá)式雖然不易理解,但是功能非常強(qiáng)大。正則表達(dá)式的語法有點(diǎn)詭異,JavaScript 提供的程序設(shè)計(jì)接口也不太易用。但正則表達(dá)式的確是檢查、處理字符串的強(qiáng)力工具。如果讀者能夠正確理解正則表達(dá)式,將會(huì)成為更高效的程序員。
創(chuàng)建正則表達(dá)式正則表達(dá)式是一種對(duì)象類型。我們可以使用兩種方法來構(gòu)造正則表達(dá)式:一是使用RegExp構(gòu)造器構(gòu)造一個(gè)正則表達(dá)式對(duì)象;二是使用斜杠(/)字符將模式包圍起來,生成一個(gè)字面值。
let re1 = new RegExp("abc"); let re2 = /abc/;
這兩個(gè)正則表達(dá)式對(duì)象都表示相同的模式:字符a后緊跟一個(gè)b,接著緊跟一個(gè)c。
使用RegExp構(gòu)造器時(shí),需要將模式書寫成普通的字符串,因此反斜杠的使用規(guī)則與往常相同。
第二種寫法將模式寫在斜杠之間,處理反斜杠的方式與第一種方法略有差別。首先,由于斜杠會(huì)結(jié)束整個(gè)模式,因此模式中包含斜杠時(shí),需在斜杠前加上反斜杠。此外,如果反斜杠不是特殊字符代碼(比如 )的一部分,則會(huì)保留反斜杠,不像字符串中會(huì)將其忽略,也不會(huì)改變模式的含義。一些字符,比如問號(hào)、加號(hào)在正則表達(dá)式中有特殊含義,如果你想要表示其字符本身,需要在字符前加上反斜杠。
let eighteenPlus = /eighteen+/;匹配測(cè)試
正則表達(dá)式對(duì)象有許多方法。其中最簡(jiǎn)單的就是test方法。test方法接受用戶傳遞的字符串,并返回一個(gè)布爾值,表示字符串中是否包含能與表達(dá)式模式匹配的字符串。
console.log(/abc/.test("abcde")); // → true console.log(/abc/.test("abxde")); // → false
不包含特殊字符的正則表達(dá)式簡(jiǎn)單地表示一個(gè)字符序列。如果使用test測(cè)試字符串時(shí),字符串中某處出現(xiàn)abc(不一定在開頭),則返回true。
字符集我們也可調(diào)用indexOf來找出字符串中是否包含abc。正則表達(dá)式允許我們表達(dá)一些更復(fù)雜的模式。
假如我們想匹配任意數(shù)字。在正則表達(dá)式中,我們可以將一組字符放在兩個(gè)方括號(hào)之間,該表達(dá)式可以匹配方括號(hào)中的任意字符。
下面兩個(gè)表達(dá)式都可以匹配包含數(shù)字的字符串。
console.log(/[0123456789]/.test("in 1992")); // → true console.log(/[0-9]/.test("in 1992")); // → true
我們可以在方括號(hào)中的兩個(gè)字符間插入連字符(–),來指定一個(gè)字符范圍,范圍內(nèi)的字符順序由字符 Unicode 代碼決定。在 Unicode 字符順序中,0 到 9 是從左到右彼此相鄰的(代碼從48到57),因此[0-9]覆蓋了這一范圍內(nèi)的所有字符,也就是說可以匹配任意數(shù)字。
許多常見字符組都有自己的內(nèi)置簡(jiǎn)寫。 數(shù)字就是其中之一: d與[0-9]表示相同的東西。
d任意數(shù)字符號(hào)
w字母和數(shù)字符號(hào)(單詞符號(hào))
s任意空白符號(hào)(空格,制表符,換行符等類似符號(hào))
D非數(shù)字符號(hào)
W非字母和數(shù)字符號(hào)
S非空白符號(hào)
.除了換行符以外的任意符號(hào)
因此你可以使用下面的表達(dá)式匹配類似于30-01-2003 15:20這樣的日期數(shù)字格式:
let dateTime = /dd-dd-ffffdd dd:dd/; console.log(dateTime.test("30-01-2003 15:20")); // → true console.log(dateTime.test("30-jan-2003 15:20")); // → false
這個(gè)表達(dá)式看起來是不是非常糟糕?該表達(dá)式中一半都是反斜杠,影響讀者的理解,使得讀者難以揣摩表達(dá)式實(shí)際想要表達(dá)的模式。稍后我們會(huì)看到一個(gè)稍加改進(jìn)的版本。
我們也可以將這些反斜杠代碼用在方括號(hào)中。例如,[d.]匹配任意數(shù)字或一個(gè)句號(hào)。但是方括號(hào)中的句號(hào)會(huì)失去其特殊含義。其他特殊字符也是如此,比如+。
你可以在左方括號(hào)后添加脫字符(^)來排除某個(gè)字符集,即表示不匹配這組字符中的任何字符。
let notBinary = /[^01]/; console.log(notBinary.test("1100100010100110")); // → false console.log(notBinary.test("1100100010200110")); // → true部分模式重復(fù)
現(xiàn)在我們已經(jīng)知道如何匹配一個(gè)數(shù)字。如果我們想匹配一個(gè)整數(shù)(一個(gè)或多個(gè)數(shù)字的序列),該如何處理呢?
在正則表達(dá)式某個(gè)元素后面添加一個(gè)加號(hào)(+),表示該元素至少重復(fù)一次。因此/d+/可以匹配一個(gè)或多個(gè)數(shù)字字符。
console.log(/"d+"/.test(""123"")); // → true console.log(/"d+"/.test("""")); // → false console.log(/"d*"/.test(""123"")); // → true console.log(/"d*"/.test("""")); // → true
星號(hào)(*)擁有類似含義,但是可以匹配模式不存在的情況。在正則表達(dá)式的元素后添加星號(hào)并不會(huì)導(dǎo)致正則表達(dá)式停止匹配該元素后面的字符。只有正則表達(dá)式無法找到可以匹配的文本時(shí)才會(huì)考慮匹配該元素從未出現(xiàn)的情況。
元素后面跟一個(gè)問號(hào)表示這部分模式“可選”,即模式可能出現(xiàn) 0 次或 1 次。下面的例子可以匹配neighbour(u出現(xiàn)1次),也可以匹配neighbor(u沒有出現(xiàn))。
let neighbor = /neighbou?r/; console.log(neighbor.test("neighbour")); // → true console.log(neighbor.test("neighbor")); // → true
我們可以使用花括號(hào)準(zhǔn)確指明某個(gè)模式的出現(xiàn)次數(shù)。例如,在某個(gè)元素后加上{4},則該模式需要出現(xiàn)且只能出現(xiàn) 4 次。也可以使用花括號(hào)指定一個(gè)范圍:比如{2,4}表示該元素至少出現(xiàn) 2 次,至多出現(xiàn) 4 次。
這里給出另一個(gè)版本的正則表達(dá)式,可以匹配日期、月份、小時(shí),每個(gè)數(shù)字都可以是一位或兩位數(shù)字。這種形式更易于解釋。
let dateTime = /d{1,2}-d{1,2}-d{4} d{1,2}:d{2}/; console.log(dateTime.test("30-1-2003 8:45")); // → true
花括號(hào)中也可以省略逗號(hào)任意一側(cè)的數(shù)字,表示不限制這一側(cè)的數(shù)量。因此{,5}表示 0 到 5 次,而{5,}表示至少五次。
子表達(dá)式分組為了一次性對(duì)多個(gè)元素使用*或者+,那么你必須使用圓括號(hào),創(chuàng)建一個(gè)分組。對(duì)于后面的操作符來說,圓括號(hào)里的表達(dá)式算作單個(gè)元素。
let cartoonCrying = /boo+(hoo+)+/i; console.log(cartoonCrying.test("Boohoooohoohooo")); // → true
第一個(gè)和第二個(gè)+字符分別作用于boo與hoo的o字符,而第三個(gè)+字符則作用于整個(gè)元組(hoo+),可以匹配hoo+這種正則表達(dá)式出現(xiàn)一次及一次以上的情況。
示例中表達(dá)式末尾的i表示正則表達(dá)式不區(qū)分大小寫,雖然模式中使用小寫字母,但可以匹配輸入字符串中的大寫字母B。
匹配和分組test方法是匹配正則表達(dá)式最簡(jiǎn)單的方法。該方法只負(fù)責(zé)判斷字符串是否與某個(gè)模式匹配。正則表達(dá)式還有一個(gè)exec(執(zhí)行,execute)方法,如果無法匹配模式則返回null,否則返回一個(gè)表示匹配字符串信息的對(duì)象。
let match = /d+/.exec("one two 100"); console.log(match); // → ["100"] console.log(match.index); // → 8
exec方法返回的對(duì)象包含index屬性,表示字符串成功匹配的起始位置。除此之外,該對(duì)象看起來像(而且實(shí)際上就是)一個(gè)字符串?dāng)?shù)組,其首元素是與模式匹配的字符串——在上面的例子中就是我們查找的數(shù)字序列。
字符串也有一個(gè)類似的match方法。
console.log("one two 100".match(/d+/)); // → ["100"]
若正則表達(dá)式包含使用圓括號(hào)包圍的子表達(dá)式分組,與這些分組匹配的文本也會(huì)出現(xiàn)在數(shù)組中。第一個(gè)元素是與整個(gè)模式匹配的字符串,其后是與第一個(gè)分組匹配的部分字符串(表達(dá)式中第一次出現(xiàn)左圓括號(hào)的那部分),然后是第二個(gè)分組。
let quotedText = /"([^"]*)"/; console.log(quotedText.exec("she said "hello"")); // → [""hello"", "hello"]
若分組最后沒有匹配任何字符串(例如在元組后加上一個(gè)問號(hào)),結(jié)果數(shù)組中與該分組對(duì)應(yīng)的元素將是undefined。類似的,若分組匹配了多個(gè)元素,則數(shù)組中只包含最后一個(gè)匹配項(xiàng)。
console.log(/bad(ly)?/.exec("bad")); // → ["bad", undefined] console.log(/(d)+/.exec("123")); // → ["123", "3"]
分組是提取部分字符串的實(shí)用特性。如果我們不只是想驗(yàn)證字符串中是否包含日期,還想將字符串中的日期字符串提取出來,并將其轉(zhuǎn)換成等價(jià)的日期對(duì)象,那么我們可以使用圓括號(hào)包圍那些匹配數(shù)字的模式字符串,并直接將日期從exec的結(jié)果中提取出來。
不過,我們暫且先討論另一個(gè)話題——在 JavaScript 中存儲(chǔ)日期和時(shí)間的內(nèi)建方法。
日期類JavaScript 提供了用于表示日期的標(biāo)準(zhǔn)類,我們甚至可以用其表示時(shí)間點(diǎn)。該類型名為Date。如果使用new創(chuàng)建一個(gè)Date對(duì)象,你會(huì)得到當(dāng)前的日期和時(shí)間。
console.log(new Date()); // → Mon Nov 13 2017 16:19:11 GMT+0100 (CET)
你也可以創(chuàng)建表示特定時(shí)間的對(duì)象。
console.log(new Date(2009, 11, 9)); // → Wed Dec 09 2009 00:00:00 GMT+0100 (CET) console.log(new Date(2009, 11, 9, 12, 59, 59, 999)); // → Wed Dec 09 2009 12:59:59 GMT+0100 (CET)
JavaScript 中約定是:使用從 0 開始的數(shù)字表示月份(因此使用 11 表示 12 月),而使用從1開始的數(shù)字表示日期。這非常容易令人混淆。要注意這個(gè)細(xì)節(jié)。
構(gòu)造器的后四個(gè)參數(shù)(小時(shí)、分鐘、秒、毫秒)是可選的,如果用戶沒有指定這些參數(shù),則參數(shù)的值默認(rèn)為 0。
時(shí)間戳存儲(chǔ)為 UTC 時(shí)區(qū)中 1970 年以來的毫秒數(shù)。 這遵循一個(gè)由“Unix 時(shí)間”設(shè)定的約定,該約定是在那個(gè)時(shí)候發(fā)明的。 你可以對(duì) 1970 年以前的時(shí)間使用負(fù)數(shù)。 日期對(duì)象上的getTime方法返回這個(gè)數(shù)字。 你可以想象它會(huì)很大。
console.log(new Date(2013, 11, 19).getTime()); // → 1387407600000 console.log(new Date(1387407600000)); // → Thu Dec 19 2013 00:00:00 GMT+0100 (CET)
如果你為Date構(gòu)造器指定了一個(gè)參數(shù),構(gòu)造器會(huì)將該參數(shù)看成毫秒數(shù)。你可以創(chuàng)建一個(gè)新的Date對(duì)象,并調(diào)用getTime方法,或調(diào)用Date.now()函數(shù)來獲取當(dāng)前時(shí)間對(duì)應(yīng)的毫秒數(shù)。
Date對(duì)象提供了一些方法來提取時(shí)間中的某些數(shù)值,比如getFullYear、getMonth、getDate、getHours、getMinutes、getSeconds。除了getFullYear之外該對(duì)象還有一個(gè)getYear方法,會(huì)返回使用兩位數(shù)字表示的年份(比如 93 或 14),但很少用到。
通過在希望捕獲的那部分模式字符串兩邊加上圓括號(hào),我們可以從字符串中創(chuàng)建對(duì)應(yīng)的Date對(duì)象。
function getDate(string) { let [_, day, month, year] = /(d{1,2})-(d{1,2})-(d{4})/.exec(string); return new Date(year, month - 1, day); } console.log(getDate("30-1-2003")); // → Thu Jan 30 2003 00:00:00 GMT+0100 (CET)
_(下劃線)綁定被忽略,并且只用于跳過由exec返回的數(shù)組中的,完整匹配元素。
單詞和字符串邊界不幸的是,getDate會(huì)從字符串"100-1-30000"中提取出一個(gè)無意義的日期——00-1-3000。正則表達(dá)式可以從字符串中的任何位置開始匹配,在我們的例子中,它從第二個(gè)字符開始匹配,到倒數(shù)第二個(gè)字符為止。
如果我們想要強(qiáng)制匹配整個(gè)字符串,可以使用^標(biāo)記和$標(biāo)記。脫字符表示輸入字符串起始位置,美元符號(hào)表示字符串結(jié)束位置。因此/^d+$/可以匹配整個(gè)由一個(gè)或多個(gè)數(shù)字組成的字符串,/^!/匹配任何以感嘆號(hào)開頭的字符串,而/x^/不匹配任何字符串(字符串起始位置之前不可能有字符x)。
另一方面,如果我們想要確保日期字符串起始結(jié)束位置在單詞邊界上,可以使用標(biāo)記。所謂單詞邊界,指的是起始和結(jié)束位置都是單詞字符(也就是w代表的字符集合),而起始位置的前一個(gè)字符以及結(jié)束位置的后一個(gè)字符不是單詞字符。
console.log(/cat/.test("concatenate")); // → true console.log(/cat/.test("concatenate")); // → false
這里需要注意,邊界標(biāo)記并不匹配實(shí)際的字符,只在強(qiáng)制正則表達(dá)式滿足模式中的條件時(shí)才進(jìn)行匹配。
選項(xiàng)模式假如我們不僅想知道文本中是否包含數(shù)字,還想知道數(shù)字之后是否跟著一個(gè)單詞(pig、cow或chicken)或其復(fù)數(shù)形式。
那么我們可以編寫三個(gè)正則表達(dá)式并輪流測(cè)試,但還有一種更好的方式。管道符號(hào)(|)表示從其左側(cè)的模式和右側(cè)的模式任意選擇一個(gè)進(jìn)行匹配。因此代碼如下所示。
let animalCount = /d+ (pig|cow|chicken)s?/; console.log(animalCount.test("15 pigs")); // → true console.log(animalCount.test("15 pigchickens")); // → false
小括號(hào)可用于限制管道符號(hào)選擇的模式范圍,而且你可以連續(xù)使用多個(gè)管道符號(hào),表示從多于兩個(gè)模式中選擇一個(gè)備選項(xiàng)進(jìn)行匹配。
匹配原理從概念上講,當(dāng)你使用exec或test時(shí),正則表達(dá)式引擎在你的字符串中尋找匹配,通過首先從字符串的開頭匹配表達(dá)式,然后從第二個(gè)字符匹配表達(dá)式,直到它找到匹配或達(dá)到字符串的末尾。 它會(huì)返回找到的第一個(gè)匹配,或者根本找不到任何匹配。
為了進(jìn)行實(shí)際的匹配,引擎會(huì)像處理流程圖一樣處理正則表達(dá)式。 這是上例中用于家畜表達(dá)式的圖表:
如果我們可以找到一條從圖表左側(cè)通往圖表右側(cè)的路徑,則可以說“表達(dá)式產(chǎn)生了匹配”。我們保存在字符串中的當(dāng)前位置,每移動(dòng)通過一個(gè)盒子,就驗(yàn)證當(dāng)前位置之后的部分字符串是否與該盒子匹配。
因此,如果我們嘗試從位置 4 匹配"the 3 pigs",大致會(huì)以如下的過程通過流程圖:
在位置 4,有一個(gè)單詞邊界,因此我們通過第一個(gè)盒子。
依然在位置 4,我們找到一個(gè)數(shù)字,因此我們通過第二個(gè)盒子。
在位置 5,有一條路徑循環(huán)回到第二個(gè)盒子(數(shù)字)之前,而另一條路徑則移動(dòng)到下一個(gè)盒子(單個(gè)空格字符)。由于這里是一個(gè)空格,而非數(shù)字,因此我們必須選擇第二條路徑。
我們目前在位置 6(pig的起始位置),而表中有三路分支。這里看不到"cow"或"chicken",但我們看到了"pig",因此選擇"pig"這條分支。
在位置 9(三路分支之后),有一條路徑跳過了s這個(gè)盒子,直接到達(dá)最后的單詞邊界,另一條路徑則匹配s。這里有一個(gè)s字符,而非單詞邊界,因此我們通過s這個(gè)盒子。
我們?cè)谖恢?10(字符串結(jié)尾),只能匹配單詞邊界。而字符串結(jié)尾可以看成一個(gè)單詞邊界,因此我們通過最后一個(gè)盒子,成功匹配字符串。
回溯正則表達(dá)式/([01]+b|d+|[da-f]h)/可以匹配三種字符串:以b結(jié)尾的二進(jìn)制數(shù)字,以h結(jié)尾的十六進(jìn)制數(shù)字(即以 16 為進(jìn)制,字母a到f表示數(shù)字 10 到 15),或者沒有后綴字符的常規(guī)十進(jìn)制數(shù)字。這是對(duì)應(yīng)的圖表。
當(dāng)匹配該表達(dá)式時(shí),常常會(huì)發(fā)生一種情況:輸入的字符串進(jìn)入上方(二進(jìn)制)分支的匹配過程,但輸入中并不包含二進(jìn)制數(shù)字。我們以匹配字符串"103"為例,匹配過程只有遇到字符 3 時(shí)才知道進(jìn)入了錯(cuò)誤分支。該字符串匹配我們給出的表達(dá)式,但沒有匹配目前應(yīng)當(dāng)處于的分支。
因此匹配器執(zhí)行“回溯”。進(jìn)入一個(gè)分支時(shí),匹配器會(huì)記住當(dāng)前位置(在本例中,是在字符串起始,剛剛通過圖中第一個(gè)表示邊界的盒子),因此若當(dāng)前分支無法匹配,可以回退并嘗試另一條分支。對(duì)于字符串"103",遇到字符 3 之后,它會(huì)開始嘗試匹配十六進(jìn)制數(shù)字的分支,它會(huì)再次失敗,因?yàn)閿?shù)字后面沒有h。所以它嘗試匹配進(jìn)制數(shù)字的分支,由于這條分支可以匹配,因此匹配器最后的會(huì)返回十進(jìn)制數(shù)的匹配信息。
一旦字符串與模式完全匹配,匹配器就會(huì)停止。這意味著多個(gè)分支都可能匹配一個(gè)字符串,但匹配器最后只會(huì)使用第一條分支(按照出現(xiàn)在正則表達(dá)式中的出現(xiàn)順序排序)。
回溯也會(huì)發(fā)生在處理重復(fù)模式運(yùn)算符(比如+和*)時(shí)。如果使用"abcxe"匹配/^.*x/,.*部分,首先嘗試匹配整個(gè)字符串,接著引擎發(fā)現(xiàn)匹配模式還需要一個(gè)字符x。由于字符串結(jié)尾沒有x,因此*運(yùn)算符嘗試少匹配一個(gè)字符。但匹配器依然無法在abcx之后找到x字符,因此它會(huì)再次回溯,此時(shí)*運(yùn)算符只匹配abc。現(xiàn)在匹配器發(fā)現(xiàn)了所需的x,接著報(bào)告從位置 0 到位置 4 匹配成功。
我們有可能編寫需要大量回溯的正則表達(dá)式。當(dāng)模式能夠以許多種不同方式匹配輸入的一部分時(shí),這種問題就會(huì)出現(xiàn)。例如,若我們?cè)诰帉懫ヅ涠M(jìn)制數(shù)字的正則表達(dá)式時(shí),一時(shí)糊涂,可能會(huì)寫出諸如/([01]+)+b/之類的表達(dá)式。
若我們嘗試匹配一些只由 0 與 1 組成的長序列,匹配器首先會(huì)不斷執(zhí)行內(nèi)部循環(huán),直到它發(fā)現(xiàn)沒有數(shù)字為止。接下來匹配器注意到,這里不存在b,因此向前回溯一個(gè)位置,開始執(zhí)行外部循環(huán),接著再次放棄,再次嘗試執(zhí)行一次內(nèi)部循環(huán)。該過程會(huì)嘗試這兩個(gè)循環(huán)的所有可能路徑。這意味著每多出一個(gè)字符,其工作量就會(huì)加倍。甚至只需較少的一堆字符,就可使匹配實(shí)際上永不停息地執(zhí)行下去。
replace方法字符串有一個(gè)replace方法,該方法可用于將字符串中的一部分替換為另一個(gè)字符串。
console.log("papa".replace("p", "m")); // → mapa
該方法第一個(gè)參數(shù)也可以是正則表達(dá)式,這種情況下會(huì)替換正則表達(dá)式首先匹配的部分字符串。若在正則表達(dá)式后追加g選項(xiàng)(全局,Global),該方法會(huì)替換字符串中所有匹配項(xiàng),而不是只替換第一個(gè)。
console.log("Borobudur".replace(/[ou]/, "a")); // → Barobudur console.log("Borobudur".replace(/[ou]/g, "a")); // → Barabadar
如果 JavaScript 為replace添加一個(gè)額外參數(shù),或提供另一個(gè)不同的方法(replaceAll),來區(qū)分替換一次匹配還是全部匹配,將會(huì)是較為明智的方案。遺憾的是,因?yàn)槟承┰?JavaScript 依靠正則表達(dá)式的屬性來區(qū)分替換行為。
如果我們?cè)谔鎿Q字符串中使用元組,就可以體現(xiàn)出replace方法的真實(shí)威力。例如,假設(shè)我們有一個(gè)規(guī)模很大的字符串,包含了人的名字,每個(gè)名字占據(jù)一行,名字格式為“姓,名”。若我們想要交換姓名,并移除中間的逗號(hào)(轉(zhuǎn)變成“名,姓”這種格式),我們可以使用下面的代碼:
console.log( "Liskov, Barbara McCarthy, John Wadler, Philip" .replace(/(w+), (w+)/g, "$2 $1")); // → Barbara Liskov // John McCarthy // Philip Wadler
替換字符串中的$1和$2引用了模式中使用圓括號(hào)包裹的元組。$1會(huì)替換為第一個(gè)元組匹配的字符串,$2會(huì)替換為第二個(gè),依次類推,直到$9為止。也可以使用$&來引用整個(gè)匹配。
第二個(gè)參數(shù)不僅可以使用字符串,還可以使用一個(gè)函數(shù)。每次匹配時(shí),都會(huì)調(diào)用函數(shù)并以匹配元組(也可以是匹配整體)作為參數(shù),該函數(shù)返回值為需要插入的新字符串。
這里給出一個(gè)小示例:
let s = "the cia and fbi"; console.log(s.replace(/(fbi|cia)/g, str => str.toUpperCase())); // → the CIA and FBI
這里給出另一個(gè)值得討論的示例:
let stock = "1 lemon, 2 cabbages, and 101 eggs"; function minusOne(match, amount, unit) { amount = Number(amount) - 1; if (amount == 1) { // only one left, remove the "s" unit = unit.slice(0, unit.length - 1); } else if (amount == 0) { amount = "no"; } return amount + " " + unit; } console.log(stock.replace(/(d+) (w+)/g, minusOne)); // → no lemon, 1 cabbage, and 100 eggs
該程序接受一個(gè)字符串,找出所有滿足模式“一個(gè)數(shù)字緊跟著一個(gè)單詞(數(shù)字和字母)”的字符串,返回時(shí)將捕獲字符串中的數(shù)字減一。
元組(d+)最后會(huì)變成函數(shù)中的amount參數(shù),而·(w+)元組將會(huì)綁定unit。該函數(shù)將amount轉(zhuǎn)換成數(shù)字(由于該參數(shù)是d+`的匹配結(jié)果,因此此過程總是執(zhí)行成功),并根據(jù)剩下 0 還是 1,決定如何做出調(diào)整。
貪婪模式使用replace編寫一個(gè)函數(shù)移除 JavaScript 代碼中的所有注釋也是可能的。這里我們嘗試一下:
function stripComments(code) { return code.replace(///.*|/*[^]**//g, ""); } console.log(stripComments("1 + /* 2 */3")); // → 1 + 3 console.log(stripComments("x = 10;// ten!")); // → x = 10; console.log(stripComments("1 /* a */+/* b */ 1")); // → 1 1
或運(yùn)算符之前的部分匹配兩個(gè)斜杠字符,后面跟著任意數(shù)量的非換行字符。多行注釋部分較為復(fù)雜,我們使用[^](任何非空字符集合)來匹配任意字符。我們這里無法使用句號(hào),因?yàn)閴K注釋可以跨行,句號(hào)無法匹配換行符。
但最后一行的輸出顯然有錯(cuò)。
為何?
在回溯一節(jié)中已經(jīng)提到過,表達(dá)式中的[^]*部分會(huì)首先匹配所有它能匹配的部分。如果其行為引起模式的下一部分匹配失敗,匹配器才會(huì)回溯一個(gè)字符,并再次嘗試。在本例中,匹配器首先匹配整個(gè)剩余字符串,然后向前移動(dòng)。匹配器回溯四個(gè)字符后,會(huì)找到*/,并完成匹配。這并非我們想要的結(jié)果。我們的意圖是匹配單個(gè)注釋,而非到達(dá)代碼末尾并找到最后一個(gè)塊注釋的結(jié)束部分。
因?yàn)檫@種行為,所以我們說模式重復(fù)運(yùn)算符(+、*、?和{})是“貪婪”的,指的是這些運(yùn)算符會(huì)盡量多地匹配它們可以匹配的字符,然后回溯。若讀者在這些符號(hào)后加上一個(gè)問號(hào)(+?、*?、??、{}?),它們會(huì)變成非貪婪的,此時(shí)這些符號(hào)會(huì)盡量少地匹配字符,只有當(dāng)剩下的模式無法匹配時(shí)才會(huì)多進(jìn)行匹配。
而這便是我們想要的情況。通過讓星號(hào)盡量少地匹配字符,我們可以匹配第一個(gè)*/,進(jìn)而匹配一個(gè)塊注釋,而不會(huì)匹配過多內(nèi)容。
function stripComments(code) { return code.replace(///.*|/*[^]*?*//g, ""); } console.log(stripComments("1 /* a */+/* b */ 1")); // → 1 + 1
對(duì)于使用了正則表達(dá)式的程序而言,其中出現(xiàn)的大量缺陷都可歸咎于一個(gè)問題:在非貪婪模式效果更好時(shí),無意間錯(cuò)用了貪婪運(yùn)算符。若使用了模式重復(fù)運(yùn)算符,請(qǐng)首先考慮一下是否可以使用非貪婪符號(hào)替代貪婪運(yùn)算符。
動(dòng)態(tài)創(chuàng)建RegExp對(duì)象有些情況下,你無法在編寫代碼時(shí)準(zhǔn)確知道需要匹配的模式。假設(shè)你想尋找文本片段中的用戶名,并使用下劃線字符將其包裹起來使其更顯眼。由于你只有在程序運(yùn)行時(shí)才知道姓名,因此你無法使用基于斜杠的記法。
但你可以構(gòu)建一個(gè)字符串,并使用RegExp構(gòu)造器根據(jù)該字符串構(gòu)造正則表達(dá)式對(duì)象。
這里給出一個(gè)示例。
let name = "harry"; let text = "Harry is a suspicious character."; let regexp = new RegExp("(" + name + ")", "gi"); console.log(text.replace(regexp, "_$1_")); // → _Harry_ is a suspicious character.
由于我們創(chuàng)建正則表達(dá)式時(shí)使用的是普通字符串,而非使用斜杠包圍的正則表達(dá)式,因此如果想創(chuàng)建邊界,我們不得不使用兩個(gè)反斜杠。RegExp構(gòu)造器的第二個(gè)參數(shù)包含了正則表達(dá)式選項(xiàng)。在本例中,"gi"表示全局和不區(qū)分大小寫。
但由于我們的用戶是怪異的青少年,如果用戶將名字設(shè)定為"dea+hl[]rd",將會(huì)發(fā)生什么?這將會(huì)導(dǎo)致正則表達(dá)式變得沒有意義,無法匹配用戶名。
為了能夠處理這種情況,我們可以在任何有特殊含義的字符前添加反斜杠。
let name = "dea+hl[]rd"; let text = "This dea+hl[]rd guy is super annoying."; let escaped = name.replace(/[^ws]/g, "$&"); let regexp = new RegExp("(" + escaped + ")", "gi"); console.log(text.replace(regexp, "_><_")); // → This _dea+hl[]rd_ guy is super annoying.search方法
字符串的indexOf方法不支持以正則表達(dá)式為參數(shù)。
但還有一個(gè)search方法,調(diào)用該方法時(shí)需要傳遞一個(gè)正則表達(dá)式。類似于indexOf,該方法會(huì)返回首先匹配的表達(dá)式的索引,若沒有找到則返回 –1。
console.log(" word".search(/S/)); // → 2 console.log(" ".search(/S/)); // → -1
遺憾的是,沒有任何方式可以指定匹配的起始偏移(就像indexOf的第二個(gè)參數(shù)),而指定起始偏移這個(gè)功能是很實(shí)用的。
lastIndex屬性exec方法同樣沒提供方便的方法來指定字符串中的起始匹配位置。但我們可以使用一種比較麻煩的方法來實(shí)現(xiàn)該功能。
正則表達(dá)式對(duì)象包含了一些屬性。其中一個(gè)屬性是source,該屬性包含用于創(chuàng)建正則表達(dá)式的字符串。另一個(gè)屬性是lastIndex,可以在極少數(shù)情況下控制下一次匹配的起始位置。
所謂的極少數(shù)情況,指的是當(dāng)正則表達(dá)式啟用了全局(g)或者粘性(y),并且使用exec匹配模式的時(shí)候。此外,另一個(gè)解決方案應(yīng)該是向exec傳遞的額外參數(shù),但 JavaScript 的正則表達(dá)式接口能設(shè)計(jì)得如此合理才是怪事。
let pattern = /y/g; pattern.lastIndex = 3; let match = pattern.exec("xyzzy"); console.log(match.index); // → 4 console.log(pattern.lastIndex); // → 5
如果成功匹配模式,exec調(diào)用會(huì)自動(dòng)更新lastIndex屬性,來指向匹配字符串后的位置。如果無法匹配,會(huì)將lastIndex清零(就像新構(gòu)建的正則表達(dá)式對(duì)象lastIndex屬性為零一樣)。
全局和粘性選項(xiàng)之間的區(qū)別在于,啟用粘性時(shí),僅當(dāng)匹配直接從lastIndex開始時(shí),搜索才會(huì)成功,而全局搜索中,它會(huì)搜索匹配可能起始的所有位置。
let global = /abc/g; console.log(global.exec("xyz abc")); // → ["abc"] let sticky = /abc/y; console.log(sticky.exec("xyz abc")); // → null
對(duì)多個(gè)exec調(diào)用使用共享的正則表達(dá)式值時(shí),這些lastIndex屬性的自動(dòng)更新可能會(huì)導(dǎo)致問題。 你的正則表達(dá)式可能意外地在之前的調(diào)用留下的索引處開始。
let digit = /d/g; console.log(digit.exec("here it is: 1")); // → ["1"] console.log(digit.exec("and now: 1")); // → null
全局選項(xiàng)還有一個(gè)值得深思的效果,它會(huì)改變match匹配字符串的工作方式。如果調(diào)用match時(shí)使用了全局表達(dá)式,不像exec返回的數(shù)組,match會(huì)找出所有匹配模式的字符串,并返回一個(gè)包含所有匹配字符串的數(shù)組。
console.log("Banana".match(/an/g)); // → ["an", "an"]
因此使用全局正則表達(dá)式時(shí)需要倍加小心。只有以下幾種情況中,你確實(shí)需要全局表達(dá)式即調(diào)用replace方法時(shí),或是需要顯示使用lastIndex時(shí)。這也基本是全局表達(dá)式唯一的應(yīng)用場(chǎng)景了。
循環(huán)匹配一個(gè)常見的事情是,找出字符串中所有模式的出現(xiàn)位置,這種情況下,我們可以在循環(huán)中使用lastIndex和exec訪問匹配的對(duì)象。
let input = "A string with 3 numbers in it... 42 and 88."; let number = /(d+)/g; let match; while (match = number.exec(input)) { console.log("Found", match[0], "at", match.index); } // → Found 3 at 14 // Found 42 at 33 // Found 88 at 40
這里我們利用了賦值表達(dá)式的一個(gè)特性,該表達(dá)式的值就是被賦予的值。因此通過使用match=re.exec(input)作為while語句的條件,我們可以在每次迭代開始時(shí)執(zhí)行匹配,將結(jié)果保存在變量中,當(dāng)無法找到更多匹配的字符串時(shí)停止循環(huán)。
解析INI文件為了總結(jié)一下本章介紹的內(nèi)容,我們來看一下如何調(diào)用正則表達(dá)式來解決問題。假設(shè)我們編寫一個(gè)程序從因特網(wǎng)上獲取我們敵人的信息(這里我們實(shí)際上不會(huì)編寫該程序,僅僅編寫讀取配置文件的那部分代碼,對(duì)不起)。配置文件如下所示。
searchengine=https://duckduckgo.com/?q=$1 spitefulness=9.7 ; comments are preceded by a semicolon... ; each section concerns an individual enemy [larry] fullname=Larry Doe type=kindergarten bully website=http://www.geocities.com/CapeCanaveral/11451 [davaeorn] fullname=Davaeorn type=evil wizard outputdir=/home/marijn/enemies/davaeorn
該配置文件格式的語法規(guī)則如下所示(它是廣泛使用的格式,我們通常稱之為INI文件):
忽略空行和以分號(hào)起始的行。
使用[]包圍的行表示一個(gè)新的節(jié)(section)。
如果行中是一個(gè)標(biāo)識(shí)符(包含字母和數(shù)字),后面跟著一個(gè)=字符,則表示向當(dāng)前節(jié)添加選項(xiàng)。
其他的格式都是無效的。
我們的任務(wù)是將這樣的字符串轉(zhuǎn)換為一個(gè)對(duì)象,該對(duì)象的屬性包含沒有節(jié)的設(shè)置的字符串,和節(jié)的子對(duì)象的字符串,節(jié)的子對(duì)象也包含節(jié)的設(shè)置。
由于我們需要逐行處理這種格式的文件,因此預(yù)處理時(shí)最好將文件分割成一行行文本。我們使用第 6 章中的string.split(" ")來分割文件內(nèi)容。但是一些操作系統(tǒng)并非使用換行符來分隔行,而是使用回車符加換行符(" ")。考慮到這點(diǎn),我們也可以使用正則表達(dá)式作為split方法的參數(shù),我們使用類似于/ ? /的正則表達(dá)式,這樣可以同時(shí)支持" "和" "兩種分隔符。
function parseINI(string) { // Start with an object to hold the top-level fields let currentSection = {name: null, fields: []}; let categories = [currentSection]; string.split(/ ? /).forEach(line => { let match; if (match = line.match(/^(w+)=(.*)$/)) { section[match[1]] = match[2]; section = result[match[1]] = {}; } else if (!/^s*(;.*)?$/.test(line)) { throw new Error("Line "" + line + "" is not valid."); } }); return result; } console.log(parseINI(` name=Vasilis [address] city=Tessaloniki`)); // → {name: "Vasilis", address: {city: "Tessaloniki"}}
代碼遍歷文件的行并構(gòu)建一個(gè)對(duì)象。 頂部的屬性直接存儲(chǔ)在該對(duì)象中,而在節(jié)中找到的屬性存儲(chǔ)在多帶帶的節(jié)對(duì)象中。 section綁定指向當(dāng)前節(jié)的對(duì)象。
有兩種重要的行 - 節(jié)標(biāo)題或?qū)傩孕小?當(dāng)一行是常規(guī)屬性時(shí),它將存儲(chǔ)在當(dāng)前節(jié)中。 當(dāng)它是一個(gè)節(jié)標(biāo)題時(shí),創(chuàng)建一個(gè)新的節(jié)對(duì)象,并設(shè)置section來指向它。
這里需要注意,我們反復(fù)使用^和$確保表達(dá)式匹配整行,而非一行中的一部分。如果不使用這兩個(gè)符號(hào),大多數(shù)情況下程序也可以正常工作,但在處理特定輸入時(shí),程序就會(huì)出現(xiàn)不合理的行為,我們一般很難發(fā)現(xiàn)這個(gè)缺陷的問題所在。
if (match = string.match(...))類似于使用賦值作為while的條件的技巧。你通常不確定你對(duì)match的調(diào)用是否成功,所以你只能在測(cè)試它的if語句中訪問結(jié)果對(duì)象。 為了不打破else if形式的令人愉快的鏈條,我們將匹配結(jié)果賦給一個(gè)綁定,并立即使用該賦值作為if語句的測(cè)試。
國際化字符由于 JavaScript 最初的實(shí)現(xiàn)非常簡(jiǎn)單,而且這種簡(jiǎn)單的處理方式后來也成了標(biāo)準(zhǔn),因此 JavaScript 正則表達(dá)式處理非英語字符時(shí)非常無力。例如,就 JavaScript 的正則表達(dá)式而言,“單詞字符”只是 26 個(gè)拉丁字母(大寫和小寫)和數(shù)字,而且由于某些原因還包括下劃線字符。像α或β這種明顯的單詞字符,則無法匹配w(會(huì)匹配大寫的W,因?yàn)樗鼈儗儆诜菃卧~字符)。
由于奇怪的歷史性意外,s(空白字符)則沒有這種問題,會(huì)匹配所有 Unicode 標(biāo)準(zhǔn)中規(guī)定的空白字符,包括不間斷空格和蒙古文元音分隔符。
另一個(gè)問題是,默認(rèn)情況下,正則表達(dá)式使用代碼單元,而不是實(shí)際的字符,正如第 5 章中所討論的那樣。 這意味著由兩個(gè)代碼單元組成的字符表現(xiàn)很奇怪。
console.log(/ud83cudf4e{3}/.test("ud83cudf4eud83cudf4eud83cudf4e")); // → false console.log(/<.>/.test("")); // → false console.log(/<.>/u.test(" ")); // → true
問題是第一行中的"ud83cudf4e"(emoji 蘋果)被視為兩個(gè)代碼單元,而{3}部分僅適用于第二個(gè)。 與之類似,點(diǎn)匹配單個(gè)代碼單元,而不是組成玫瑰 emoji 符號(hào)的兩個(gè)代碼單元。
你必須在正則表達(dá)式中添加一個(gè)u選項(xiàng)(表示 Unicode),才能正確處理這些字符。 不幸的是,錯(cuò)誤的行為仍然是默認(rèn)行為,因?yàn)楦淖兯赡軙?huì)導(dǎo)致依賴于它的現(xiàn)有代碼出現(xiàn)問題。
盡管這是剛剛標(biāo)準(zhǔn)化的,在撰寫本文時(shí)尚未得到廣泛支持,但可以在正則表達(dá)式中使用p(必須啟用 Unicode 選項(xiàng))以匹配 Unicode 標(biāo)準(zhǔn)分配了給定屬性的所有字符。
console.log(/p{Script=Greek}/u.test("α")); // → true console.log(/p{Script=Arabic}/u.test("α")); // → false console.log(/p{Alphabetic}/u.test("α")); // → true console.log(/p{Alphabetic}/u.test("!")); // → false
Unicode 定義了許多有用的屬性,盡管找到你需要的屬性可能并不總是沒有意義。 你可以使用p{Property=Value}符號(hào)來匹配任何具有該屬性的給定值的字符。 如果屬性名稱保持不變,如p{Name}中那樣,名稱被假定為二元屬性,如Alphabetic,或者類別,如Number。
本章小結(jié)正則表達(dá)式是表示字符串模式的對(duì)象,使用自己的語言來表達(dá)這些模式:
/abc/:字符序列
/[abc]/:字符集中的任何字符
/[^abc]/:不在字符集中的任何字符
/[0-9]/:字符范圍內(nèi)的任何字符
/x+/:出現(xiàn)一次或多次
/x+?/:出現(xiàn)一次或多次,非貪婪模式
/x*/:出現(xiàn)零次或多次
/x??/:出現(xiàn)零次或多次,非貪婪模式
/x{2,4}/:出現(xiàn)兩次到四次
/(abc)/:元組
/a|b|c/:匹配任意一個(gè)模式
/d/:數(shù)字字符
/w/:字母和數(shù)字字符(單詞字符)
/s/:任意空白字符
/./:任意字符(除換行符外)
//:?jiǎn)卧~邊界
/^/:輸入起始位置
/$/:輸入結(jié)束位置
正則表達(dá)式有一個(gè)test方法來測(cè)試給定的字符串是否匹配它。 它還有一個(gè)exec方法,當(dāng)找到匹配項(xiàng)時(shí),返回一個(gè)包含所有匹配組的數(shù)組。 這樣的數(shù)組有一個(gè)index屬性,用于表明匹配開始的位置。
字符串有一個(gè)match方法來對(duì)正確表達(dá)式匹配它們,以及search方法來搜索字符串,只返回匹配的起始位置。 他們的replace方法可以用替換字符串或函數(shù)替換模式匹配。
正則表達(dá)式擁有選項(xiàng),這些選項(xiàng)寫在閉合斜線后面。 i選項(xiàng)使匹配不區(qū)分大小寫。 g選項(xiàng)使表達(dá)式成為全聚德,除此之外,它使replace方法替換所有實(shí)例,而不是第一個(gè)。 y選項(xiàng)使它變?yōu)檎承裕@意味著它在搜索匹配時(shí)不會(huì)向前搜索并跳過部分字符串。 u選項(xiàng)開啟 Unicode 模式,該模式解決了處理占用兩個(gè)代碼單元的字符時(shí)的一些問題。
正則表達(dá)式是難以駕馭的強(qiáng)力工具。它可以簡(jiǎn)化一些任務(wù),但用到一些復(fù)雜問題上時(shí)也會(huì)難以控制管理。想要學(xué)會(huì)使用正則表達(dá)式的重要一點(diǎn)是:不要將其用到無法干凈地表達(dá)為正則表達(dá)式的問題。
習(xí)題在做本章習(xí)題時(shí),讀者不可避免地會(huì)對(duì)一些正則表達(dá)式的莫名其妙的行為感到困惑,因而備受挫折。讀者可以使用類似于 http://debuggex.com/ 這樣的在線學(xué)習(xí)工具,將你想編寫的正則表達(dá)式可視化,并試驗(yàn)其對(duì)不同輸入字符串的響應(yīng)。
RegexpGolfCode Golf 是一種游戲,嘗試盡量用最少的字符來描述特定程序。類似的,Regexp Golf 這種活動(dòng)是編寫盡量短小的正則表達(dá)式,來匹配給定模式(而且只能匹配給定模式)。
針對(duì)以下幾項(xiàng),編寫正則表達(dá)式,測(cè)試給定的子串是否在字符串中出現(xiàn)。正則表達(dá)式匹配的字符串,應(yīng)該只包含以下描述的子串之一。除非明顯提到單詞邊界,否則千萬不要擔(dān)心邊界問題。當(dāng)你的表達(dá)式有效時(shí),請(qǐng)檢查一下能否讓正則表達(dá)式更短小。
car和cat
pop和prop
ferret、ferry和ferrari
以ious結(jié)尾的單詞
句號(hào)、冒號(hào)、分號(hào)之前的空白字符
多于六個(gè)字母的單詞
不包含e(或者E)的單詞
需要幫助時(shí),請(qǐng)參考本章總結(jié)中的表格。使用少量測(cè)試字符串來測(cè)試每個(gè)解決方案。
// Fill in the regular expressions verify(/.../, ["my car", "bad cats"], ["camper", "high art"]); verify(/.../, ["pop culture", "mad props"], ["plop", "prrrop"]]); verify(/.../, ["ferret", "ferry", "ferrari"], ["ferrum", "transfer A"]); verify(/.../, ["how delicious", "spacious room"], ["ruinous", "consciousness"]); verify(/.../, ["bad punctuation ."], ["escape the period"]); verify(/.../, ["hottentottententen"], ["no", "hotten totten tenten"]); verify(/.../, ["red platypus", "wobbling nest"], ["earth bed", "learning ape", "BEET"]); function verify(regexp, yes, no) { // Ignore unfinished exercises if (regexp.source == "...") return; for (let str of yes) if (!regexp.test(str)) { console.log(`Failure to match "${str}"`); } for (let str of no) if (regexp.test(str)) { console.log(`Unexpected match for "${str}"`); } }QuotingStyle
想象一下,你編寫了一個(gè)故事,自始至終都使用單引號(hào)來標(biāo)記對(duì)話?,F(xiàn)在你想要將對(duì)話的引號(hào)替換成雙引號(hào),但不能替換在縮略形式中使用的單引號(hào)。
思考一下可以區(qū)分這兩種引號(hào)用法的模式,并手動(dòng)調(diào)用replace方法進(jìn)行正確替換。
let text = ""I"m the cook," he said, "it"s my job.""; // Change this call. console.log(text.replace(/A/g, "B")); // → "I"m the cook," he said, "it"s my job."NumbersAgain
編寫一個(gè)表達(dá)式,只匹配 JavaScript 風(fēng)格的數(shù)字。支持?jǐn)?shù)字前可選的正號(hào)與負(fù)號(hào)、十進(jìn)制小數(shù)點(diǎn)、指數(shù)計(jì)數(shù)法(5e-3或1E10,指數(shù)前也需要支持可選的符號(hào))。也請(qǐng)注意小數(shù)點(diǎn)前或小數(shù)點(diǎn)后的數(shù)字也是不必要的,但數(shù)字不能只有小數(shù)點(diǎn)。例如.5和5.都是合法的 JavaScript 數(shù)字,但單個(gè)點(diǎn)則不是。
// Fill in this regular expression. let number = /^...$/; // Tests: for (let str of ["1", "-1", "+15", "1.55", ".5", "5.", "1.3e2", "1E-4", "1e+12"]) { if (!number.test(str)) { console.log(`Failed to match "${str}"`); } } for (let str of ["1a", "+-1", "1.2.3", "1+1", "1e4.5", ".5.", "1f5", "."]) { if (number.test(str)) { console.log(`Incorrectly accepted "${str}"`); } }
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/105020.html
摘要:來源編程精解中文第三版翻譯項(xiàng)目原文譯者飛龍協(xié)議自豪地采用谷歌翻譯部分參考了編程精解第版確定編程語言中的表達(dá)式含義的求值器只是另一個(gè)程序。若文本不是一個(gè)合法程序,解析器應(yīng)該指出錯(cuò)誤。 來源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:Project: A Programming Language 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 自豪地采用...
摘要:來源編程精解中文第三版翻譯項(xiàng)目原文譯者飛龍協(xié)議自豪地采用谷歌翻譯部分參考了編程精解第版,這是一本關(guān)于指導(dǎo)電腦的書。在可控的范圍內(nèi)編寫程序是編程過程中首要解決的問題。我們可以用中文來描述這些指令將數(shù)字存儲(chǔ)在內(nèi)存地址中的位置。 來源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:Introduction 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 自豪地...
摘要:為了運(yùn)行包裹的程序,可以將這些值應(yīng)用于它們。在瀏覽器中,輸出出現(xiàn)在控制臺(tái)中。在英文版頁面上運(yùn)行示例或自己的代碼時(shí),會(huì)在示例之后顯示輸出,而不是在瀏覽器的控制臺(tái)中顯示。這被稱為條件執(zhí)行。 來源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:Program Structure 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 自豪地采用谷歌翻譯 部分參考了《J...
摘要:來源編程精解中文第三版翻譯項(xiàng)目原文譯者飛龍協(xié)議自豪地采用谷歌翻譯部分參考了編程精解第版在機(jī)器的表面之下,程序在運(yùn)轉(zhuǎn)。本章將會(huì)介紹程序當(dāng)中的基本元素,包括簡(jiǎn)單的值類型以及值運(yùn)算符。示例中的乘法運(yùn)算符優(yōu)先級(jí)高于加法。 來源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:Values, Types, and Operators 譯者:飛龍 協(xié)議:CC BY-NC...
摘要:來源編程精解中文第三版翻譯項(xiàng)目原文譯者飛龍協(xié)議自豪地采用谷歌翻譯部分參考了編程精解第版技能分享會(huì)是一個(gè)活動(dòng),其中興趣相同的人聚在一起,針對(duì)他們所知的事情進(jìn)行小型非正式的展示。所有接口均以路徑為中心。 來源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:Project: Skill-Sharing Website 譯者:飛龍 協(xié)議:CC BY-NC-SA 4...
閱讀 1886·2021-11-15 11:39
閱讀 1091·2020-12-03 17:06
閱讀 746·2019-12-27 11:42
閱讀 3278·2019-08-30 13:59
閱讀 1474·2019-08-26 13:22
閱讀 3291·2019-08-26 12:15
閱讀 2480·2019-08-26 10:22
閱讀 1570·2019-08-23 18:40