JavaScript高级程序设计(第4版)读书笔记:十

JavaScript Date与RegExp详解

JavaScript程序设计

4 基本引用类型

引用值(或者对象)是某个特定引用类型的实例。在 ECMAScript 中,引用类型是把数据和功能组织到一起的结构,经常被人错误地称作“类”。虽然从技术上讲 JavaScript 是一门面向对象语言,但ECMAScript 缺少传统的面向对象编程语言所具备的某些基本结构,包括类和接口。引用类型有时候也被称为对象定义,因为它们描述了自己的对象应有的属性和方法。

对象被认为是某个特定引用类型的实例。新对象通过使用 new 操作符后跟一个**构造函数(constructor)**来创建。构造函数就是用来创建新对象的函数,比如下面这行代码:

let now = new Date();

这行代码创建了引用类型 Date 的一个新实例,并将它保存在变量 now 中。Date()在这里就是构造函数,它负责创建一个只有默认属性和方法的简单对象。ECMAScript 提供了很多像 Date 这样的原生引用类型,帮助开发者实现常见的任务。

Note: 函数也是一种引用类型。

4.1 Date

ECMAScript 的 Date 类型参考了 Java 早期版本中的 java.util.Date。为此,Date 类型将日期保存为自协调世界时(UTC,Universal Time Coordinated)时间 1970 年 1 月 1 日午夜(零时)至今所经过的毫秒数。使用这种存储格式,Date 类型可以精确表示 1970 年 1 月 1 日之前及之后 285 616 年的日期。

要创建日期对象,就使用 new 操作符来调用 Date 构造函数:

let now = new Date();

在不给 Date 构造函数传参数的情况下,创建的对象将保存当前日期和时间。要基于其他日期和时间创建日期对象,必须传入其毫秒表示(UNIX 纪元 1970 年 1 月 1 日午夜之后的毫秒数)。ECMAScript为此提供了两个辅助方法:Date.parse()Date.UTC()

Date.parse()方法接收一个表示日期的字符串参数,尝试将这个字符串转换为表示该日期的毫秒数。ECMA-262 第 5 版定义了 Date.parse()应该支持的日期格式,填充了第 3 版遗留的空白。所有实现都必须支持下列日期格式:

  • “月/日/年”,如"5/23/2019";
  • “月名 日, 年”,如"May 23, 2019";
  • “周几 月名 日 年 时:分:秒 时区”,如"Tue May 23 2019 00:00:00 GMT-0700";
  • ISO 8601 扩展格式“YYYY-MM-DDTHH:mm:ss.sssZ”,如 2019-05-23T00:00:00(只适用于兼容 ES5 的实现)

比如,要创建一个表示“2019 年 5 月 23 日”的日期对象,可以使用以下代码:

let someDate = new Date(Date.parse("May 23, 2019"));

如果传给 Date.parse()的字符串并不表示日期,则该方法会返回 NaN如果直接把表示日期的字符串传给 Date 构造函数,那么 Date 会在后台调用 Date.parse()。换句话说,下面这行代码跟前面那行代码是等价的:

// 这行代码与上面一行得到的日期对象
let someDate = new Date("May 23, 2019");

Note: 不同的浏览器对 Date 类型的实现有很多问题。比如,很多浏览器会选择用当前日期替代越界的日期,因此有些浏览器会将"January 32, 2019"解释为"February 1, 2019"。Opera 则会插入当前月的当前日,返回"January 当前日, 2019"。就是说,如果是在 9 月 21 日运行代码,会返回"January 21, 2019"

Date.UTC()方法也返回日期的毫秒表示,但使用的是跟 Date.parse()不同的信息来生成这个值。传给 Date.UTC()的参数是年、零起点月数(1 月是 0,2 月是 1,以此类推)、日(131)、时(023)、分、秒和毫秒。这些参数中,只有前两个(年和月)是必需的。如果不提供日,那么默认为 1 日。其他参数的默认值都是 0。下面是使用 Date.UTC()的两个例子:

