这篇文章开始分享第五章的内容了。
第 5 章 基本引用类型
引用值(或者对象)是某个特定引用类型的实例。在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日之前及之后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:00GMT-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);
以上代码创建了与前面例子中相同的两个日期,但这次的两个日期是(由于系统设置决定的)本地时区的日期。(也就是说,我们直接调用Date.UTC(),得到的是GMT日期,但是通过Date(),得到的是本地日期)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
(注意,这里是两个对象在比较大小,所以比较之前调用了valueOf方法)日期2019年1月1日在2019年2月1日之前,所以说前者小于后者没问题。因为2019年1月1日的毫秒表示小于2019年2月1日的毫秒表示,所以用小于号比较这两个日期时会返回 true 。这也是确保日期先后的一个简单方式。