mirror of
https://github.com/apachecn/eloquent-js-3e-zh.git
synced 2025-05-23 20:02:20 +00:00
9.
This commit is contained in:
parent
53c34b58de
commit
d48e8603c5
163
9.md
163
9.md
@ -218,7 +218,7 @@ JavaScript中约定是:使用从0开始的数字表示月份(因此使用11
|
|||||||
|
|
||||||
构造函数的后四个参数(小时、分钟、秒、毫秒)是可选的,如果用户没有指定这些参数,则参数的值默认为0。
|
构造函数的后四个参数(小时、分钟、秒、毫秒)是可选的,如果用户没有指定这些参数,则参数的值默认为0。
|
||||||
|
|
||||||
时间戳存储为 UTC 时区中 1970 年以来的毫秒数。 这遵循一个由“Unix 时间”设定的约定,该约定是在那个时候发明的。 您可以对 1970 年以前的时间使用负数。 日期对象上的`getTime`方法返回这个数字。 你可以想象它会很大。
|
时间戳存储为 UTC 时区中 1970 年以来的毫秒数。 这遵循一个由“Unix 时间”设定的约定,该约定是在那个时候发明的。 你可以对 1970 年以前的时间使用负数。 日期对象上的`getTime`方法返回这个数字。 你可以想象它会很大。
|
||||||
|
|
||||||
```
|
```
|
||||||
console.log(new Date(2013, 11, 19).getTime());
|
console.log(new Date(2013, 11, 19).getTime());
|
||||||
@ -504,7 +504,7 @@ console.log(sticky.exec("xyz abc"));
|
|||||||
// → null
|
// → null
|
||||||
```
|
```
|
||||||
|
|
||||||
对多个`exec`调用使用共享的正则表达式值时,这些`lastIndex`属性的自动更新可能会导致问题。 您的正则表达式可能意外地在之前的调用留下的索引处开始。
|
对多个`exec`调用使用共享的正则表达式值时,这些`lastIndex`属性的自动更新可能会导致问题。 你的正则表达式可能意外地在之前的调用留下的索引处开始。
|
||||||
|
|
||||||
```
|
```
|
||||||
let digit = /\d/g;
|
let digit = /\d/g;
|
||||||
@ -572,7 +572,7 @@ outputdir=/home/marijn/enemies/davaeorn
|
|||||||
|
|
||||||
+ 其他的格式都是无效的。
|
+ 其他的格式都是无效的。
|
||||||
|
|
||||||
我们的任务是将这样的字符串转换成对象的数组,数组中每个元素包含一个name属性和一个选项数组。我们需要使用一个对象表示节,也需要使用一个对象表示全局选项。
|
我们的任务是将这样的字符串转换为一个对象,该对象的属性包含没有段的设置的字符串,和段的子对象的字符串,段的子对象也包含段的设置。
|
||||||
|
|
||||||
由于我们需要逐行处理这种格式的文件,因此预处理时最好将文件分割成一行行文本。我们使用第6章中的string.split(“\n”)来分割文件内容。但是一些操作系统并非使用换行符来分隔行,而是使用回车符加换行符(“\r\n”)。考虑到这点,我们也可以使用正则表达式作为split方法的参数,我们使用类似于/\r?\n/的正则表达式,这样可以同时支持“\n”和“\r\n”两种分隔符。
|
由于我们需要逐行处理这种格式的文件,因此预处理时最好将文件分割成一行行文本。我们使用第6章中的string.split(“\n”)来分割文件内容。但是一些操作系统并非使用换行符来分隔行,而是使用回车符加换行符(“\r\n”)。考虑到这点,我们也可以使用正则表达式作为split方法的参数,我们使用类似于/\r?\n/的正则表达式,这样可以同时支持“\n”和“\r\n”两种分隔符。
|
||||||
|
|
||||||
@ -582,68 +582,91 @@ function parseINI(string) {
|
|||||||
let currentSection = {name: null, fields: []};
|
let currentSection = {name: null, fields: []};
|
||||||
let categories = [currentSection];
|
let categories = [currentSection];
|
||||||
|
|
||||||
string.split(/\r?\n/).forEach(function(line) {
|
string.split(/\r?\n/).forEach(line => {
|
||||||
let match;
|
let match;
|
||||||
if (/^\s*(;.*)?$/.test(line)) {
|
if (match = line.match(/^(\w+)=(.*)$/)) {
|
||||||
return;
|
section[match[1]] = match[2];
|
||||||
} else if (match = line.match(/^\[(.*)\]$/)) {
|
section = result[match[1]] = {};
|
||||||
currentSection = {name: match[1], fields: []};
|
} else if (!/^\s*(;.*)?$/.test(line)) {
|
||||||
categories.push(currentSection);
|
throw new Error("Line '" + line + "' is not valid.");
|
||||||
} else if (match = line.match(/^(\w+)=(.*)$/)) {
|
|
||||||
currentSection.fields.push({name: match[1],
|
|
||||||
value: match[2]});
|
|
||||||
} else {
|
|
||||||
throw new Error("Line '" + line + "' is invalid.");
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return categories;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(parseINI(`
|
||||||
|
name=Vasilis
|
||||||
|
[address]
|
||||||
|
city=Tessaloniki`));
|
||||||
|
// → {name: "Vasilis", address: {city: "Tessaloniki"}}
|
||||||
```
|
```
|
||||||
|
|
||||||
上面这段代码会遍历文件中的每一行,并更新当前节对应的对象。首先,使用表达式/^\s*(;.*)?$/检查是否应该忽略该行。你能看出这段表达式是如何工作的吗?括号中的表达式负责匹配注释,而?则匹配全为空白字符的行。
|
代码遍历文件的行并构建一个对象。 顶部的属性直接存储在该对象中,而在段中找到的属性存储在单独的段对象中。 `section`绑定指向当前段的对象。
|
||||||
|
|
||||||
如果这行文本不是注释,代码会检查这一行是否是一个新节的起始位置。如果是的话,则创建一个新的节对象,并将后续选项都添加到这个对象中。
|
有两种重要的行 - 段标题或属性行。 当一行是常规属性时,它将存储在当前段中。 当它是一个段标题时,创建一个新的段对象,并设置`section`来指向它。
|
||||||
|
|
||||||
这行文本还可能是一个普通选项,这种情况下代码会将选项添加到当前节对象中。
|
|
||||||
|
|
||||||
如果某行文本无法匹配任何模式,那么函数会抛出一个错误。
|
|
||||||
|
|
||||||
这里需要注意,我们反复使用^和$确保表达式匹配整行,而非一行中的一部分。如果不使用这两个符号,大多数情况下程序也可以正常工作,但在处理特定输入时,程序就会出现不合理的行为,我们一般很难发现这个缺陷的问题所在。
|
这里需要注意,我们反复使用^和$确保表达式匹配整行,而非一行中的一部分。如果不使用这两个符号,大多数情况下程序也可以正常工作,但在处理特定输入时,程序就会出现不合理的行为,我们一般很难发现这个缺陷的问题所在。
|
||||||
|
|
||||||
类似于在while循环中使用赋值表达式,if(match=string.match(...))这种形式的表达式也是一种技巧。一般我们无法确定match是否执行成功,因此一般只需要在确实存在匹配结果时,我们才在if语句中访问match的匹配结果。为了不打破if这种优雅的链形式,我们需要将匹配结果赋值给一个变量,并使用赋值表达式的值来作为if中的判断条件。
|
`if (match = string.match(...))`类似于使用赋值作为`while`的条件的技巧。你通常不确定你对`match`的调用是否成功,所以你只能在测试它的`if`语句中访问结果对象。 为了不打破`else if`形式的令人愉快的链条,我们将匹配结果赋给一个绑定,并立即使用该赋值作为`if`语句的测试。
|
||||||
|
|
||||||
### 9.18 国际化字符
|
### 国际化字符
|
||||||
|
|
||||||
由于JavaScript最初的实现非常简单,而且这种简单的处理方式后来也成了标准,因此JavaScript正则表达式处理非英语字符时非常无力。例如,就JavaScript的正则表达式而言,“单词字符”只是26个拉丁字母(大写和小写),而且由于某些原因还包括下划线字符。
|
由于JavaScript最初的实现非常简单,而且这种简单的处理方式后来也成了标准,因此JavaScript正则表达式处理非英语字符时非常无力。例如,就JavaScript的正则表达式而言,“单词字符”只是26个拉丁字母(大写和小写)和数字,而且由于某些原因还包括下划线字符。像α或或β这种明显的单词字符,则无法匹配\w(会匹配大写的\W,因为它们属于非单词字符)。
|
||||||
|
|
||||||
像a`或或β这种明显的单词字符,则无法匹配\w(会匹配大写的\W,因为它们属于非单词字符)。
|
|
||||||
|
|
||||||
由于奇怪的历史性意外,\s(空白字符)则没有这种问题,会匹配所有Unicode 标准中规定的空白字符,包括不间断空格和蒙古文元音分隔符。
|
由于奇怪的历史性意外,\s(空白字符)则没有这种问题,会匹配所有Unicode 标准中规定的空白字符,包括不间断空格和蒙古文元音分隔符。
|
||||||
|
|
||||||
一些程序设计语言中的正则表达式语法实现可以匹配特定Unicode 字符集,比如所有大写字母,所有标点符号或控制字符JavaScript 中也计划加入对这些字符集的支持,但遗憾的是在不远的将来这貌似是无法实现的。
|
另一个问题是,默认情况下,正则表达式使用代码单元,而不是实际的字符,正如第 5 章中所讨论的那样。 这意味着由两个代码单元组成的字符表现很奇怪。
|
||||||
|
|
||||||
### 9.19 本章小结
|
```
|
||||||
|
console.log(/\ud83c\udf4e{3}/.test("\ud83c\udf4e\ud83c\udf4e\ud83c\udf4e"));
|
||||||
|
// → false
|
||||||
|
console.log(/<.>/.test("<\ud83c\udf39>"));
|
||||||
|
// → false
|
||||||
|
console.log(/<.>/u.test("<\ud83c\udf39>"));
|
||||||
|
// → true
|
||||||
|
```
|
||||||
|
|
||||||
正则表达式是表示字符串模式的对象,使用自己的语法来表达这些模式:
|
问题是第一行中的`"\ud83c\udf4e"`(emoji 苹果)被视为两个代码单元,而`{3}`部分仅适用于第二个。 与之类似,点匹配单个代码单元,而不是组成玫瑰 emoji 符号的两个代码单元。
|
||||||
|
|
||||||
|
你必须在正则表达式中添加一个`u`选项(表示 Unicode),才能正确处理这些字符。 不幸的是,错误的行为仍然是默认行为,因为改变它可能会导致依赖于它的现有代码出现问题。
|
||||||
|
|
||||||
|
尽管这是刚刚标准化的,在撰写本文时尚未得到广泛支持,但可以在正则表达式中使用'\p'(必须启用 Unicode 选项)以匹配 Unicode 标准分配了给定属性的所有字符。
|
||||||
|
|
||||||
|
```
|
||||||
|
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}'符号来匹配任何具有该属性的给定值的字符。 如果属性名称保持不变,如`\p{Name}`中那样,名称被假定为二元属性,如`Alphabetic`,或者类别,如`Number`。
|
||||||
|
|
||||||
|
### 本章小结
|
||||||
|
|
||||||
|
正则表达式是表示字符串模式的对象,使用自己的语言来表达这些模式:
|
||||||
|
|
||||||
+ /abc/:字符序列
|
+ /abc/:字符序列
|
||||||
|
|
||||||
+ /[abc]/:字符集中的任何字符
|
+ /[abc]/:字符集中的任何字符
|
||||||
|
|
||||||
+ /[^abc]/:任何不在字符集中的字符
|
+ /[^abc]/:不在字符集中的任何字符
|
||||||
|
|
||||||
+ /[0-9]/:任何在字符范围内的字符
|
+ /[0-9]/:字符范围内的任何字符
|
||||||
|
|
||||||
+ /x+/:出现模式x一次或多次
|
+ /x+/:出现一次或多次
|
||||||
|
|
||||||
+ /x+?/:出现模式x一次或多次,非贪婪模式
|
+ /x+?/:出现一次或多次,非贪婪模式
|
||||||
|
|
||||||
+ /x*/:出现模式次或多次
|
+ /x*/:出现零次或多次
|
||||||
|
|
||||||
+ /x?/:出现模式零次或多次,非贪婪模式
|
+ /x?/:出现零次或多次,非贪婪模式
|
||||||
|
|
||||||
+ /x{2,4}/:出现次数在两个数字范围之间
|
+ /x{2,4}/:出现两次到四次
|
||||||
|
|
||||||
+ /(abc)/:元组
|
+ /(abc)/:元组
|
||||||
|
|
||||||
@ -663,21 +686,19 @@ function parseINI(string) {
|
|||||||
|
|
||||||
+ /$/:输入结束位置
|
+ /$/:输入结束位置
|
||||||
|
|
||||||
正则表达式有test方法,用于测试给定的字符串是否匹配模式。还有一个exec方法,当匹配模式后,返回包含所有匹配元组的数组。这类数组有一个index属性,用于指出匹配的起始位置。
|
正则表达式有一个`test`方法来测试给定的字符串是否匹配它。 它还有一个`exec`方法,当找到匹配项时,返回一个包含所有匹配组的数组。 这样的数组有一个`index`属性,用于表明匹配开始的位置。
|
||||||
|
|
||||||
字符串有一个match方法,可以使用正则表达式进行匹配,还有一个search方法,可以搜索一个匹配项,返回匹配的起始位置。字符串的replace方法可以使用匹配字符串替换原字符串中匹配模式的文本。你还可以向replace传递一个函数,根据匹配文本和匹配元组创建匹配字符串。
|
字符串有一个`match`方法来对正确表达式匹配它们,以及`search`方法来搜索字符串,只返回匹配的起始位置。 他们的`replace`方法可以用替换字符串或函数替换模式匹配。
|
||||||
|
|
||||||
正则表达式可以在结尾的斜杠后添加选项。选项i使得匹配不区分大小写,选项g使得表达式变成全局匹配,这种情况下,replace方法会替换所有匹配项,而非第一项。
|
正则表达式拥有选项,这些选项写在闭合斜线后面。 `i`选项使匹配不区分大小写。 `g`选项使表达式成为全聚德,除此之外,它使`replace`方法替换所有实例,而不是第一个。 `y`选项使它变为粘性,这意味着它在搜索匹配时不会向前搜索并跳过部分字符串。 `u`选项开启 Unicode 模式,该模式解决了处理占用两个代码单元的字符时的一些问题。
|
||||||
|
|
||||||
RegExp构造函数可以用于通过字符串创建正则表达式。
|
正则表达式是难以驾驭的强力工具。它可以简化一些任务,但用到一些复杂问题上时也会难以控制管理。想要学会使用正则表达式的重要一点是:不要将其用到无法干净地表达为正则表达式的问题。
|
||||||
|
|
||||||
正则表达式是难以驾驭的强力工具。它可以简化一些任务,但用到一些复杂问题上时也会难以控制管理。想要学会使用正则表达式的重要一点是:不要将其用到无法使用正则表达式描述的问题中。
|
### 习题
|
||||||
|
|
||||||
### 9.20 习题
|
|
||||||
|
|
||||||
在做本章习题时,读者不可避免地会对一些正则表达式的莫名其妙的行为感到困惑,因而备受挫折。读者可以使用类似于[http://debuggex.com/](http://debuggex.com/)这样的在线学习工具,将你想编写的正则表达式可视化,并试验其对不同输入字符串的响应。
|
在做本章习题时,读者不可避免地会对一些正则表达式的莫名其妙的行为感到困惑,因而备受挫折。读者可以使用类似于[http://debuggex.com/](http://debuggex.com/)这样的在线学习工具,将你想编写的正则表达式可视化,并试验其对不同输入字符串的响应。
|
||||||
|
|
||||||
#### 9.20.1 RegexpGolf
|
#### RegexpGolf
|
||||||
|
|
||||||
Code Golf是一种游戏,尝试尽量用最少的字符来描述特定程序。类似的,Regexp Golf这种活动是编写尽量短小的正则表达式,来匹配给定模式(而且只能匹配给定模式)。
|
Code Golf是一种游戏,尝试尽量用最少的字符来描述特定程序。类似的,Regexp Golf这种活动是编写尽量短小的正则表达式,来匹配给定模式(而且只能匹配给定模式)。
|
||||||
|
|
||||||
@ -691,11 +712,11 @@ Code Golf是一种游戏,尝试尽量用最少的字符来描述特定程序
|
|||||||
|
|
||||||
4.以ious结尾的单词
|
4.以ious结尾的单词
|
||||||
|
|
||||||
5.空白字符后面紧跟着句号、冒号、分号
|
5.句号、冒号、分号之前的空白字符
|
||||||
|
|
||||||
6.多于六个字母的单词
|
6.多于六个字母的单词
|
||||||
|
|
||||||
7.不包含e的单词
|
7.不包含e(或者E)的单词
|
||||||
|
|
||||||
需要帮助时,请参考本章总结中的表格。使用少量测试字符串来测试每个解决方案。
|
需要帮助时,请参考本章总结中的表格。使用少量测试字符串来测试每个解决方案。
|
||||||
|
|
||||||
@ -708,7 +729,7 @@ verify(/.../,
|
|||||||
|
|
||||||
verify(/.../,
|
verify(/.../,
|
||||||
["pop culture", "mad props"],
|
["pop culture", "mad props"],
|
||||||
["plop"]);
|
["plop", "prrrop"]]);
|
||||||
|
|
||||||
verify(/.../,
|
verify(/.../,
|
||||||
["ferret", "ferry", "ferrari"],
|
["ferret", "ferry", "ferrari"],
|
||||||
@ -720,7 +741,7 @@ verify(/.../,
|
|||||||
|
|
||||||
verify(/.../,
|
verify(/.../,
|
||||||
["bad punctuation ."],
|
["bad punctuation ."],
|
||||||
["escape the dot"]);
|
["escape the period"]);
|
||||||
|
|
||||||
verify(/.../,
|
verify(/.../,
|
||||||
["hottentottententen"],
|
["hottentottententen"],
|
||||||
@ -728,31 +749,53 @@ verify(/.../,
|
|||||||
|
|
||||||
verify(/.../,
|
verify(/.../,
|
||||||
["red platypus", "wobbling nest"],
|
["red platypus", "wobbling nest"],
|
||||||
["earth bed", "learning ape"]);
|
["earth bed", "learning ape", "BEET"]);
|
||||||
|
|
||||||
|
|
||||||
function verify(regexp, yes, no) {
|
function verify(regexp, yes, no) {
|
||||||
// Ignore unfinished exercises
|
// Ignore unfinished exercises
|
||||||
if (regexp.source == "...") return;
|
if (regexp.source == "...") return;
|
||||||
yes.forEach(function(s) {
|
for (let str of yes) if (!regexp.test(str)) {
|
||||||
if (!regexp.test(s))
|
console.log(`Failure to match '${str}'`);
|
||||||
console.log("Failure to match '" + s + "'");
|
}
|
||||||
});
|
for (let str of no) if (regexp.test(str)) {
|
||||||
no.forEach(function(s) {
|
console.log(`Unexpected match for '${str}'`);
|
||||||
if (regexp.test(s))
|
}
|
||||||
console.log("Unexpected match for '" + s + "'");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 9.20.2 QuotingStyle
|
#### QuotingStyle
|
||||||
|
|
||||||
想象一下,你编写了一个故事,自始至终都使用单引号来标记对话。现在你想要将对话的引号替换成双引号,但不能替换在缩略形式中使用的单引号。
|
想象一下,你编写了一个故事,自始至终都使用单引号来标记对话。现在你想要将对话的引号替换成双引号,但不能替换在缩略形式中使用的单引号。
|
||||||
|
|
||||||
思考一下可以区分这两种引号用法的模式,并手动调用replace方法进行正确替换。
|
思考一下可以区分这两种引号用法的模式,并手动调用replace方法进行正确替换。
|
||||||
|
|
||||||
#### 9.20.3 NumbersAgain
|
```
|
||||||
|
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."
|
||||||
|
```
|
||||||
|
|
||||||
一个数字序列可以使用简单的正则表达式/\d+/匹配。
|
#### NumbersAgain
|
||||||
|
|
||||||
编写一个表达式,只匹配JavaScript风格的数字。支持数字前可选的正号与负号、十进制小数点、指数计数法(5e-3或1E10,指数前也需要支持可选的符号)。也请注意小数点前或小数点后的数字也是不必要的,但数字不能只有小数点。例如.5和5.都是合法的JavaScript数字,但单个点则不是。
|
编写一个表达式,只匹配JavaScript风格的数字。支持数字前可选的正号与负号、十进制小数点、指数计数法(5e-3或1E10,指数前也需要支持可选的符号)。也请注意小数点前或小数点后的数字也是不必要的,但数字不能只有小数点。例如.5和5.都是合法的JavaScript数字,但单个点则不是。
|
||||||
|
|
||||||
|
```
|
||||||
|
// 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}'`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Loading…
x
Reference in New Issue
Block a user