// GMT 时间 2000 年 1 月 1 日零点
let y2k = new Date(Date.UTC(2000, 0)); 
// GMT 时间 2005 年 5 月 5 日下午 5 点 55 分 55 秒
let allFives = new Date(Date.UTC(2005, 4, 5, 17, 55, 55));

这个例子创建了两个日期 。第一个日期是 2000 年 1 月 1 日零点(GMT),2000 代表年,0 代表月(1 月)。因为没有其他参数(日取 1,其他取 0),所以结果就是该月第 1 天零点。第二个日期表示 2005年 5 月 5 日下午 5 点 55 分 55 秒(GMT)。虽然日期里面涉及的都是 5,但月数必须用 4,因为月数是零起点的。小时也必须是 17,因为这里采用的是 24 小时制,即取值范围是 0~23。其他参数就都很直观了。

Date.parse()一样,Date.UTC()也会被 Date 构造函数隐式调用,但有一个区别:这种情况下创建的是本地日期,不是 GMT 日期。不过 Date 构造函数跟 Date.UTC()接收的参数是一样的。因此,如果第一个参数是数值,则构造函数假设它是日期中的年,第二个参数就是月,以此类推。前面的例子也可以这样来写:

// 本地时间 2000 年 1 月 1 日零点
let y2k = new Date(2000, 0); 
// 本地时间 2005 年 5 月 5 日下午 5 点 55 分 55 秒
let allFives = new Date(2005, 4, 5, 17, 55, 55);

// 例如:
// 假设在北京时区(GMT+8)
const utcDate = new Date(Date.UTC(2000, 0, 1, 12, 0, 0)); // UTC时间(世界统一时间,以英国伦敦(格林威治)时区为准)
const localDate = new Date(2000, 0, 1, 12, 0, 0); // 本地时间(此处即以北京时区为准)

console.log(utcDate.toISOString());    // "2000-01-01T12:00:00.000Z"
console.log(utcDate.toString());       // "Sat Jan 01 2000 20:00:00 GMT+0800" (中国标准时间)

console.log(localDate.toISOString());  // "2000-01-01T04:00:00.000Z"  
console.log(localDate.toString());     // "Sat Jan 01 2000 12:00:00 GMT+0800" (中国标准时间)

以上代码创建了与前面例子中相同的两个日期,但这次的两个日期是(由于系统设置决定的)本地时区的日期。

ECMAScript 还提供了 Date.now()方法,返回表示方法执行时日期和时间的毫秒数。这个方法可以方便地用在代码分析中:

// 起始时间
let start = Date.now(); 
// 调用函数
doSomething(); 
// 结束时间
let stop = Date.now(), 
result = stop - start;
4.1.1 继承的方法

与其他类型一样,Date 类型重写了 toLocaleString()toString()valueOf()方法。但与其他类型不同,重写后这些方法的返回值不一样。Date 类型的 toLocaleString()方法返回与浏览器运行的本地环境一致的日期和时间。这通常意味着格式中包含针对时间的 AM(上午)或 PM(下午),但不包含时区信息(具体格式可能因浏览器而不同)。toString()方法通常返回带时区信息的日期和时间,而时间也是以 24 小时制(0~23)表示的。下面给出了 toLocaleString()和 toString()返回的2019 年 2 月 1 日零点的示例(地区为"en-US"的 PST,即 Pacific Standard Time,太平洋标准时间):

toLocaleString() - 2/1/2019 12:00:00 AM 
toString() - Thu Feb 1 2019 00:00:00 GMT-0800 (Pacific Standard Time)

现代浏览器在这两个方法的输出上已经趋于一致。在比较老的浏览器上,每个方法返回的结果可能在每个浏览器上都是不同的。这些差异意味着 toLocaleString()和 toString()可能只对调试有用,不能用于显示。

Date 类型的 valueOf()方法根本就不返回字符串,这个方法被重写后返回的是日期的毫秒表示因此,操作符(如小于号和大于号)可以直接使用它返回的值。比如下面的例子:

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 
console.log(date1 > date2); // false

