基本引用类型
引用值(或者对象)是某个特定引用类型的实例。在 ECMAScript 中,引用类型是把数据和功能组织到一起的结构,经常被人错误地称作“类”。虽然从技术上讲 JavaScript 是一门面向对象语言,但 ECMAScript 缺少传统的面向对象编程语言所具备的某些基本结构,包括类和接口。引用类型有时候也被称为对象定义,因为它们描述了自己的对象应有的属性和方法。
对象被认为是某个特定引用类型的实例。新对象通过使用 new 操作符后跟一个构造函数(constructor)来创建。构造函数就是用来创建新对象的函数。
let obj = new Object();
let now = new Date();
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();
// 2024-06-20T06:52:36.522Z
在不给 Date
构造函数传参数的情况下,创建的对象将保存当前日期和时间。要基于其他日期和时间创建日期对象,必须传入其毫秒表示(UNIX 纪元 1970 年 1 月 1 日午夜之后的毫秒数)。
// 传入 0 毫秒,则意味着 1970 年 1 月 1 日 午夜
console.log(new Date(0)); // 1970-01-01T00:00:00.000Z
// 一天后:1000 * 60 * 60 * 24
console.log(new Date(1000 * 60 * 60 * 24)); // 1970-01-02T00:00:00.000Z
ECMAScript 为此提供了两个辅助方法将日期转换成毫秒:Date.parse()
和 Date.UTC()
。
Date.parse()
Date.parse()
方法是 JavaScript 中用于解析日期字符串并返回时间戳(自1970年1月1日00:00:00 UTC以来的毫秒数)的方法。该方法支持多种日期字符串格式,但最常见和可靠的格式是 ISO 8601 格式。下面是一些 Date.parse()
支持的常见日期字符串格式:
ISO 8601 格式:
- 日期和时间:
YYYY-MM-DDTHH:mm:ss.sssZ
. - 仅日期:
YYYY-MM-DD
. - 日期和时间(无时间部分的 T 分隔符)::
YYYY-MM-DDTHH:mm:ss
. - 日期和时间(无秒和毫秒):
YYYY-MM-DDTHH:mm
.
RFC 2822 / IETF 日期格式
常用于电子邮件和 HTTP 标头: Day, DD Mon YYYY HH:mm:ss GMT
自然语言格(部分浏览器支持):
MM/DD/YYYY
或MM-DD-YYYY
Month DD, YYYY
Day Month DD YYYY
console.log(Date.parse('2024-06-20T12:34:56.789Z')); // ISO 8601
console.log(Date.parse('2024-06-20')); // ISO 8601 (only date)
console.log(Date.parse('Wed, 20 Jun 2024 12:34:56 GMT')); // RFC 2822
console.log(Date.parse('06/20/2024')); // MM/DD/YYYY
console.log(Date.parse('June 20, 2024')); // Month DD, YYYY
为了确保跨浏览器和跨平台的一致性,推荐使用 ISO 8601 格式来解析日期字符串。虽然 Date.parse()
支持多种格式,但非标准格式的解析行为可能会有所不同,导致潜在的错误和不一致性。因此,使用标准化格式是最佳实践。
如果传给 Date.parse()
的字符串并不表示日期,则该方法会返回NaN。如果直接把表示日期的字符串传给 Date
构造函数,那么 Date
会在后台调用 Date.parse()
。
console.log(Date.parse('soemday')); // NaN
console.log(new Date('2024-06-20')); // 2024-06-20T00:00:00.000Z
Date.UTC()
Date.UTC()
是 JavaScript 的一个静态方法,用于生成一个 UTC 时间的时间戳。该方法不需要创建 Date 对象,而是直接返回一个表示指定时间的 UTC 时间戳。
// year:必需。一个表示年份的整数。
// month:必需。一个表示月份的整数,从 0(1 月)到 11(12 月)。
// day:可选。一个表示日期的整数,从 1 到 31。
// hour:可选。一个表示小时的整数,从 0 到 23。
// minutes:可选。一个表示分钟的整数,从 0 到 59。
// seconds:可选。一个表示秒的整数,从 0 到 59。
// milliseconds:可选。一个表示毫秒的整数,从 0 到 999。
Date.UTC(year, month, day, hour, minutes, seconds, milliseconds);
let timestamp = Date.UTC(1970, 0, 1, 0, 0, 0);
console.log(timestamp); // 输出: 0
与 Date.parse()
一样,Date.UTC()
也会被 Date
构造函数隐式调用,但有一个区别:这种情况 下创建的是本地日期,不是 GMT 日期。
Date
构造函数跟 Date.UTC()
接收的参数是一样的。因此,如果第一个参数是数值,则构造函数假设它是日期中的年,第二个参数就是月,以此类推。
// 本地时间2000年1月1日零点
let y2k = new Date(2000, 0);
// 本地时间2005年5月5日下午5点55分55秒s
let allFives = new Date(2005, 4, 5, 17, 55, 55);
Date.now()
Date.now()
是 JavaScript 中的一个静态方法,用于返回当前时间的时间戳。
let currentTimestamp = Date.now();
// 示例 1: 获取当前时间的时间戳
let currentTimestamp = Date.now();
console.log('Current Timestamp:', currentTimestamp); // Current Timestamp: 1718868440654
// 示例 2: 计算运行时间
let start = Date.now();
// 模拟一些耗时操作
for (let i = 0; i < 1000000; i++) {}
let end = Date.now();
console.log('Operation took:', end - start, 'milliseconds'); // Operation took: 2 milliseconds
// 示例 3: 与 Date 对象结合使用
let currentDate = new Date(currentTimestamp);
console.log('Current Date:', currentDate.toISOString()); // Current Date: 2024-06-20T07:27:20.654Z
继承的方法
与其他类型一样,Date
类型重写了 toLocaleString()
、toString()
和 valueOf()
方法。但与其他类型不同,重写后这些方法的返回值不一 样。Date
类型的 toLocaleString()
方法返回与浏览器运行的本地环境一致的日期和时间。这通常意味着格式中包含针对时间的 AM(上午)或 PM(下午), 但不包含时区信息(具体格式可能因浏览器而不同)。toString()
方法通常返回带时区信息的日期和时间,而时间也是以 24 小时制(0~23)表示的。Date
类型的 valueOf()
方法根本就不返回字符串,这个方法被重写后返回的是时间戳。
let now = new Date();
console.log(now.toLocaleString()); // 6/20/2024, 3:31:52 PM
console.log(now.toString()); // Thu Jun 20 2024 15:31:52 GMT+0800 (China Standard Time)
console.log(now.valueOf()); // 1718868712068
日期格式化的方法
Date
类型有几个专门用于格式化日期的方法,它们都会返回字符串:
-
toDateString()
显示日期中的周几、月、日、年(格式特定于实现); -
toTimeString()
显示日期中的时、分、秒和时区(格式特定于实现); -
toLocaleDateString()
显示日期中的周几、月、日、年(格式特定于实现和地区); -
toLocaleTimeString()
显示日期中的时、分、秒(格式特定于实现和地区); -
toUTCString()
显示完整的 UTC 日期(格式特定于实现)。
这些方法的输出与 toLocaleString()
和 toString()
一样,会因浏览器而异。因此不能用于在用户界面上一致地显示日期。
日期/时间组件方法
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,进入夏令时的地区可能有所差异) |
RegExp
ECMAScript 通过 RegExp
类型支持正则表达式。正则表达式使用类似 Perl 的简洁语法来创建:
// i: 匹配模式
let expression = /pattern/i;
这个正则表达式的 pattern(模式) 可以是任何简单或复杂的正则表达式,包括字符类、限定符、 分组、向前查找和反向引用。每个正则表达式可以带零个或多个 flags(标记),用于控制正则表达式的行为。
下面给出了表示匹配模式的标记:
-
g
: 全局模式,表示查找字符串的全部内容,而不是找到第一个匹配的内容就结束。 -
i
: 不区分大小 写,表示在查找匹配时忽略 pattern 和字符串的大小写。 -
m
: 多行模式,表示查找到一行文本末尾时会继续查找。 -
y
: 粘附模式,表示只查找从 lastIndex 开始及之后的字符串。 -
u
: Unicode 模式,启用 Unicode 匹配。 -
s
: dotAll 模式,表示元字符.匹配任何字符(包括\n 或\r)。
RegExp实例属性
RegExp实例方法
RegExp构造函数属性
模式局限
原始值包装类型
为了方便操作原始值,ECMAScript 提供了3种特殊的引用类型: Boolean
、Number
和 String
。 这些 类型具有本章介绍的其他引用类型一样的特点,但也具有与各自原始类型对应的特殊行为。
在 JavaScript 中,虽然字符串是原始值,但是在访问字符串原始值时,JavaScript 引擎会临时创建一个相应的 String 对象实例,以便访问该字符串的方法。这种行为被称为 "装箱"(boxing)或者 "包装"(wrapping)。
let s1 = 'some text';
let s2 = s1.substring(2);
// (1)创建一个 String 类型的实例
let s11 = new String('some text');
// (2) 调用实例上的特定方法;
let s22 = s11.substring(2);
// (3) 销毁实例
s11 = null;
这种行为可以让原始值拥有对象的行为。对布尔值和数值而言,以上 3 步也会在后台发生,只不过 使用的是 Boolean
和 Number
包装类型而已。
引用类型与原始值包装类型的主要区别在于对象的生命周期。在通过 new 实例化引用类型后,得到的实例会在离开作用域时被销毁,而自动创建的原始值包装对象则只存在于访问它的那行代码执行期间。这意味着不能在运行时给原始值添加属性和方法。
let s1 = 'some text';
s1.color = 'red';
console.log(s1.color); // undefined
这里的第二行代码尝试给字符串 s1 添加了一个 color 属性。可是,第三行代码访问 color 属性时,它却不见了。原因就是第二行代码运行时会临时创建一个 String 对象,而当第三行代码执行时,这个对象已经被销毁了。实际上,第三行代码在这里创建了 自己的 String 对象,但这个对象没有 color 属性。
可以显式地使用 Boolean
、Number
和 String
构造函数创建原始值包装对象。不过应该在确实必要时再这么做,否则容易让开发者疑惑,分不清它们到底是原始值还是引用值。在原始值包装类型的实例上调用 typeof
会返回"object",所有原始值包装对象都会转换为布尔值 true
。
另外,Object 构造函数作为一个工厂方法,能够根据传入值的类型返回相应原始值包装类型的实例。比如:
let bol = new Object(true);
let num = new Object(18);
let str = new Object('some text');
console.log(bol instanceof Boolean); // true
console.log(num instanceof Number); // true
console.log(str instanceof String); // true
虽然不推荐显式创建原始值包装类型的实例,但它们对于操作原始值的功能是很重要的。每个原始值包装类型都有相应的一套方法来方便数据操作。
Boolean
Boolean
是对应布尔值的引用类型。要创建一个 Boolean
对象,就使用 Boolean
构造函数并 传入 true 或 false,如下例所示:
let booleanObject = new Boolean(true);
Boolean
的实例会重写 valueOf()
方法,返回一个原始值 true 或 false。toString()
方法被调用时也会被覆盖,返回字符串"true"或"false"。不过,Boolean 对象在 ECMAScript 中用得很少。 不仅如此,它们还容易引起误会,尤其是在布尔表达式中使用 Boolean
对象时,比如:
let falseObject = new Boolean(false);
let result = falseObject && true;
console.log(result); // true
let falseValue = false;
result = falseValue && true;
console.log(result); // false
上面代码看起来会造成一些误解,所有对象在布尔表达式中都会自动转换为 true,因此 falseObject 在这个表达式里实际上表示一个 true 值。
除此之外,原始值和引用值(Boolean
对象)还有几个区别。首先,typeof
操作符对原始值返回 "boolean",但对引用值返回"object"。同样,Boolean
对象是 Boolean
类型的实例,在使用 instaceof
操作符时返回 true,但对原始值则返回 false,
console.log(typeof falseObject); // object
console.log(typeof falseValue); // boolean
console.log(falseObject instanceof Boolean); // true
console.log(falseValue instanceof Boolean); // false
理解原始布尔值和 Boolean
对象之间的区别非常重要,强烈建议永远不要使用后者。
Number
Number
是对应数值的引用类型。要创建一个 Number
对象,就使用 Number
构造函数并传入一个数值。
let numberObject = new Number(10);
与 Boolean
类型一样,Number
类型重写了 valueOf()
、toLocaleString()
和 toString()
方法。valueOf()
方法返回 Number
对象表示的原始数值,另外两个方法返回数值字符串。toString()
方法可选地接收一个表示基数的参数,并返回相应基数形式的数值字符串。
let num = 10;
console.log(num.toString()); // "10"
console.log(num.toString(2)); // "1010"
console.log(num.toString(8)); // "12"
console.log(num.toString(10)); // "10"
console.log(num.toString(16)); // "a"
除了继承的方法,Number
类型还提供了几个用于将数值格式化为字符串的方法。
toFixed()
toFixed()
方法返回包含指定小数点位数的数值字符串。如果小数点不够自动补零,若有多余的小数点进行四舍五入。
// digits: 可选的整数参数,指定小数点后要显示的位数。这个参数的取值范围是从 0 到 20。如果省略,则默认为 0。
number.toFixed(digits);
let num = 123.456789;
let formatted = num.toFixed(2);
console.log(formatted); // Output: "123.46"
toFixed()
在需要显示货币等固定小数位数的情况下非常有用,能够提供清晰和精确的数字格式化。
toExponential()
toExponential()
是 JavaScript 中用于将数字转换为指数表示法(科学计数法)的方法。它可以将一个数字表示为一个以 "e" 为指数的字符串,其中 "e" 表示 "×10^"。这种格式通常用于处理极大或极小的数字,以便更紧凑地表示它们。
// digits: 可选参数,表示小数点后要显示的位数。这个参数的取值范围是从 0 到 20。如果省略,则使用尽可能多的数字来表示指数部分。
number.toExponential(digits);
let num = 12345.6789;
let exponential = num.toExponential();
console.log(exponential); // 输出: "1.23456789e+4"
exponential = num.toExponential(2);
console.log(exponential); // 输出: "1.23e+4"
// 负数的指数
let num2 = 0.000012345;
exponential = num2.toExponential();
console.log(exponential); // 输出: "1.2345e-5"
// 整数的指数
let num3 = 123;
exponential = num3.toExponential();
console.log(exponential); // 输出: "1.23e+2"
toPrecision()
toPrecision(precision)
是 JavaScript 中用于格式化数字的方法之一,它可以根据指定 的有效位数将数字转换为字符串表示。
precision
: 必需的参数,表示要显示的有效位数(总位数)。这个参数的取值范围是从 1 到 21。如果指定的位数不足以表示整数部分和小数部分,则用零填充。如果省略,则行为与 toString()
相同,返回完整精度的字符串表示。
let num = 12345.6789;
let precision = num.toPrecision(6);
console.log(precision); // 输出: "12345.7"
// 不足位数时的填充 0
console.log(num.toPrecision(10)); // 12345.67890
toPrecision()
方法在需要对数字进行特定精度的显示时很有用,能够根据需求在科学计算、工程计算或其他需要精确数值表示的场景中提供适当的格式化功 能。
isInteger() 方法与安全整数
ES6 新增了 Number.isInteger()
方法,用于辨别一个数值是否保存为整数。有时候,小数位的 0 可能会让人误以为数值是一个浮点值:
console.log(Number.isInteger(1)); // true
console.log(Number.isInteger(1.0)); // true
console.log(Number.isInteger(1.01)); // false
IEEE 754 数值格式有一个特殊的数值范围,在这个范围内二进制值可以表示一个整数值。这个数值 范围从 Number.MIN_SAFE_INTEGER
()到 Number.MAX_SAFE_INTEGER
()。对超出这个范围的数值,即使尝试保存为整数,IEEE 754 编码格式也意味着二进制值可能会表示一个完全不同的数值。为了鉴别整数是否在这个范围内,可以使用 Number.isSafeInteger()
方法:
console.log(Number.isSafeInteger(-1 * 2 ** 53)); // false
console.log(Number.isSafeInteger(-1 * 2 ** 53 + 1)); // true
console.log(Number.isSafeInteger(2 ** 53)); // false
console.log(Number.isSafeInteger(2 ** 53 - 1)); // trues
String
String
是对应字符串的引用类型。要创建一个 String
对象,使用 String
构造函数并传入一个数值,如下例所示:
let str = new String('hello world');
String 对象的 3 个继承方法:valueOf()
、toLocaleString()
和 toString()
都返回对象的原始字符串值。
每个 String 对象都有一个 length 属性,表示字符串中字符的数量。来看下面的例子:
let str = 'hello world';
console.log(str.length); // "11"
JavaScript 字符
JavaScript 字符串由 16 位码元(code unit)组成。对多数字符来说,每16位码元对应一个字符。换 句话说,字符串的 length 属性表示字符串包含多少16位码元。
charAt()
方法返回给定索引位置的字符,由传给方法的整数参数指定。
let str = 'hello world';
console.log(str.charAt(2)); // l
JavaScript 字符串使用了两种 Unicode 编码混合的策略:UCS-2 和 UTF-16。对于可以采用 16 位编码 的字符(U+0000~U+FFFF),这两种编码实际上是一样的。
使用 charCodeAt()
方法可以查看指定码元的字符编码。这个方法返回指定索引位置的码元值,索引以整数指定。
let message = 'abcde';
// Unicode "Latin small letter C"的编码是 U+0063
console.log(message.charCodeAt(2)); // 99
// 十进制99等于十六进制63
console.log(99 === 0x63); // true
fromCharCode()
方法用于根据给定的 UTF-16 码元创建字符串中的字符。这个方法可以接受任意多个数值,并返回将所有数值对应的字符拼接起来的字符串:
console.log(String.fromCharCode(0x63)); // "c"
对于 U+0000~U+FFFF 范围内的字符,length
、charAt()
、charCodeAt()
和 fromCharCode()
返回的结果都跟预期是一样的。这是因为在这个范围内,每个字符都是用 16 位表示的,而这几个方法 也都基于 16 位码元完成操作。只要字符编码大小与码元大小一一对应,这些方法就能如期工作。
待补充