---
title: Regular Expressions
tags: sirla, class, JavaScript
description:
1. title 請改為 [授課名稱]課程
2. tag 請刪去template,加上活動內容類型或名稱
3. 下方會議記錄請使用會議記錄範本
4. 加上"{%hackmd BkVfcTxlQ %}"意為套用黑色模板
---
{%hackmd BkVfcTxlQ %}
# **_Regular Expressions_**
> 負責人:黃丰嘉
> 授課時間:2019-11-17 (日)
>
---
# **參考資源**
> 1. [(MDN) 正規表達式](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Guide/Regular_Expressions)
> 2. [(JS) 正則表達式(Regular Expression, regex)](https://pjchender.github.io/2017/09/26/js-%E6%AD%A3%E5%89%87%E8%A1%A8%E9%81%94%E5%BC%8F-regular-expression-regex/)
> 3. [(w3schools) JavaScript RegExp Reference](https://www.w3schools.com/jsref/jsref_obj_regexp.asp)
> 4. [JavaScript (7) – 字串處理與正規表達式 (作者:陳鍾誠)](http://programmermagazine.github.io/201307/htm/article2.html)
> 5. [greedy & nongreedy](https://amobiz.github.io/2014/08/03/regular-expression-javascript-study-notes-1-theory-1/)
> 6. [Javascript Regular Expressions , 表示法](https://www.puritys.me/docs-blog/article-30-Javascript-Regular-Expressions-,-%E8%A1%A8%E7%A4%BA%E6%B3%95.html)
> 7. [量符(Quantifier) greedy vs. nongreedy](http://notepad.yehyeh.net/Content/Program/RegularExpression/8.php)
---
# **課程大綱**
[TOC]
---
## (P) **什麼是 Regular Expressions?**
* Regular Expressions (正規表達式)
* 一個描述字串資料(string data)之模式(patterns)的方式。
## (P) **創建一個正規表達式 (Creating a regular expression)**
* 正規表達式的型別(type)為`物件(object)`。
* 創建方式
1. 使用 `RegExp()`
```javascript=
let re1 = new RegExp("abc");
```
2. 使用 `正斜杠 /` 包覆所描述的模式
```javascript=
let re2 = /abc/;
```
* Both of those regular expression objects represent the same pattern: an `a` character followed by a `b` followed by a `c`.
* 若`?`和`+`的前面添加反斜線`\`,代表將之當成一般字元使用,意即表示字元本身(沒有特殊用途)。
```javascript=
let eighteenPlus = /eighteen\+/;
// + 原本是特殊字元,但這裡要當成非特殊字元
```
## (P) **比對測試 (Testing for matches)**
* `test()`方法
* 給定一個字串,以及欲比對的模式。它將回傳布林值,並說明該字串是否符合欲比對的模式。
```javascript=
console.log(/abc/.test("abcde"));
// → true
console.log(/abc/.test("abxde"));
// → false
```
> 若正規表達式中只包含`非特殊字元`,表示該模式為比對字元本身。
## (P) **字元的集合 (Sets of characters)**
* `indexOf`方法
* 回傳給定元素於陣列中第一個被找到之索引,若不存在於陣列中則回傳`-1`。
```javascript=
console.log(/[0123456789]/.test("in 1992"));
// → true
console.log(/[0-9]/.test("in 1992"));
// → true
>>> Both match all strings that contain a digit.
```
> 若將一組字符放在`[]`內,表示匹配`[]`內的所有字符。
> 意即只要符合`[]`內所列出的字符,就算匹配成功。
> `[]`內所使用的`hyphen (-)`,表示字符範圍,其順序由Unicode決定。
> Ex:字符`0`到`9`,以Unicode的順序彼此相鄰(代號48到57),`[0-9]`代表匹配所有的數字字符。
* 許多常見的字符組都有自己的內建快捷表達方式(built-in shortcuts)。如:`\d` 與 `[0-9]` 含義相同(匹配所有數字)。
|特殊字元|解說|
|---|---|
|\d|任何數字|
|\w|任何字母和數字("文字字符")|
|\s|任何空白字符(space, tab, newline...)|
|\D|非數字的字符|
|\W|非字母和數字字符|
|\S|非空白字符|
|.|除換行字符以外的任何字符|
```javascript=
let dateTime = /\d\d-\d\d-\d\d\d\d \d\d:\d\d/;
console.log(dateTime.test("01-30-2003 15:20"));
// → true
console.log(dateTime.test("30-jan-2003 15:20"));
// → false
* 稍後,我們會對此`匹配日期和時間 的表達式`進行一些改進!
```
* `[\d.]` 表示匹配任何數字或句點字符。若`.`或`+`存在於`方括號 [ ]`內,則會失去其特殊的意義。
* `[^aA]` 表示匹配所有不是 a 或 A 的字。若`^`存在於`方括號 [ ]`內,則表示`匹配非方括號內的字符`。
```javascript=
let notBinary = /[^01]/;
console.log(notBinary.test("1100100010100110"));
// → false
console.log(notBinary.test("1100100010200110"));
// → true 1100100010`2`00110
```
## (P) **重複部分的模式 (Repeating parts of a pattern)**
> 如何匹配一個或多個數字的序列?
* 當把`+`放在某個字符後面,代表該字符重複出現`至少1次`。
* `/\d+/` 匹配任何數字,且其至少出現1次。
* 當把`*`放在某個字符後面,代表該字符重複出現`0次以上`。
* 當把`?`放在某個字符後面,代表該字符可能`出現0次或1次`。
```javascript=
console.log(/'\d+'/.test("'123'")); // 任何數字 至少出現1次,即符合
// → true
console.log(/'\d+'/.test("''"));
// → false
console.log(/'\d*'/.test("'123'")); // 任何數字 出現0次以上,即符合
// → true
console.log(/'\d*'/.test("''"));
// → true
let neighbor = /neighbou?r/; // u 出現0次或1次,即符合
console.log(neighbor.test("neighbour"));
// → true
console.log(neighbor.test("neighbor"));
// → true
```
* 為了精確匹配該字符的出現次數,使用`{ }`放在某個字符後面。
* `{4}` 代表該字符需出現4次,即符合。
* `{2,4}` 代表該字符出現至少2次、最多4次,即符合。
* `{5,}` 代表該字符出現至少5次,即符合。(開放式範圍)
> 接下來,改進`匹配日期和時間 的表達式`!
> 允許`日`、`月`、`小時`為一位數或兩位數。
```javascript=
let dateTime = /\d{1,2}-\d{1,2}-\d{4} \d{1,2}:\d{2}/;
console.log(dateTime.test("1-30-2003 8:45"));
// → true
```
## (P) **群組子表達式 (Grouping subexpressions)**
* ==`括號 ()` 用於驗證字符串==
* 若`一次要對多個字符`使用`+`或`*`之類的特殊符號,則必須使用`括號 ()`。
* 若`沒有使用括號 ()`包起來,則代表其是`針對前面的一個字符`。
```javascript=
let cartoonCrying = /boo+(hok+)+/i;
console.log(cartoonCrying.test("BoohoooohooHooo"));
// → false
console.log(cartoonCrying.test("Boohokoohokhook"));
// → true
>>> 第一個和第二個`+`分別針對`boo`中的第二個`o`,以及`hok`中的`k`。
>>> 第三個`+`是針對整個群組`(hok+)`出現至少一次。
>>> 表達式末端的`i`是指`不區分大小寫`。因此,允許與輸入字串的`B`匹配。
```
## (P) **匹配與群組(Matches and groups)**
* `test()`方法:最簡單的匹配方式,只會回傳True或False。
* `exec()`(execute)方法:當沒有匹配時,回傳null;否則傳回object匹配資訊。
* `index`屬性:回傳成功匹配的字符從何處開始。
* `match()`方法:處理字串的方法,與`exec()`方法類似。
```javascript=
let match = /\d+/.exec("one two 100");
console.log(match);
// → ["100"]
console.log(match.index);
// → 8
console.log("one two 100".match(/\d+/));
// → ["100"]
```
* 當正規表達式內使用`( )`的群組子表達式時,所匹配的字串將會以`陣列`呈現。`( )`代表要將匹配的內容,另外捕捉、儲存下來。
```javascript=
let quotedText = /'([^']*)'/;
// '([^']*)' -> 字串外面使用''包裹。群組裡面沒有`'`出現0次以上。
// 'hello' or hello
console.log(quotedText.exec("she said 'hello'"));
// → ["'hello'", "hello"]
const regexp = /(\w+)\.jpg/;
console.log(regexp.exec('File name: cat.jpg'));
// ["cat.jpg", "cat",
// index: 11,
// input: "File name: cat.jpg", groups: undefined]
```
* `?`代表前個字符出現0次或1次。
* `括號 ()`除了驗證字符串,還可用於==提取字串的一部分==。
```javascript=
console.log(/bad(ly)?/.exec("bad")); // (ly) 0次 , 1次
// → ["bad", undefined]
/* 先比對 bad(ly)?
* 再比對 (ly)?
*/
console.log(/bad(ly)?/.exec("badl"));
// ["bad", undefined, index: 0, input: "badl", groups: undefined]
console.log(/bad(ly)?/.exec("badlyr"));
// ["badly", "ly", index: 0, input: "badlyr", groups: undefined]
```
* 當一個群組`( )`被成功匹配多次時,僅回傳最後一個匹配的項目,即`"3"`。
```javascript=
console.log(/(\d)+/.exec("123")); // (\d)+
// → ["123", "3"]
```
## (P) **日期類 (The Date class)**
* JavaScript擁有專用於表示日期(或時間點)的class。稱為`Date`。
* `Date`物件提供`getFullYear()`, `getMonth()`, `getDate()`, `getHours()`, `getMinutes()`, and `getSeconds()`方法。
* 取得現在的日期和時間
```javascript=
console.log(new Date());
// → Tue Nov 12 2019 10:28:37 GMT+0800 (台北標準時間)
```
* 創建特定時間的物件(object)
```javascript=
console.log(new Date(2019, 11, 12));
// → Thu Dec 12 2019 00:00:00 GMT+0800 (台北標準時間)
console.log(new Date(2009, 11, 12, 12, 59, 59, 999));
// → Sat Dec 12 2009 12:59:59 GMT+0800 (台北標準時間)
```
> 注意!
> JavaScript的月份從0開始(所以12月是11);日期從1開始。
> 最後的四個參數為(小時,分鐘,秒和毫秒),預設皆為0。
* JavaScript的時間戳記(Timestamps)遵循Unix time,紀錄1970年以來的毫秒數並儲存在UTC時區中。
* 使用`getTime()`方法,可回傳從1970年至某個時間點的毫秒數。
* 若給予`Date()`方法一個參數,則JavaScript將該參數視為毫秒數。
```javascript=
console.log(new Date(2019, 10, 12).getTime());
// → 1573488000000
console.log(Date.now());
// → 1573527038896
console.log(new Date(1573527038896));
// → Tue Nov 12 2019 10:50:38 GMT+0800 (台北標準時間)
```
* 進行日期的處理
```javascript=
function processDate(string) {
let [_, month, day, year] = /(\d{1,2})-(\d{1,2})-(\d{4})/.exec(string);
return new Date(year, month - 1, day);
}
console.log(processDate("testing: 1-30-2003 in javascript."));
// → Thu Jan 30 2003 00:00:00 GMT+0800 (台北標準時間)
```
> `底線 _ (underscore)` 忽略,用於跳過由exec返回的陣列中匹配的物件。
>
> 備註:
> (匹配模式不夠嚴謹,於下一節 Word and string boundaries 說明)
> ```
> console.log(getDate("100-1-30000")); // 匹配 "00-1-3000"
> >>> Sun Dec 01 2999 00:00:00 GMT+0800 (台北標準時間)
> ```
## (P) **單詞和字串的邊界 (Word and string boundaries)**
* 使用`^`匹配字串的開頭;使用`$`匹配字串的結尾。
* `/^\d+$/` 匹配開頭包含至少一個數字的字串、結尾包含至少一個數字的字串。`/^!/` 匹配開頭為`!`的字串。
* 錯誤寫法:`/x^/`
* 使用`\b`配對 word boundary,word boundary 是指一個字元的前後沒有其他任何字元。
```javascript=
let matchedResult = 'This is an apple.'.match(/\bis\b/);
// is 這個單字才會被選到
// Th`is` 的 is 不會被選到,因為前有其他字元。
// [ 'is', index: 5, input: 'This is an apple.' ]
```
```javascript=
console.log(/cat/.test("concatenate"));
// → true
console.log(/\bcat\b/.test("concatenate"));
// → false
console.log(/\bcat/.test("catenate"));
// → true
```
> 注意:
> 邊界標記與實際字符不匹配。
> 它只是強制正則表達式僅在`條件出現在模式中的位置時`才匹配。
>
## (P) **選擇模式 (Choice patterns)**
* 使用`|` 表示在左側模式和右側模式之間進行選擇。(一次匹配多種可選擇的類型)
```javascript=
let animalCount = /\b\d+ (pig|cow|chicken)s?\b/;
console.log(animalCount.test("15 pigs"));
// → true
console.log(animalCount.test("15 pigchickens"));
// → false
```
## (P) **匹配機制 (The mechanics of matching)**

```javascript=
let animalCount = /\b\d+ (pig|cow|chicken)s?\b/;
console.log(animalCount.test("the 3 pigs"));
// → true
```
* 概念上,當使用`exec()`或`test()`時,正規表達式引擎會嘗試在字串中查找匹配項:
* 首先,從字串開頭匹配,然後從第二個字符匹配,依此類推,直到找到匹配項或到達字符串的末尾。
* 如果可以找到從圖的左側到右側的路徑,則表達式匹配。
## (P) **回溯 (Backtracking)**

```javascript=
let reg = /\b([01]+b|[\da-f]+h|\d+)\b/;
console.log(reg.test("0 856 fr2 eah 1b"));
// → true
console.log(reg.exec("0m 856 fr2 eah 1b")); // 856 eah 1b 皆符合
// → ["856", "856", index: 3, input: "0m 856 fr2 eah 1b", groups: undefined]
```
> 若有多個匹配,則只回傳第一個成功匹配的。
>
```javascript=
let reg = /^.*x/;
console.log(reg.test("."));
// → false
console.log(reg.test(""));
// → false
console.log(reg.test("x"));
// → true
console.log(reg.test(".x"));
// → true
console.log(reg.test(".xa"));
// → true
console.log(reg.test("abcxe"));
// → true
```
```javascript=
let reg = /([01]+)+b/;
console.log(reg.test("01 123"));
// → false
console.log(reg.test("0 reg"));
// → false
console.log(reg.test("01b 123"));
// → true
console.log(reg.test("0b reg"));
// → true
```
## (P) **取代方法 (The replace method)**
* `replace()`方法:用於將一個字串替換為另一個字串。
* (replace()的第一個參數)使用`g`於正規表達式中,可取代`所有`匹配項。
```javascript=
console.log("papa".replace("p", "m"));
// → mapa
console.log("Borobudur".replace(/[ou]/, "j"));
// → Bjrobudur
console.log("Borobudur".replace(/[ou]/g, "j"));
// → Bjrjbjdjr
```
* (replace()的第二個參數)使用`$`,可針對`括號( )`內的內容。
```javascript=
console.log(
"Liskov, Barbara\nMcCarthy, John\nWadler, Philip"
.replace(/(\w+), (\w+)/g, "$2 $1"));
// → Barbara Liskov
// John McCarthy
// Philip Wadler
console.log(
"Liskov, Barbara\nMcCarthy, John\nWadler, Philip"
.replace(/(\w+), (\w+)/g, "$&"));
// → Liskov, Barbara
// McCarthy, John
// Wadler, Philip
let s = "the cia and fbi";
console.log(s.replace(/\b(fbi|cia)\b/g, str => str.toUpperCase()));
// → the CIA and FBI
```
> `$2`指第二個括號`(\w+)`的內容;`$1`指第一個括號`(\w+)`的內容。
> `$&`指的是所有括號`( )`的內容。
* [String.prototype.replace()](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/String/replace)
```javascript=
let stock = "1 lemon, 2 cabbages, and 101 eggs";
console.log(stock.replace(/(\d+) (\w+)/g,"$&")); //(amount) (unit)
// → 1 lemon, 2 cabbages, and 101 eggs
function minusOne(match, amount, unit) { // $&, amount=1, unit=lemon
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
```
## (P) **貪婪式比對 (Greed)**
1. Greedy
* 使用`*`、`+`、`?`或`{}`等重複運算符,將會使這些greedy(儘可能匹配最多的字元)。
```javascript=
var html = `
<table>
<td>aaa</td>
</table>
<table>
<td>bbb</td>
</table>
`;
var reg = /<table[.\n\s\S]*<\/table>/g;
var r = html.match(reg);
console.log(r);
// → [ '<table>\n <td>aaa</td>\n</table>\n<table>\n <td>bbb</td>\n</table>' ]
```
```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
```
>`[^]*` 會匹配儘可能多的字元。
> `[^]` 匹配只要是非空集合的任何字元都符合(any character that is not in the empty set of characters)。
> `.` 匹配任意單一字元。
2. Non-Greedy
* 使用`*?`、`+?`、`??`或`{}?`,將會使nongreedy(儘可能匹配最少的字元)。
* 例如:在`123abc`中應用 `/\d+/` 可匹配「123」,但使用 `/\d+?/` 在相同字串上只能匹配「1」。
```javascript=
var html = `
<table>
<td>aaa</td>
</table>
<table>
<td>bbb</td>
</table>
`;
var reg = /<table[.\n\s\S]*?<\/table>/g;
var r = html.match(reg);
console.log(r);
// → [ '<table>\n <td>aaa</td>\n</table>',
// '<table>\n <td>bbb</td>\n</table>' ]
```
```javascript=
function stripComments(code) {
return code.replace(/\/\*[^]*?\*\//g, "");
}
console.log(stripComments("1 /* a */+/* b */ 1"));
// → 1 + 1
function stripComments(code) {
return code.replace(/\/\*[^]*\*\//g, "");
}
console.log(stripComments("1 /* a */+/* b */ 1"));
// → 1 1
```
## (P) **動態創建RegExp物件 (Dynamically creating RegExp objects)**
* 建構一個字串,並在其上使用`RegExp()`構造函數
> [JavaScript: 如何轉換 Regular Expression 變成 RegExp() 的參數](http://magicjackting.pixnet.net/blog/post/208907845)
```javascript=
let name = "harry"; // 把 harry 當成模式
let text = "Harry is a suspicious character.";
let regexp = new RegExp("\\b(" + name + ")\\b", "gi"); // 符合模式
console.log(text.replace(regexp, "_$1_"));
// → _Harry_ is a suspicious character.
name1 = "harary"; // 把 harary 當成模式
let regexp1 = new RegExp("\\b(" + name1 + ")\\b", "gi"); // 不符合模式
console.log(text.replace(regexp1, "_$1_"));
// → Harry is a suspicious character.
```
```javascript=
let name = "harry"; // 把 harry 當成模式
let text1 = "Harry is a suspicious character.";
let regexp = new RegExp(`\\b(${name})\\b`, "gi");
// 符合模式
text1.replace(regexp, "_$1_");
// → _Harry_ is a suspicious character.
```
> `\\b` 使用2個反斜線,因為是用一般字串來表示,而非使用正規表達式來表示。
> `g` 針對`所有`匹配項。`i` 不區分大小寫。
* 假設有人很中二的寫他的名字`dea+hl[]rd`
* `/[`:使用`/`在特殊符號(如:`[`)之前,會將之當成`非特殊字元`處理。
```javascript=
let name = "dea+hl[]rd";
let text = "This dea+hl[]rd guy is super annoying.";
let escaped = name.replace(/[\\[.+*?(){|^$]/g, "\\$&");
console.log(escaped);
// [\\[.+*?(){
// ^$]
// → dea\+hl\[]rd
let regexp = new RegExp("\\b" + escaped + "\\b", "gi");
console.log(text.replace(regexp, "_$&_"));
// → This _dea+hl[]rd_ guy is super annoying.
```
## (P) **搜尋方法 (The search method)**
* [`indexOf()`方法](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf)
* 不能用於正規表達式。~~`str.indexOf(/[abc]/ , i);`~~
* 優點:能從特定位置開始搜尋。
* [`search()`方法](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/search)
* 可用於正規表達式。
* 回傳給定元素於陣列中`第一個`被找到之索引,若不存在於陣列中則回傳`-1`。
* 缺點:不能從特定位置才開始搜尋。
```javascript=
console.log(" word".search(/\S/)); //012
// → 2
console.log(" ".search(/\S/));
// → -1
```
## (P) **lastIndex屬性 (The lastIndex property)**
* [`exec()`方法](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/String/lastIndexOf)
* 缺點:不能從特定位置才開始搜尋。
* 正規表達式的物件,擁有以下屬性:
* source:創建表達式的字串。
* lastIndex:用於控制,該從何處開始匹配。
```javascript=
let str = "a0bc1";
// Indexes: 01234
let rexWithout = /\d/;
let rexWithout_g = /\d/g;
let rexWith_y = /\d/y;
// Without:
rexWithout.lastIndex = 2;
console.log(rexWithout.exec(str));
// ["0"], found at index 1, because without the g or y flag,
// the search is always from index 0
// → [ '0', index: 1, input: 'a0bc1', groups: undefined ]
// Without:
rexWithout_g.lastIndex = 2;
console.log(rexWithout_g.exec(str));
// → [ '1', index: 4, input: 'a0bc1', groups: undefined ]
// With, unsuccessful:
rexWith_y.lastIndex = 2; // Says to *only* match at index 2.
console.log(rexWith_y.exec(str)); // => null, there's no match at index 2
// → null
// With, successful:
rexWith_y.lastIndex = 1; // Says to *only* match at index 1.
console.log(rexWith_y.exec(str)); // => ["0"], there was a match at index 1.
// → [ '0', index: 1, input: 'a0bc1', groups: undefined ]
// With, successful again:
rexWith_y.lastIndex = 4; // Says to *only* match at index 4.
console.log(rexWith_y.exec(str)); // => ["1"], there was a match at index 4.
// → [ '1', index: 4, input: 'a0bc1', groups: undefined ]
```
> 當釘住`y`啟用,代表只會匹配給定的索引(`rexWith.lastIndex = 2;`),不再繼續匹配後面的索引。
>
> `g`則會先全部搜尋一次,再從符合匹配的地方開始。
```javascript=
let pattern = /y/g; //比對 全部的y
pattern.lastIndex = 3; //從index=3,開始比對
let match = pattern.exec("xyzzy");
console.log(match.index);
// → 4
console.log(pattern.lastIndex); //在第8個位置
// → 5
let global = /abc/g; // `g` global
console.log(global.exec("xyz abc"));
// → [ 'abc', index: 4, input: 'xyz abc', groups: undefined ]
let sticky = /abc/y; // `y` sticky options
console.log(sticky.exec("xyz abc"));
// → null
```
* 當對多個exec()調用共享的正規表達式之值時,對lastIndex屬性的自動更新可能會引起問題。(可能會從上次調用留下的索引處開始。)
```javascript=
let digit = /\d/g;
console.log(digit.exec("here it is: 1"));
// → ["1"]
console.log(digit.exec("and now: 1"));
// → null
```
* `match()`方法 + `g` global option
* `match()`方法會在字串中找到該模式的`所有匹配項`,並回傳包含匹配字串的陣列。
```javascript=
console.log("Banana".match(/an/g));
// → ["an", "an"]
```
### 循環匹配 (Looping over matches)
```javascript=
let input = "A string with 3 numbers in it... 42 and 88.";
let number = /\b\d+\b/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
while (match = number.exec(input)) {
console.log("Found", match, "at", match.index);
}
// → Found ["3", index: 14, input: "A string with 3 numbers in it... 42 and 88.", groups: undefined] at 14
// → Found ["42", index: 33, input: "A string with 3 numbers in it... 42 and 88.", groups: undefined] at 33
// → Found ["88", index: 40, input: "A string with 3 numbers in it... 42 and 88.", groups: undefined] at 40
```
## (P) **解析一個INI文件 (Parsing an INI file)**
* `.ini` 文件規則如下:
1. 空行、以`;`開頭的行,將被忽略。
2. 使用`[`和`]`的行,代表開始新的部分。
3. 包含字母數字標識符,且後面跟著`=`的行,將添加到當前的部分。
4. 其他都無效。
```
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
```
* `\r`:返回符號;`\n`:換行符號。
* `/\r?\n/` 允許行與行之間為`\n`或`\r\n`的拆分方式。
* `^`:匹配開頭;`$`:匹配結尾。確保表達式與整行匹配。
```javascript=
function parseINI(string) {
// Start with an object to hold the top-level fields
let result = {};
let section = result;
string.split(/\r?\n/).forEach(line => {
let match;
if (match = line.match(/^(\w+)=(.*)$/)) {
//是屬性
section[match[1]] = match[2];
} else if (match = line.match(/^\[(.*)\]$/)) {
//是節標題
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"}}
```
## (P) **國際字符 (International characters)**
* JavaScript對於非英文字母的處理,顯得愚蠢。
* JavaScript的`word character`只包含26個大小寫的英文字母、十進位數字、底線。
* 而像是`é`、`β`等字符,將不被匹配`\w`(文字字符),但可匹配`\W`(非文字字符)。
* `\s`(空白),可匹配Unicode標準認為的所有字符,包括`不間斷空格`和`蒙古元音分隔符`之類的東西。
* JavaScript預設:處理正規表達式的單個程式碼字元,而不是處理實際的單個字符。
```javascript=
console.log(/🍎{3}/.test("🍎🍎🍎"));
// 因為🍎被視為2個程式碼字元所組成
// → false
console.log(/<.>/.test("<🌹>"));
// → false
console.log(/<.>/u.test("<🌹>"));
// 必須在正則表達式中添加`u`,以Unicode使其正確處理此類字符。
// → true
```
* `\u` 意味以Unicode處理此類字符。`\p`是 Unicode 屬性轉義,它賦予了我們`根據 Unicode 字符的屬性數據構造表達式`的能力。
* 使用`\p{Property=Value}` 匹配具有該屬性給定值的任何字符。
* 不使用`\p{Property=Value}`方式。若使用`\p{Name}`,`name`將被假定為`Alphabetic`或`Number`的二進為屬性。
```javascript=
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
```