日期 2019 年 1 月 1 日在 2019 年 2 月 1 日之前,所以说前者小于后者没问题。因为 2019 年 1 月 1 日的毫秒表示小于 2019 年 2 月 1 日的毫秒表示,所以用小于号比较这两个日期时会返回 true。这也是确保日期先后的一个简单方式。

4.1.2 日期格式化方法

Date 类型有几个专门用于格式化日期的方法,它们都会返回字符串:

  • toDateString()显示日期中的周几、月、日、年(格式特定于实现)
  • toTimeString()显示日期中的时、分、秒和时区(格式特定于实现)
  • toLocaleDateString()显示日期中的周几、月、日、年(格式特定于实现和地区)
  • toLocaleTimeString()显示日期中的时、分、秒(格式特定于实现和地区)
  • toUTCString()显示完整的 UTC 日期(格式特定于实现)

这些方法的输出与 toLocaleString()toString()一样,会因浏览器而异。因此不能用于在用户界面上一致地显示日期。

Note: 还有一个方法叫 toGMTString(),这个方法跟 toUTCString()是一样的,目的是为了向后兼容。不过,规范建议新代码使用 toUTCString()。

4.1.3 日期/时间组件方法

Date 类型剩下的方法(见下表)直接涉及取得或设置日期值的特定部分。注意表中“UTC 日期”,指的是没有时区偏移(将日期转换为 GMT)时的日期。

