开始阅读JavaScript高级程序设计(第5版)学习JS,总共有1000+页,非常全面,短期看完不太现实,找到了一篇博客,花些时间跟着这篇博客过一下红宝书。
红宝书《JavaScript高级程序设计(第5版)》学习大纲 - 大前端全栈开发 - SegmentFault 思否
引用值(或者对象)是某个特定引用类型的实例。引用类型指的是:变量存储的不是值的本身,而是值的”地址”(引用)。
在ECMAScript中,引用类型是把数据和功能组织到一起的结构,经常被人错误地称作“类”,虽然与类相似,但是实现不同。虽然从技术上讲JavaScript是一门面向对象语言,但ECMAScript缺少传统的面向对象编程语言所具备的某些基本结构,包括类和接口。
引用类型有时候也被称为对象定义,因为它们描述了自己的对象应有的属性和方法。函数也是一种引用类型,也是对象,函数实际上是Function这个引用类型的实例。因为函数也是对象,所以函数也有方法,可以用于增强其能力。
JavaScript 中除了原始值(string、number、boolean 等)以外都是引用类型,对象、数组、函数等。
对象被认为是某个特定引用类型的实例。新对象通过使用new操作符后跟一个构造函数(constructor)来创建。构造函数就是用来创建新对象的函数。
1
let now = new Date();
这行代码创建了引用类型Date(是ES的原生引用类型之一)的一个新实例,并将它保存在变量now中。Date()在这里就是构造函数,它负责创建一个只有默认属性和方法的简单对象。
Date:
Date 内部将日期存为 UTC 1970-01-01 00:00:00 至今的毫秒数,可精确表示该时间点前后约 28 万年的日期。
创建日期对象
1
let now = new Date(); // 当前时间
不传参数默认为当前时间。传入其他日期需提供毫秒数,ECMAScript 提供了两个辅助方法。
Date.parse() — 字符串转毫秒
接收日期字符串,返回对应毫秒数。支持以下格式:
| 格式 | 示例 |
|---|---|
| 月/日/年 | "5/23/2019" |
| 月名 日, 年 | "May 23, 2019" |
| 完整时区格式 | "Tue May 23 2019 00:00:00 GMT-0700" |
| ISO 8601 | "2019-05-23T00:00:00" |
1
2
3
let someDate = new Date(Date.parse("May 23, 2019"));
// 等价写法(构造函数自动调用 Date.parse)
let someDate = new Date("May 23, 2019");
字符串无法解析时返回 NaN。
Date.UTC() — 参数转毫秒(UTC 时间)
参数依次为:年、月(0起点)、日、时、分、秒、毫秒,只有年和月必填,其余默认 0,日默认 1。
1
2
let y2k = new Date(Date.UTC(2000, 0)); // UTC 2000-01-01 00:00
let allFives = new Date(Date.UTC(2005, 4, 5, 17, 55, 55)); // UTC 2005-05-05 17:55:55
构造函数也接受相同参数,但创建的是本地时间而非 UTC:
1
2
let y2k = new Date(2000, 0); // 本地 2000-01-01 00:00
let allFives = new Date(2005, 4, 5, 17, 55, 55); // 本地 2005-05-05 17:55:55
Date.now() — 当前时间戳
返回当前时刻的毫秒数,常用于性能计时:
1
2
3
let start = Date.now();
doSomething();
let result = Date.now() - start; // 耗时毫秒数
继承的方法:
Date 类型重写了三个方法,但行为各不相同:
toLocaleString():返回符合本地环境的日期时间,含 AM/PM,不含时区。格式因浏览器而异,仅适合调试,不适合显示。
toString():返回含时区信息的日期时间,24小时制。同样因浏览器而异。
1
2
toLocaleString() → 2/1/2019 12:00:00 AM
toString() → Thu Feb 1 2019 00:00:00 GMT-0800 (Pacific Standard Time)
valueOf():不返回字符串,返回日期的毫秒时间戳。因此可以直接用比较运算符对日期排序:
1
2
3
4
let date1 = new Date(2019, 0, 1); // 2019年1月1日
let date2 = new Date(2019, 1, 1); // 2019年2月1日
console.log(date1 < date2); // true ← 毫秒数更小 = 日期更早
日期格式化方法与日期/时间组件方法:
Date类型有几个专门用于格式化日期的方法,它们都会返回字符串。Date类型剩下的方法直接涉及取得或设置日期值的特定部分。此部分由于是列举,可直接查看原文。
RegExp:
ECMAScript 通过 RegExp 类型支持正则表达式,语法如下:
1
let expression = /pattern/flags;
pattern 可以是任意正则表达式,支持字符类、限定符、分组、向前查找、反向引用等。flags 匹配标记用于控制匹配行为:
| 标记 | 名称 | 作用 |
|---|---|---|
g | 全局模式 | 匹配全部内容,而不是找到第一个就停止 |
i | 忽略大小写 | 匹配时不区分大小写 |
m | 多行模式 | 到达一行末尾后继续查找下一行 |
y | 黏附模式 | 只从 lastIndex 位置开始匹配 |
u | Unicode 模式 | 启用 Unicode 匹配 |
s | 单行模式 | . 可匹配换行符 |
d | 索引模式 | 结果包含每个捕获组的起止索引 |
1
2
3
4
5
6
7
8
// 匹配字符串中的所有"at"
let pattern1 = /at/g;
// 匹配第一个"bat"或"cat",忽略大小写
let pattern2 = /[bc]at/i;
// 匹配所有以"at"结尾的三字符组合,忽略大小写
let pattern3 = /.at/gi;
正则表达式中的元字符必须转义才能匹配字面量:
1
2
3
4
5
6
7
8
9
10
11
12
13
( [ { \ ^ $ | ) ] } ? * + .
// 匹配第一个"bat"或"cat",忽略大小写
let pattern1 = /[bc]at/i;
// 匹配第一个"[bc]at",忽略大小写
let pattern2 = /\[bc\]at/i;
// 匹配所有以"at"结尾的三字符组合,忽略大小写
let pattern3 = /.at/gi;
// 匹配所有".at",忽略大小写
let pattern4 = /\.at/gi;
前面例子中的正则表达式都是使用字面量形式定义的。正则表达式也可以使用RegExp构造函数来创建,它接收两个参数:模式字符串和(可选的)标记字符串:
1
2
3
4
5
// 匹配第一个"bat"或"cat",忽略大小写
let pattern1 = /[bc]at/i;
// 跟pattern1一样,只不过是用构造函数创建的
let pattern2 = new RegExp("[bc]at", "i");
这里的pattern1和pattern2是等效的正则表达式。注意,RegExp构造函数的两个参数都是字符串。因为RegExp的模式参数是字符串,所以在某些情况下需要二次转义。
字面量:一层解析(正则引擎)
1
/\[bc\]at/ // 正则引擎看到 \[ → 匹配字面量 [
\ 只需要满足正则引擎的转义要求。
构造函数:两层解析(字符串 → 正则引擎)
1
new RegExp("\\[bc\\]at")
第一层:JS 字符串解析
"\\["→ 字符串值为\[
第二层:正则引擎解析
\[→ 匹配字面量[
所有元字符都必须二次转义,包括转义字符序列,如 \n(\ 转义后的字符串是 \\,在正则表达式字符串中则要写成 \\\)。下表展示了几个正则表达式的字面量形式,以及使用RegExp构造函数创建时对应的模式字符串。 
此外,使用RegExp也可以基于已有的正则表达式实例,并可选择性地修改它们的标记:
1
2
3
4
5
6
7
8
const re1 = /cat/g;
console.log(re1); // "/cat/g"
const re2 = new RegExp(re1);
console.log(re2); // "/cat/g"
const re3 = new RegExp(re1, "i");
console.log(re3); // "/cat/i"
捕获组可以通过索引访问,不好的一点是索引没有提供它们实际包含内容的上下文:
1
2
3
4
5
6
const text = '2018-03-14';
const re = /(\d+)-(\d+)-(\d+)/;
console.log(re.exec(text)) ;
//["2018-03-14", "2018", "03", "14"]
命名捕获组支持将有效的JavaScript标识符与捕获组相关联,然后可以从结果的groups属性中取得捕获组:
1
2
3
4
5
6
const text = '2018-03-14';
const re = /(?<year>\d+)-(?<month>\d+)-(?<day>\d+)/;
console.log (re.exec(text).groups) ;
// { year: "2018", month: "03", day: "14" }
RegExp实例属性:
RegExp 实例属性一览:
| 属性 | 类型 | 含义 |
|---|---|---|
global | 布尔 | 是否设置了 g 标记 |
ignoreCase | 布尔 | 是否设置了 i 标记 |
multiline | 布尔 | 是否设置了 m 标记 |
unicode | 布尔 | 是否设置了 u 标记 |
sticky | 布尔 | 是否设置了 y 标记 |
dotAll | 布尔 | 是否设置了 s 标记 |
hasIndices | 布尔 | 是否设置了 d 标记 |
lastIndex | 整数 | 下次搜索的起始位置,默认 0 |
source | 字符串 | 模式的字面量形式(无前后斜杠) |
flags | 字符串 | 标记字符串 |
通过这些属性可以全面了解正则表达式的信息,不过实际开发中用得并不多,因为模式声明中包含这些信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let pattern1 = /\[bc\]at/i;
console.log(pattern1.global); // false
console.log(pattern1.ignoreCase); // true
console.log(pattern1.multiline); // false
console.log(pattern1.lastIndex); // 0
console.log(pattern1.source); // "\[bc\]at"
console.log(pattern1.flags); // "i"
let pattern2 = new RegExp("\\[bc\\]at", "i");
console.log(pattern2.global); // false
console.log(pattern2.ignoreCase); // true
console.log(pattern2.multiline); // false
console.log(pattern2.lastIndex); // 0
console.log(pattern2.source); // "\[bc\]at"
console.log(pattern2.flags); // "i"
虽然第一个模式是通过字面量创建的,第二个模式是通过RegExp构造函数创建的,但两个模式的source和flags属性是相同的。source和flags属性返回的是规范化之后可以在字面量中使用的形式。
RegExp实例方法:
exec() — 捕获组匹配
接收字符串,返回数组或 null。返回的数组有两个额外属性:
| 属性 | 含义 |
|---|---|
index | 匹配的起始位置 |
input | 原始字符串 |
数组元素:[0] = 整个匹配,[1]、[2]… = 各捕获组。
js复制
1
2
3
4
5
6
7
8
let text = "mom and dad and baby";
let pattern = /mom( and dad( and baby)?)?/gi;
let matches = pattern.exec(text);
matches[0]; // "mom and dad and baby" ← 整个匹配
matches[1]; // " and dad and baby" ← 第1个捕获组
matches[2]; // " and baby" ← 第2个捕获组
matches.index; // 0
全局标记 g 对 exec() 的影响
1
2
3
4
5
6
7
8
9
10
// 无 g:每次调用都返回第一个匹配,lastIndex 始终为 0
let pattern = /.at/;
pattern.exec("cat, bat, sat, fat"); // "cat",lastIndex = 0
pattern.exec("cat, bat, sat, fat"); // "cat",lastIndex = 0
// 有 g:每次调用返回下一个匹配,lastIndex 自动更新
let pattern = /.at/g;
pattern.exec("cat, bat, sat, fat"); // "cat",lastIndex = 3
pattern.exec("cat, bat, sat, fat"); // "bat",lastIndex = 8
pattern.exec("cat, bat, sat, fat"); // "sat",lastIndex = 13
黏附标记 y — 只从 lastIndex 处匹配
1
2
3
4
5
6
7
8
let pattern = /.at/y;
pattern.exec("cat, bat, sat, fat"); // "cat",lastIndex = 3
pattern.exec("cat, bat, sat, fat"); // null(索引3是逗号,不匹配)
// 匹配失败时 lastIndex 重置为 0
pattern.lastIndex = 5; // 手动设置起点
pattern.exec("cat, bat, sat, fat"); // "bat",lastIndex = 8
y 覆盖 g:只从 lastIndex 位置开始匹配,不自动继续搜索。
test() — 只判断是否匹配
1
2
let pattern = /\d{3}-\d{2}-\d{4}/;
pattern.test("000-00-0000"); // true
只返回 true/false,不需要匹配内容时用,常用于输入验证。
继承方法
1
2
3
4
let pattern = new RegExp("\\[bc\\]at", "gi");
pattern.toString(); // /\[bc\]at/gi ← 返回字面量形式
pattern.toLocaleString(); // /\[bc\]at/gi ← 同上
pattern.valueOf(); // 返回正则表达式本身
构造函数创建的 RegExp,toString() 也返回字面量形式。