1
0
mirror of https://github.com/apachecn/eloquent-js-3e-zh.git synced 2025-05-23 20:02:20 +00:00
This commit is contained in:
wizardforcel 2018-05-07 10:44:19 +08:00
parent 53c34b58de
commit d48e8603c5

163
9.md
View File

@ -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循环中使用赋值表达式ifmatch=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{24}/:出现次数在两个数字范围之间 + /x{24}/:出现两次到四次
+ /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}'`);
}
}
```