方法说明
getTime()返回日期的毫秒表示;与 valueOf()相同
setTime(milliseconds)设置日期的毫秒表示,从而修改整个日期
getFullYear()返回 4 位数年(即 2019 而不是 19)
getUTCFullYear()返回 UTC 日期的 4 位数年
setFullYear(year)设置日期的年(year 必须是 4 位数)
setUTCFullYear(year)设置 UTC 日期的年(year 必须是 4 位数)
getMonth()返回日期的月(0 表示 1 月,11 表示 12 月
getUTCMonth()返回 UTC 日期的月(0 表示 1 月,11 表示 12 月)
setMonth(month)设置日期的月(month 为大于 0 的数值,大于 11 加年)
setUTCMonth(month)设置 UTC 日期的月(month 为大于 0 的数值,大于 11 加年)
getDate()返回日期中的日(1~31)
getUTCDate()返回 UTC 日期中的日(1~31)
setDate(date)设置日期中的日(如果 date 大于该月天数,则加月)
setUTCDate(date)设置 UTC 日期中的日(如果 date 大于该月天数,则加月)
getDay()返回日期中表示周几的数值(0 表示周日,6 表示周六
getUTCDay()返回 UTC 日期中表示周几的数值(0 表示周日,6 表示周六)
getHours()返回日期中的时(0~23)
getUTCHours()返回 UTC 日期中的时(0~23)
setHours(hours)设置日期中的时(如果 hours 大于 23,则加日)
setUTCHours(hours)设置 UTC 日期中的时(如果 hours 大于 23,则加日)
getMinutes()返回日期中的分(0~59)
getUTCMinutes()返回 UTC 日期中的分(0~59)
setMinutes(minutes)设置日期中的分(如果 minutes 大于 59,则加时)
setUTCMinutes(minutes)设置 UTC 日期中的分(如果 minutes 大于 59,则加时)
getSeconds()返回日期中的秒(0~59)
getUTCSeconds()返回 UTC 日期中的秒(0~59)
setSeconds(seconds)设置日期中的秒(如果 seconds 大于 59,则加分)
setUTCSeconds(seconds)设置 UTC 日期中的秒(如果 seconds 大于 59,则加分)
getMilliseconds()返回日期中的毫秒
getUTCMilliseconds()返回 UTC 日期中的毫秒
setMilliseconds(milliseconds)设置日期中的毫秒
setUTCMilliseconds(milliseconds)设置 UTC 日期中的毫秒
getTimezoneOffset()返回以分钟计的 UTC 与本地时区的偏移量(如美国 EST 即“东部标准时间”返回 300,进入夏令时的地区可能有所差异)

4.2 RegExp

ECMAScript 通过 RegExp 类型支持正则表达式。正则表达式使用类似 Perl 的简洁语法来创建:

let expression = /pattern/flags;

这个正则表达式的 pattern(模式)可以是任何简单或复杂的正则表达式,包括字符类、限定符、分组、向前查找和反向引用。每个正则表达式可以带零个或多个 flags(标记),用于控制正则表达式的行为。下面给出了表示匹配模式的标记:

  • g:全局模式,表示查找字符串的全部内容,而不是找到第一个匹配的内容就结束。
  • i:不区分大小写,表示在查找匹配时忽略 pattern 和字符串的大小写。
  • m:多行模式,表示查找到一行文本末尾时会继续查找。
  • y:粘附模式,表示只查找从 lastIndex 开始及之后的字符串。
  • u:Unicode 模式,启用 Unicode 匹配。
  • s:dotAll 模式,表示元字符.匹配任何字符(包括\n 或\r)。

使用不同模式和标记可以创建出各种正则表达式,比如:

// 匹配字符串中的所有"at" 
let pattern1 = /at/g; 
// 匹配第一个"bat"或"cat",忽略大小写
let pattern2 = /[bc]at/i; 
// 匹配所有以"at"结尾的三字符组合,忽略大小写
let pattern3 = /.at/gi;

与其他语言中的正则表达式类似,所有元字符在模式中也必须转义,包括:

( [ { \ ^ $ | ) ] } ? * + .

元字符在正则表达式中都有一种或多种特殊功能,所以要匹配上面这些字符本身,就必须使用反斜杠来转义。下面是几个例子:

// 匹配第一个"bat"或"cat",忽略大小写
let pattern1 = /[bc]at/i; 
// 匹配第一个"[bc]at",忽略大小写
let pattern2 = /\[bc\]at/i; 
// 匹配所有以"at"结尾的三字符组合,忽略大小写
let pattern3 = /.at/gi; 
// 匹配所有".at",忽略大小写
let pattern4 = /\.at/gi;

这里的 pattern1 匹配"bat"或"cat",不区分大小写。要直接匹配"[bc]at",左右中括号都必须像 pattern2 中那样使用反斜杠转义。在 pattern3 中,点号表示"at"前面的任意字符都可以匹配。如果想匹配".at",那么要像 pattern4 中那样对点号进行转义。

前面例子中的正则表达式都是使用字面量形式定义的。正则表达式也可以使用 RegExp 构造函数来创建,它接收两个参数:模式字符串和(可选的)标记字符串。任何使用字面量定义的正则表达式也可以通过构造函数来创建,比如:

// 匹配第一个"bat"或"cat",忽略大小写
let pattern1 = /[bc]at/i; 
// 跟 pattern1 一样,只不过是用构造函数创建的
let pattern2 = new RegExp("[bc]at", "i");

这里的 pattern1 和 pattern2 是等效的正则表达式。注意,RegExp 构造函数的两个参数都是字符串。因为 RegExp 的模式参数是字符串,所以在某些情况下需要二次转义所有元字符都必须二次转义,包括转义字符序列,如\n(\转义后的字符串是\,在正则表达式字符串中则要写成\\)。下表展示了几个正则表达式的字面量形式,以及使用 RegExp 构造函数创建时对应的模式字符串。

字面量模式对应的字符串
/\[bc\]at/“\\[bc\\]at”
/\.at/“\\.at”
/name/age/“name\/age”
/\d.\d{1,2}/“\\d.\\d{1,2}”
/\w\\hello\\123/“\\w\\\\hello\\\\123”

此外,使用 RegExp 也可以基于已有的正则表达式实例,并可选择性地修改它们的标记:

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"
4.2.1 RegExp 实例属性

每个 RegExp 实例都有下列属性,提供有关模式的各方面信息。

  • global:布尔值,表示是否设置了 g 标记。
  • ignoreCase:布尔值,表示是否设置了 i 标记。
  • unicode:布尔值,表示是否设置了 u 标记。
  • sticky:布尔值,表示是否设置了 y 标记。
  • lastIndex:整数,表示在源字符串中下一次搜索的开始位置,始终从 0 开始。
  • multiline:布尔值,表示是否设置了 m 标记。
  • dotAll:布尔值,表示是否设置了 s 标记。
  • source:正则表达式的字面量字符串(不是传给构造函数的模式字符串),没有开头和结尾的斜杠。
  • flags:正则表达式的标记字符串。始终以字面量而非传入构造函数的字符串模式形式返回(没有前后斜杠)。

通过这些属性可以全面了解正则表达式的信息,不过实际开发中用得并不多,因为模式声明中包含这些信息。下面是一个例子:

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 属性返回的是规范化之后可以在字面量中使用的形式

4.2.2 RegExp 实例方法

RegExp 实例的主要方法是 exec(),主要用于配合捕获组使用。这个方法只接收一个参数,即要应用模式的字符串。如果找到了匹配项,则返回包含第一个匹配信息的数组;如果没找到匹配项,则返回null。返回的数组虽然是 Array 的实例,但包含两个额外的属性:index 和 input。index 是字符串中匹配模式的起始位置,input 是要查找的字符串。这个数组的第一个元素是匹配整个模式的字符串,其他元素是与表达式中的捕获组匹配的字符串。如果模式中没有捕获组,则数组只包含一个元素。来看下面的例子:

let text = "mom and dad and baby"; 
let pattern = /mom( and dad( and baby)?)?/gi; 
let matches = pattern.exec(text); 
console.log(matches.index); // 0 
console.log(matches.input); // "mom and dad and baby" 
console.log(matches[0]); // "mom and dad and baby" 
console.log(matches[1]); // " and dad and baby" 
console.log(matches[2]); // " and baby"

在这个例子中,模式包含两个捕获组:最内部的匹配项" and baby",以及外部的匹配项" and dad"或" and dad and baby"。调用 exec()后找到了一个匹配项。因为整个字符串匹配模式,所以 matchs数组的 index 属性就是 0。数组的第一个元素是匹配的整个字符串,第二个元素是匹配第一个捕获组的字符串,第三个元素是匹配第二个捕获组的字符串。

这里对捕获组来解释一下:

捕获组就是正则表达式中用圆括号 () 括起来的部分,它有两个作用:

  1. 分组:把多个字符当作一个整体
  2. 捕获:记住括号内匹配的内容,方便后续使用

基本示例:

// 提取日期中的年、月、日
let datePattern = /(\d{4})-(\d{2})-(\d{2})/;
let date = "2024-01-15";
let result = date.match(datePattern);

console.log(result[0]); // "2024-01-15" - 整个日期
console.log(result[1]); // "2024" - 年(第一个捕获组)
console.log(result[2]); // "01"   - 月(第二个捕获组)
console.log(result[3]); // "15"   - 日(第三个捕获组)

实际用途:

// 1. 提取信息
let emailPattern = /(\w+)@(\w+\.\w+)/;
let email = "user@example.com";
let emailParts = email.match(emailPattern);

console.log("用户名:", emailParts[1]); // "user"
console.log("域名:", emailParts[2]);   // "example.com"

// 2. 在替换中使用
let name = "张 三";
let swapped = name.replace(/(\w+) (\w+)/, "$2 $1");
console.log(swapped); // "三 张" - 交换了位置

// 3. 验证复杂格式
// 验证并提取电话号码
let phonePattern = /(\d{3})-(\d{4})-(\d{4})/;
let phone = "010-1234-5678";
if (phonePattern.test(phone)) {
    let parts = phone.match(phonePattern);
    console.log("区号:", parts[1]); // "010"
    console.log("前四位:", parts[2]); // "1234"
    console.log("后四位:", parts[3]); // "5678"
}

捕获组的特点:

  1. 编号顺序:从左到右,按左括号的顺序编号
  2. 从1开始matches[1] 是第一个捕获组,matches[0] 是整个匹配
  3. 可选捕获组:如果捕获组没有匹配到内容,对应位置是 undefined
let optionalPattern = /hello( world)?/;
console.log("hello".match(optionalPattern)[1]); // undefined
console.log("hello world".match(optionalPattern)[1]); // " world"

接着回到上文,如果模式设置了全局标记,则每次调用 exec()方法会返回一个匹配的信息。如果没有设置全局标

记,则无论对同一个字符串调用多少次 exec(),也只会返回第一个匹配的信息。

let text = "cat, bat, sat, fat"; 
let pattern = /.at/; 
let matches = pattern.exec(text); 
console.log(matches.index); // 0 
console.log(matches[0]); // cat 
console.log(pattern.lastIndex); // 0 
matches = pattern.exec(text); 
console.log(matches.index); // 0 
console.log(matches[0]); // cat 
console.log(pattern.lastIndex); // 0

上面例子中的模式没有设置全局标记,因此调用 exec()只返回第一个匹配项(“cat”)。lastIndex 在非全局模式下始终不变。

如果在这个模式上设置了 g 标记,则每次调用 exec()都会在字符串中向前搜索下一个匹配项,如下面的例子所示:

let text = "cat, bat, sat, fat"; 
let pattern = /.at/g; 
let matches = pattern.exec(text); 
console.log(matches.index); // 0 
console.log(matches[0]); // cat 
console.log(pattern.lastIndex); // 3 
matches = pattern.exec(text); 
console.log(matches.index); // 5 
console.log(matches[0]); // bat 
console.log(pattern.lastIndex); // 8 
matches = pattern.exec(text); 
console.log(matches.index); // 10 
console.log(matches[0]); // sat 
console.log(pattern.lastIndex); // 13

这次模式设置了全局标记,因此每次调用 exec()都会返回字符串中的下一个匹配项,直到搜索到字符串末尾。注意模式的 lastIndex 属性每次都会变化。在全局匹配模式下,每次调用 exec()都会更新 lastIndex 值,以反映上次匹配的最后一个字符的索引

如果模式设置了粘附标记 y,则每次调用 exec()就只会在 lastIndex 的位置上寻找匹配项。粘附标记覆盖全局标记

let text = "cat, bat, sat, fat"; 
let pattern = /.at/y; 
let matches = pattern.exec(text); 
console.log(matches.index); // 0 
console.log(matches[0]); // cat 
console.log(pattern.lastIndex); // 3 
// 以索引 3 对应的字符开头找不到匹配项,因此 exec()返回 null 
// exec()没找到匹配项,于是将 lastIndex 设置为 0 
matches = pattern.exec(text); 
console.log(matches); // null 
console.log(pattern.lastIndex); // 0 
// 向前设置 lastIndex 可以让粘附的模式通过 exec()找到下一个匹配项:
pattern.lastIndex = 5; 
matches = pattern.exec(text); 
console.log(matches.index); // 5 
console.log(matches[0]); // bat 
console.log(pattern.lastIndex); // 8

简单的说,粘附标记(y)具有以下特点:

  1. 必须从lastIndex位置开始匹配
  2. 必须立即在lastIndex位置找到匹配
  3. 覆盖全局标记(g)的行为

即,想象你在看书:

  • g标记:从头到尾扫描整本书找关键词
  • y标记:只看当前手指指着的那个位置有没有关键词,没有就放弃(返回 null)

正则表达式的另一个方法是 test(),接收一个字符串参数。如果输入的文本与模式匹配,则参数返回 true,否则返回 false。这个方法适用于只想测试模式是否匹配,而不需要实际匹配内容的情况。test()经常用在 if 语句中:

let text = "000-00-0000"; 
let pattern = /\d{3}-\d{2}-\d{4}/; 
if (pattern.test(text)) { 
   console.log("The pattern was matched."); 
}

在这个例子中,正则表达式用于测试特定的数值序列。如果输入的文本与模式匹配,则显示匹配成功的消息。这个用法常用于验证用户输入,此时我们只在乎输入是否有效,不关心为什么无效。

无论正则表达式是怎么创建的,继承的方法 toLocaleString()toString()都返回正则表达式的字面量表示。比如:

let pattern = new RegExp("\\[bc\\]at", "gi"); 
console.log(pattern.toString()); // /\[bc\]at/gi 
console.log(pattern.toLocaleString()); // /\[bc\]at/gi

这里的模式是通过 RegExp 构造函数创建的,但 toLocaleString()toString()返回的都是其字面量的形式

Note: 正则表达式的 valueOf()方法返回正则表达式本身。

4.2.3 RegExp 构造函数属性

RegExp 构造函数本身也有几个属性。(在其他语言中,这种属性被称为静态属性。)这些属性适用于作用域中的所有正则表达式,而且会根据最后执行的正则表达式操作而变化。这些属性还有一个特点,就是可以通过两种不同的方式访问它们。换句话说,每个属性都有一个全名和一个简写。下表列出了 RegExp 构造函数的属性。

全名简写说明
input$_最后搜索的字符串(非标准特性)
lastMatch$&最后匹配的文本
lastParen$+最后匹配的捕获组(非标准特性)
leftContext$`input 字符串中出现在 lastMatch 前面的文本
rightContext$’input 字符串中出现在 lastMatch 后面的文本

通过这些属性可以提取出与 exec()test()执行的操作相关的信息。来看下面的例子:

let text = "this has been a short summer"; 
let pattern = /(.)hort/g; 
if (pattern.test(text)) { 
   console.log(RegExp.input); // this has been a short summer 
   console.log(RegExp.leftContext); // this has been a 
   console.log(RegExp.rightContext); // summer 
   console.log(RegExp.lastMatch); // short 
   console.log(RegExp.lastParen); // s 
}

以上代码创建了一个模式,用于搜索任何后跟"hort"的字符,并把第一个字符放在了捕获组中。不同属性包含的内容如下。

  • input 属性中包含原始的字符串。
  • leftConext 属性包含原始字符串中"short"之前的内容,rightContext 属性包含"short"之后的内容。
  • lastMatch 属性包含匹配整个正则表达式的上一个字符串,即"short"。
  • lastParen 属性包含捕获组的上一次匹配,即"s"。

这些属性名也可以替换成简写形式,只不过要使用中括号语法来访问,如下面的例子所示,因为大多数简写形式都不是合法的 ECMAScript 标识符:

let text = "this has been a short summer"; 
let pattern = /(.)hort/g; 
/* 
 * 注意:Opera 不支持简写属性名
 * IE 不支持多行匹配
 */ 
if (pattern.test(text)) { 
   console.log(RegExp.$_); // this has been a short summer 
   console.log(RegExp["$`"]); // this has been a 
   console.log(RegExp["$'"]); // summer 
   console.log(RegExp["$&"]); // short 
   console.log(RegExp["$+"]); // s 
}

RegExp 还有其他几个构造函数属性,可以存储最多 9 个捕获组的匹配项这些属性通过 RegExp. $1~RegExp.$9 来访问,分别包含第 1~9 个捕获组的匹配项。在调用 exec()test()时,这些属性就会被填充,然后就可以像下面这样使用它们:

let text = "this has been a short summer"; 
let pattern = /(..)or(.)/g; 
if (pattern.test(text)) { 
   console.log(RegExp.$1); // sh 
   console.log(RegExp.$2); // t 
}

在这个例子中,模式包含两个捕获组。调用 test()搜索字符串之后,因为找到了匹配项所以返回 true,而且可以打印出通过 RegExp 构造函数的$1 和$2 属性取得的两个捕获组匹配的内容。

Note: RegExp 构造函数的所有属性都没有任何 Web 标准出处,因此不要在生产环境中使用它们。

4.2.4 模式局限

虽然 ECMAScript 对正则表达式的支持有了长足的进步,但仍然缺少 Perl 语言中的一些高级特性。下列特性目前还没有得到 ECMAScript 的支持(想要了解更多信息,可以参考 Regular-Expressions.info网站):

  • \A 和\Z 锚(分别匹配字符串的开始和末尾)
  • 联合及交叉类
  • 原子组
  • x(忽略空格)匹配模式
  • 条件式匹配
  • 正则表达式注释

虽然还有这些局限,但 ECMAScript 的正则表达式已经非常强大,可以用于大多数模式匹配任务。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值