基本引用类型
引用值(或者对象)是某个特定引用类型的实例。在 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 位码元完成操作。只要字符编码大小与码元大小一一对应,这些方法就能如期工作。
待补充
normalize() 方法
字符串操作方法
concat()
方法用于将一个或多个字符串拼接成一个新字符串。
let stringValue = 'hello ';
let result = stringValue.concat('world', '!');
console.log(result); // "hello world!"
console.log(stringValue); // "hello"
ECMAScript 提供了 3 个从字符串中提取子字符串的方法: slice()
、substr()
和 substring()
。这3个方法都返回调用它们的字符串的一个子字符串,而且都接收一或两个参数。第一个参数表示子字符串开始的位置,第二个参数表示子字符串结束的位置。对 slice()
和 substring()
而言,第二个参数是提取结束的位置(即该位置之前的字符会被提取出来)。对 substr()
而言,第二个参数表示返回的子字符串数量。任何情况下,省略第二个参数都意味着提取到字符串末尾。与 concat()
方法一样,不会修改调用它们的字符串,而只会返回提取到的原始新字符串值。
let stringValue = 'hello world';
console.log(stringValue.slice(3)); // lo world
console.log(stringValue.substring(3)); //lo world
console.log(stringValue.slice(3, 7)); // lo w
console.log(stringValue.substring(3, 7)); // lo w
console.log(stringValue.substr(3)); //lo world
console.log(stringValue.substr(3, 7)); // lo worl
slice()
和 substring()
都是 JavaScript 中用于提取字符串子串的方法,但它们在参数处理和行为上有一些细微的区别。
slice()
参数可以是负值。如果参数是负值,则表示从字符串末尾开始计算位置。
substring()
参数不能是负值。如果参数是负值,则会被当作 0。
let str = 'Hello, World!';
console.log(str.slice(-3, 5)); // 输出: ""
console.log(str.substring(-3, 5)); // 输出: "Hello" (负值当作0)
字符串位置方法
有两个方法用于在字符串中定位子字符串: indexOf()
和 lastIndexOf()
。这两个方法从字符串中搜索传入的字符串 ,并返回位置(如果没找到,则返回-1)。两者的区别在于,indexOf()
方法从字符串开头开始查找子字符串,而 lastIndexOf()
方法从字符串末尾开始查找子字符串。
let stringValue = 'hello world';
console.log(stringValue.indexOf('o')); // 4
console.log(stringValue.lastIndexOf('o')); // 7
这两个方法都可以接收可选的第二个参数,表示开始搜索的位置。这意味着,indexOf()
会从这个 参数指定的位置开始向字符串末尾搜索,忽略该位置之前的字符; lastIndexOf()
则会从这个参数指定的位置开始向字符串开头搜索,忽略该位置之后直到字符串末尾的字符。
let stringValue = 'hello world';
console.log(stringValue.indexOf('o', 6)); // 7
console.log(stringValue.lastIndexOf('o', 6)); // 4
字符串包含方法
ECMAScript 6 增加了 3 个用于判断字符串中是否包含另一个字符串的方法: startsWith()
、 endsWith()
和 includes()
。
startsWith()
方法用来判断当前字符串是否以另一个子字符串开头,根据结果返回 true 或 false。
// searchString:必需。要搜索的子字符串。
// position:可选。开始搜索的位置。默认为 0。
str.startsWith(searchString, position);
let str = 'Hello, World!';
console.log(str.startsWith('Hello')); // 输出: true
console.log(str.startsWith('World')); // 输出: false
console.log(str.startsWith('World', 7)); // 输出: true
endsWith()
方法用来判断当前字符串是否以另一个子字符串结尾,根据结果返回 true 或 false。
// searchString:必需。要搜索的子字符串。
// length:可选。在字符串的前 length 个字符中搜索。默认为字符串的长度。
str.endsWith(searchString, length);
let str = 'Hello, World!';
console.log(str.endsWith('World!')); // 输出: true
console.log(str.endsWith('Hello')); // 输出: false
console.log(str.endsWith('Hello', 5)); // 输出: true
includes()
是 JavaScript 中用于检查一个字符串是否包含另一个子字符串的方法,返回一个布尔值(true 或 false),表示是否找到了指定的子字符串。
// searchString:必需。要搜索的子字符串。
// position:可选。开始搜索的位置。默认为 0。
str.includes(searchString, position);
let str = 'Hello, World!';
// 区分大小写
console.log(str.includes('World')); // 输出: true
console.log(str.includes('world')); // 输出: false
console.log(str.includes('World', 7)); // 输出: true
trim() 方法
ECMAScript 在所有字符串上都提供了 trim()
方法。这个方法会创建字符串的一个副本,删除前、 后所有空格符,再返回结果。
let stringValue = ' hello world ';
let trimmedStringValue = stringValue.trim();
console.log(stringValue); // " hello world "
console.log(trimmedStringValue); // "hello world"
另外两个方法 trimLeft()
和 trimRight()
方法分别用于从字符串开始和末尾清理空格符。
repeat() 方法
ECMAScript 在所有字符串上都提供了 repeat()
方法。这个方法接收一个整数参数,表示要将字符串复制多少次,然后返回拼接所有副本后的结果。
let stringValue = 'na ';
console.log(stringValue.repeat(16) + 'batman');
// na na na na na na na na na na na na na na na na batman
padStart() 和 padEnd() 方法
padStart()
和 padEnd()
方法会复制字符串,如果小于指定长度,则在相应一边填充字符,直至满足长度条件。这两个方法的第一个参数是长度,第二个参数是可选的填充字符串,默认为空格 (U+0020)。
let stringValue = 'foo';
console.log(stringValue.padStart(6)); // " foo"
console.log(stringValue.padStart(9, '.')); // "......foo"
console.log(stringValue.padEnd(6)); // "foo "
console.log(stringValue.padEnd(9, '.')); // "foo......"
可选的第二个参数并不限于一个字符。如果提供了多个字符的字符串,则会将其拼接并截断以匹配 指定长度。此外,如果长度小于或等于字符串长度,则会返回原始字符串。
let stringValue = 'foo';
console.log(stringValue.padStart(8, 'bar')); // "barbafoo"
console.log(stringValue.padStart(2)); // "foo"
console.log(stringValue.padEnd(8, 'bar')); // "foobarba"
console.log(stringValue.padEnd(2)); // "foo"
字符串迭代与解构
字符串的原型上暴露了一个@@iterator 方法,表示可以迭代字符串的每个字符。
let message = 'abc';
let stringIterator = message[Symbol.iterator]();
console.log(stringIterator.next()); // {value: "a", done: false}
console.log(stringIterator.next()); // {value: "b", done: false}
console.log(stringIterator.next()); // {value: "c", done: false}
console.log(stringIterator.next()); // {value: undefined, done: true}
在 for-of 循环中可以通过这个迭代器按序访问每个字符:
for (const c of 'abcde') {
console.log(c);
}
// a
// b
// c
// d
// e
let message = 'abcde';
console.log([...message]); // ["a", "b", "c", "d", "e"]
字符串大小写转换
在 JavaScript 中,可以使用内置的字符串方法来进行大小写转换。常用的方法有 toUpperCase()
和 toLowerCase()
,分别用于将字符串转换为大写和小写。此外,还有 toLocaleUpperCase()
和 toLocaleLowerCase()
,用于根据特定区域设置进行大小写转换。
let str = 'Hello, World!';
let upperStr = str.toUpperCase();
console.log(upperStr); // 输出: "HELLO, WORLD!"
let str = 'Hello, World!';
let lowerStr = str.toLowerCase();
console.log(lowerStr); // 输出: "hello, world!"
toLocaleLowerCase()
和 toLocaleUpperCase()
方法旨在基于特定地区实现。在很多地区,地区特定的方法与通用的方法是一样的。但在少数语言中(如土耳其语), Unicode 大小写转换需应用特殊规则,要使用地区特定的方法才能实现正确转换。
let str = 'iğğüş';
console.log(str.toUpperCase()); // 输出: "IĞĞÜŞ" (默认英语区域设置)
console.log(str.toLocaleUpperCase('tr')); // 输出: "IĞĞÜŞ" (土耳其语区域设置,结果相同)
let str = 'IĞĞÜŞ';
console.log(str.toLowerCase()); // 输出: "ığğüş" (默认英语区域设置)
console.log(str.toLocaleLowerCase('tr')); // 输出: "ığğüş" (土耳其语区域设置,结果相同)
字符串模式匹配方法
String 类型专门为在字符串中实现模式匹配设计了几个方法。第一个就是 match()
方法,这个方法本质上跟 RegExp
对象的 exec()
方法相同。match()
方法接收一个参数,可以是一个正则表达式字符串,也可以是一个 RegExp
对象。
localeCompare() 方法
localeCompare()
是 JavaScript 字符串对象的一个方法,用于比较两个字符串,并返回一个表示比较结果的数字。这个方法可以根据特定的区域设置(locale)和比较选项来执行字符串比较。
// compareString:必需。要与参考字符串(referenceStr)进行比较的字符串。
// locales:可选。一个字符串或数组,表示要使用的区域设置。若不指定,使用默认区域设置。
// options:可选。一个对象,包含用于比较的配置选项。
referenceStr.localeCompare(compareString, locales, options);
localeCompare()
返回一个数值,该数值指示参考字符串在排序顺序中的位置是位于比较字符串的前面、后面还是与其相同:
- 负数:
referenceStr
小于compareString
- 0:
referenceStr
等于compareString
- 正数:
referenceStr
大于compareString
let str1 = 'apple';
let str2 = 'banana';
console.log(str1.localeCompare(str2)); // -1,表示 "apple" 小于 "banana"
console.log(str2.localeCompare(str1)); // 1,表示 "banana" 大于 "apple"
console.log(str1.localeCompare('apple')); // 0,表示相等
单例内置对象
ECMA-262 对内置对象的定义是“任何由 ECMAScript 实现提供、与宿主环境无关,并在 ECMAScript 程序开始执行时就存在的对象”。这就意味着,开发者不用显式地实例化内置对象,因为它们已经实例 化好了。前面我们已经接触了大部分内置对象,包括 Object
、Array
和 String
。本节介绍 ECMA-262 定义的另外两个单例内置对象:Global
和 Math
。
Global
Global
对象是 ECMAScript 中最特别的对象,因为代码不会显式地访问它。ECMA-262 规定 Global
对象为一种兜底对象,它所针对的是不属于任何对象的属性和方法。事实上,不存在全局变量或全局函数这种东西。在全局作用域中定义的变量和函数都会变成 Global
对象的属性。前面介绍的函数,包括 isNaN()
、isFinite()
、parseInt()
和 parseFloat()
,实际上都是 Global
对象的方法。除了这些,Global 对象上还有另外一些方法。
URL 编码方法
encodeURI()
和 encodeURIComponent()
方法用于编码统一资源标识符(URI),以便传给浏览器。有效的 URI 不能包含某些字符,比如空格。使用 URI 编码方法来编码 URI 可以让浏览器能够理解它们,同时又以特殊的 UTF-8 编码替换掉所有无效字符。
encodeURI()
方法用于对整个 URL 进行编码,使其成为有效的 URI。它不会编码保留字符,因为这些字符在 URL 中有特殊意义,如 :, /, ?, &, #, = 等。
let url = 'https://www.example.com/path name/?query=test#fragment';
let encodedURL = encodeURI(url);
console.log(encodedURL); // 输出: https://www.example.com/path%20name/?query=test#fragment
encodeURIComponent()
方法用于编码 URL 的单个组件(如查询参数的值或路径片段)。它会对 URL 组件中的所有非字母数字字符进行编码,包括保留字符。
let component = 'name=value&other=测试';
let encodedComponent = encodeURIComponent(component);
console.log(encodedComponent); // 输出: name%3Dvalue%26other%3D%E6%B5%8B%E8%AF%95
有编码方法自然有解码方法。使用 decodeURI()
解码由 encodeURI()
编码的 URL。使用 decodeURIComponent()
解码由 encodeURIComponent()
编码的 URL 组件。
let encodedURL = 'https://www.example.com/path%20name/?query=test#fragment';
let decodedURL = decodeURI(encodedURL);
console.log(decodedURL); // 输出: https://www.example.com/path name/?query=test#fragment
let encodedComponent = 'name%3Dvalue%26other%3D%E6%B5%8B%E8%AF%95';
let decodedComponent = decodeURIComponent(encodedComponent);
console.log(decodedComponent); // 输出: name=value&other=测试
eval() 方法
eval()
是 JavaScript 中的一个内置函数,用于将字符串解析成 JavaScript 代码并执行该代码。虽然 eval()
可以用于动态执行代码,但它也有许多潜在的风险和缺点,因此在实际开发中应尽量避免使用或谨慎使用。
// string:包含 JavaScript 表达式、语句或代码片段的字符串。
eval(string);
当解释器发现 eval()
调用时,会将参数解释为实际的 ECMAScript 语句,然后将其插入到该位置。 通过 eval()
执行的代码属于该调用所在上下文,被执行的代码与该上下文拥有相同的作用域链。这意味着定义在包含上下文中的变量可以在 eval()
调用内部被引用,比如下面这个例子:
let msg = 'hello world';
eval('console.log(msg)'); // "hello world"
类似地,可以在 eval()
内部定义一个函数或变量,然后在外部代码中引用,如下所示:
eval("function sayHi() { console.log('hi'); }");
sayHi();
通过 eval()
定义的任何变量和函数都不会被提升,这是因为在解析代码的时候,它们是被包含在一个字符串中的。它们只是在 eval()
执行的时候才会被创建。
严格模式下,在 eval()
内部创建的变量和函数无法被外部访问。
解释代码字符串的能力是非常强大的,但也非常危险。在使用 eval()
的时候必须极为慎重,特别是在解释用户输入的内容时。因为这个方法会对 XSS 利用暴露出很大的攻击面。恶意用户可能插入会导致你网站或应用崩溃的代码。
Global 对象属性
Global
对象有很多属性,其中一些前面已经提到过了。像 undefined
、NaN
和 Infinity
等特殊值都是 Global
对象的属性。此外,所有原生引用类型构造函数,比如 Object
和 Function
,也都是 Global
对象的属性。下表列出了所有这些属性。
属性 | 说明 |
---|---|
undefined | 特殊值 undefined |
NaN | 特殊值 NaN |
Infinity | 特殊值 Infinity |
Object | Object 的构造函数 |
Array | Array 的构造函数 |
Function | Function 的构造函数 |
Boolean | Boolean 的构造函数 |
String | String 的构造函数 |
Number | Number 的构造函数 |
Date | Date 的构造函数 |
RegExp | RegExp 的构造函数 |
Symbol | Symbol 的构造函数 |
Error | Error 的构造函数 |
EvalError | EvalError 的构造函数 |
RangeError | RangeError 的构造函数 |
ReferenceError | ReferenceError 的构造函数 |
SyntaxError | SyntaxError 的构造函数 |
TypeError | TypeError 的构造函数 |
URIError | URIError 的构造函数 |
window 对象
虽然 ECMA-262 没有规定直接访问 Global
对象的方式,但浏览器将 window
对象实现 为 Global
对象的代理。因此,所有全局作用域中声明的变量和函数都变成了 window
的属性。
Math
ECMAScript 提供了 Math
对象作为保存数学公式、信息和计算的地方。Math
对象提供了一些辅助计算的属性和方法。
Math
对象上提供的计算要比直接在JavaScript实现的快得多,因为 Math
对象上的计算使用了 JavaScript 引擎中更高效的实现和处理器指令。但使用 Math
计算的问题是精度会因浏览器、操作系统、指令集和硬件而异。
Math 对象属性
Math
对象有一些属性,主要用于保存数学中的一些特殊值。
属性 | 说明 |
---|---|
Math.E | 自然对数的基数 e 的值 |
Math.LN10 | 10 为底的自然对数 |
Math.LN2 | 2 为底的自然对数 |
Math.LOG2E | 以 2 为底 e 的对数 |
Math.LOG10E | 以 10 为底 e 的对数 |
Math.PI | π 的值 |
Math.SQRT1_2 | 1/2 的平方根 |
Math.SQRT2 | 2 的平方根 |
min() 和 max() 方法
Math
对象也提供了很多辅助执行简单或复杂数学计算的方法。
min()
和 max()
方法用于确定一组数值中的最小值和最大值。这两个方法都接收任意多个参数。
let max = Math.max(3, 54, 32, 16);
console.log(max); // 54
let min = Math.min(3, 54, 32, 16);
console.log(min); // 3
舍入方法
接下来是用于把小数值舍入为整数的 4 个方法: Math.ceil()
、Math.floor()
、Math.round()
和 Math.fround()
。这几个方法处理舍入的方式如下所述。
-
Math.ceil()
方法始终向上舍入为最接近的整数。 -
Math.floor()
方法始终向下舍入为最接近的整数。 -
Math.round()
方法执行四舍五入。 -
Math.fround()
方法返回数值最接近的单精度(32 位)浮点值表示。
console.log(Math.ceil(25.9)); // 26
console.log(Math.ceil(25.5)); // 26
console.log(Math.ceil(25.1)); // 26
console.log(Math.floor(25.9)); // 25
console.log(Math.floor(25.5)); // 25
console.log(Math.floor(25.1)); // 25
console.log(Math.round(25.9)); // 26
console.log(Math.round(25.5)); // 26
console.log(Math.round(25.1)); // 25
console.log(Math.fround(0.4)); // 0.4000000059604645
console.log(Math.fround(0.5)); // 0.5
console.log(Math.fround(25.9)); // 25.899999618530273
random() 方法
在 JavaScript 中,Math.random()
是一个用于生成随机数的内置函数。它返回一个浮点数,该数大于等于 0 且小于 1(即 0 ≤ Math.random() < 1)。
let randomNumber = Math.random();
console.log(randomNumber); // 输出一个介于 0(包括)和 1(不包括)之间的随机数
// 生成一个介于 0 和 9 之间的随机整数
let randomInteger = Math.floor(Math.random() * 10);
console.log(randomInteger); // 输出一个 0 到 9 之间的整数
其他方法
Math
对象还有很多涉及各种简单或高阶数运算的方法,每种方法都值的去了解。
方法 | 说明 |
---|---|
Math.abs(x) | 返回 x 的绝对值 |
Math.exp(x) | 返回 Math.E 的 x 次幂 |
Math.expm1(x) | 等于 Math.exp(x) - 1 |
Math.log(x) | 返回 x 的自然对数 |
Math.log1p(x) | 等于 1 + Math.log(x) |
Math.pow(x, power) | 返回 x 的 power 次幂 |
Math.hypot(...nums) | 返回 nums 中每个数字平方和的平方根 |
Math.clz32(x) | 返回 32 位整数 x 的前置零的数量 |
Math.sign(x) | 返回表示 x 符号的 1, 0, -0 或 -1 |
Math.trunc(x) | 返回 x 的整数部分,删除所有小数 |
Math.sqrt(x) | 返回 x 的平方根 |
Math.cbrt(x) | 返回 x 的立方根 |
Math.acos(x) | 返回 x 的反余弦 |
Math.acosh(x) | 返回 x 的反双曲余弦 |
Math.asin(x) | 返回 x 的反正弦 |
Math.asinh(x) | 返回 x 的反双曲正弦 |
Math.atan(x) | 返回 x 的反正切 |
Math.atanh(x) | 返回 x 的反双曲正切 |
Math.atan2(y, x) | 返回 y/x 的反正切 |
Math.cos(x) | 返回 x 的余弦 |
Math.sin(x) | 返回 x 的正弦 |
Math.tan(x) | 返回 x 的正切 |
即便这些方法都是由 ECMA-262 定义的,对正弦、余弦、正切等计算的实现仍然取决于浏览器,因为计算这些值的方式有很多种。结果,这些方法的精度可能因实现而异。