第五章 基本引用类型(上)——Date、RegExp

发布时间:2024年01月15日

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

????????引用类型虽然有点像类,但跟类并不是一个概念。

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

let now = new Date();

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

注意 ????????函数也是一种引用类型,但有关函数的内容太多了,一章放不下,后面会详细介绍

5.1 Date

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

????????要创建日期对象,就使用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");

注意 ????????不同的浏览器对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,以此类推)、日(1~31)、时(0~23)、分、秒和毫秒。这些参数中,只有前两个(年和月)是必需的。如果不提供日,那么默认为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);

????????以上代码创建了与前面例子中相同的两个日期,但这次的两个日期是(由于系统设置决定的)本地时区的日期。
????????ECMAScript还提供了Date.now()方法,返回表示方法执行时日期和时间的毫秒数。这个方法可以方便地用在代码分析中:

// 起始时间
let start = Date.now();

// 调用函数
doSomething();

// 结束时间
let stop = Date.now(),
result = stop - start;

5.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。这也是确保日期先后的一个简单方式。

5.1.2 日期格式化方法

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

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

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

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

5.1.3 日期/时间组件方法

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

5.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构造函数创建时对应的模式字符串。

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

5.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。数组的第一个元素是匹配的整个字符串,第二个元素是匹配第一个捕获组的字符串,第三个元素是匹配第二个捕获组的字符串。

????????如果模式设置了全局标记,则每次调用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

????????正则表达式的另一个方法是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()返回的都是其字面量的形式。

注意 ????????正则表达式的valueOf()方法返回正则表达式本身。

5.2.3 RegExp构造函数属性

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

????????通过这些属性可以提取出与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属性取得的两个捕获组匹配的内容。??????

文章来源:https://blog.csdn.net/weixin_52878347/article/details/135598351
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。