文章目录
- 第五章 基本引用类型
- 5.1 Date
- 5.2 RegExp
- 5.3 原始值包装类型
- 5.3.1 Boolean
- 5.3.2 Number
- 5.3.3 String
- (1) JavaScript 字符:`length`属性 `charAt()`方法 `charCodeAt()`方法 `codePointAt()`方法 `fromCharCode()` 方法 `fromCodePoint()` 方法
- (2) ==normalize()方法 规范化字符格式==
- (3) 字符串操作方法 concat() 、slice()、substr()和 substring()
- (4) 字符串位置方法 `indexOf(searchvalue, start)`和 `lastIndexOf(searchvalue, start)`
- (5) 字符串包含方法 `startsWith()`、`endsWith()`、`includes()`
- (6) `trim()`方法, 去除字符串两边的空白
- (7) `repeat()`方法 复制字符串指定次数,并将它们连接在一起返回
- (8) `padStart()`和 `padEnd()`方法
- (9) 字符串迭代与解构 [Symbol.iterator] 、...
- (10) 字符串大小写转换 toLowerCase()、toLocaleLowerCase()、toUpperCase()和toLocaleUpperCase()
- (11) 字符串模式匹配方法 match() search() replace() split()
- (12) `localeCompare()` 方法 比较两个字符串
- 5.4 单例内置对象
- 5.5 小结
第五章 基本引用类型
引用值(或者对象) 是某个特定引用类型的实例。
在ECMAScript中,引用类型是把数据和功能组织到一起的结构,经常被人错误地称作“类”。
虽然从技术上讲 JavaScript 是一门面向对象语言,但ECMAScript 缺少传统的面向对象编程语言所具备的某些基本结构,包括类和接口。
引用类型有时候也被称为对象定义,因为它们描述了自己的对象应有的属性和方法。
对象被认为是某个特定引用类型的实例。新对象通过使用new操作符后面跟一个构造函数来创建。构造函数就是用来创建新对象的函数。
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();
1、在不给 Date
构造函数传参数的情况下,创建的对象将保存当前日期和时间。
2、要基于其他日期和时间创建日期对象,必须传入其毫秒表示(UNIX 纪元 1970 年 1 月 1 日午夜之后的毫秒数)。ECMAScript为此提供了两个辅助方法:Date.parse()
和 Date.UTC()
。
(1) Date.parse()
方法 传参表示日期的字符串参数
Date.parse()
方法接收一个表示日期的字符串参数,尝试将这个字符串转换为表示该日期的毫秒数。
如果传给 Date.parse()
的字符串并不表示日期,则该方法会返回 NaN
。
如果直接把表示日期的字符串传给 Date
构造函数,那么 Date
会隐式调用 Date.parse()
。
(2) Date.UTC()
方法 创建UTC日期 (被Date
构造函数隐式调用时创建本地时间)
Date.UTC()
方法也返回日期的毫秒表示,Date.UTC()
的参数是年、零起点月数(1 月是 0,2 月是 1,以此类推)、日(1-31)、时(0~23)、分、秒和毫秒。这些参数中,只有前两个(年和月)是必需的。如果不提供日,那么默认为 1 日。其他参数的默认值都是 0。
使用Date.UTC()
方法创建的是GMT日期, 但浏览器在展示时会 +8 小时转换为北京时间。
与 Date.parse()
一样,Date.UTC()
也会被 Date
构造函数隐式调用,但有一个区别:Date.UTC()
被 Date
构造函数隐式调用时创建的是本地日期,不是 GMT 日期。不过 Date
构造函数跟 Date.UTC()
接收的参数是一样的。因此,如果第一个参数是数值,则构造函数假设它是日期中的年,第二个参数就是月,以此类推。
例:使用Date.UTC()
方法创建的是GMT日期, 浏览器console
展示时会 +8 小时转换为北京时间。
北京时间 = GMT时间 + 8小时。
GMT指的是格林威治中央区时,北京位于东八区,也就是北京的地方时比中央时区的地方时早8小时。GMT - 4:00表示比中央区时晚4小时,也就是说这个地方在中央时区以西,西四区。
UTC + 时区差 = 本地时间
举个列来说明:当GMT为下午17点,那么北京时间就应该为当日 17 + 8 = 25 时(第二天凌晨3点)
UTC(GMT)参考链接
例:Date.UTC()
被 Date
构造函数隐式调用时创建的是本地日期,不是 GMT 日期。
以下代码创建了与前面例子中相同的两个日期,但这次的两个日期是(由于系统设置决定的)本地时区的日期。
3、 Date.now()
方法
ECMAScript 还提供了 Date.now()
方法,返回表示方法执行时日期和时间的毫秒数。
返回一个 Number
,表示自 UNIX 纪元开始(1970 年 1 月 1 日 00:00:00 (UTC))到当前时间的毫秒数。
这个方法可以方便地用在代码分析中:
// 起始时间
let start = Date.now();
// 调用函数
doSomething();
// 结束时间
let stop = Date.now(),
// 时间差
result = stop - start;
5.1.1 继承的方法
与其他类型一样,Date
类型重写了 toLocaleString()、toString()和 valueOf()
方法。但与其他类型不同,重写后这些方法的返回值不一样。
Date
类型的 toLocaleString()
方法返回与浏览器运行的本地环境一致的日期和时间。这通常意味着格式中包含针对时间的 AM(上午)或 PM(下午),但不包含时区信息(具体格式可能因浏览器而不同)。
Date
类型的 toString()
方法通常返回带时区信息的日期和时间,而时间也是以 24 小时制(0~23)表示的。
现代浏览器在这两个方法的输出上已经趋于一致。在比较老的浏览器上,每个方法返回的结果可能在每个浏览器上都是不同的。这些差异意味着 toLocaleString()和 toString()可能只对调试有用,不能用于显示。
Date
类型的 valueOf()
方法根本就不返回字符串,这个方法被重写后返回的是日期的毫秒表示。因此,操作符(如小于号和大于号)可以直接使用它返回的值。
5.1.2 日期格式化方法
5.1.3 日期/时间组件方法
5.2 RegExp
参考 菜鸟教程https://www.runoob.com/jsref/jsref-obj-regexp.html
ECMAScript 通过 RegExp
(regular expression)类型支持正则表达式。
正则表达式可以通过字面量形式定义,或者使用RegExp构造函数来构建。
(1) 使用字面量形式来定义正则表达式
正则表达式使用类似 Perl 的简洁语法来创建:
let expression = /pattern/flags;
pattern(模式)可以是任何简单或复杂的正则表达式,包括字符类、限定符、分组、向前查找和反向引用。
flags(标记, 也称为修饰符,正则表达式的标记用于指定额外的匹配策略), 每个正则表达式可以带零个或多个 flags(标记),用于控制正则表达式的行为。
下面给出了表示匹配模式的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"结尾的三字符组合,忽略大小写(.点号表示"at"前面的任意字符都可以匹配).
let pattern3 = /.at/gi;
与其他语言中的正则表达式类似,所有元字符在模式中也必须转义,包括以下元字符:
( [ { \ ^ $ | ) ] } ? * + .
元字符在正则表达式中都有一种或多种特殊功能,所以要匹配上面这些字符本身,就必须使用反斜杠\
来转义。
// 匹配第一个"bat"或"cat",忽略大小写
let pattern1 = /[bc]at/i;
// 匹配第一个"[bc]at",忽略大小写(左右中括号都必须使用反斜杠转义)
let pattern2 = /\[bc\]at/i;
// 匹配所有以"at"结尾的三字符组合,忽略大小写(.点号表示"at"前面的任意字符都可以匹配)
let pattern3 = /.at/gi;
// 匹配所有".at",忽略大小写(对点号进行转义)
let pattern4 = /\.at/gi;
(2) 使用RegExp
构造函数来创建正则表达式
RegExp 构造函数创建正则表达式时,接收两个参数:模式字符串pattern 和(可选的)标记字符串flags。
任何使用字面量定义的正则表达式也可以通过构造函数来创建。
// 匹配第一个"bat"或"cat",忽略大小写
let pattern1 = /[bc]at/i;
// 跟 pattern1 一样,只不过是用构造函数创建的
let pattern2 = new RegExp("[bc]at", "i");
注意,RegExp
构造函数的两个参数都是字符串。因为 RegExp
的模式参数是字符串,所以在某些情况下需要二次转义。所有元字符都必须二次转义,包括转义字符序列,如\n
(\
转义后的字符串是\\
,在正则表达式字符串中则要写成\\\\
)。
下表展示了几个正则表达式的字面量形式,以及使用 RegExp 构造函数创建时对应的模式字符串。
此外,使用 RegExp 也可以基于已有的正则表达式实例,并可选择性地修改它们的标记flags:
const re1 = /cat/g;
console.log(re1); // "/cat/g"
const re2 = new RegExp(re1);
console.log(re2); // "/cat/g"
// 标记从 g 修改为 i
const re3 = new RegExp(re1, "i");
console.log(re3); // "/cat/i"
5.2.1 RegExp实例属性
每个 RegExp 实例都有下列属性,提供有关模式的各方面信息。
global:布尔值,表示是否设置了 g 标记 (全局匹配, 表示查找字符串的全部内容, 而不是找到第一个匹配的内容就结束)。
ignoreCase:布尔值,表示是否设置了 i 标记 (不区分大小写, 表示在查找匹配时忽略pattern和字符串的大小写)。
unicode:布尔值,表示是否设置了 u 标记 (Unicode模式, 启用Unicode匹配)。
sticky:布尔值,表示是否设置了 y 标记 (粘附模式, 表示只查找从lastIndex开始及之后的字符串)。
lastIndex:整数,表示在源字符串中下一次搜索的开始位置,始终从 0 开始。
multiline:布尔值,表示是否设置了 m 标记 (多行模式, 表示查找到一行文本末尾时会继续查找)。
dotAll:布尔值,表示是否设置了 s 标记 (dotAll模式, 表示元字符 .
匹配任何字符, 包括 \n
或 \r)
。
source:正则表达式的字面量字符串(不是传给构造函数的模式字符串),没有开头和结尾的斜杠。
flags:正则表达式的标记字符串。始终以字面量而非传入构造函数的字符串模式形式返回(没有前后斜杠)。
通过这些属性可以全面了解正则表达式的信息,不过实际开发中用得并不多,因为模式声明中包含这些信息。
例: 查看RegExp
实例属性
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()
和 test()
, 继承的 toLocalString()
和 toString()
valueOf()
1- exec()
用于检索字符串中的正则表达式的匹配
(1) RegExp
实例的主要方法是 exec(string)
,主要用于配合捕获组使用。
这个方法只接收一个参数,即要应用pattern
模式的字符串。
如果找到了匹配项,则返回包含第一个匹配信息的数组;
如果没找到匹配项,则返回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" 与表达式中的捕获组匹配的字符串
(2) 区分模式pattern
是否设置全局标记g
g
标记(全局匹配, 表示查找字符串的全部内容, 而不是找到第一个匹配的内容就结束)
若没有设置全局标记g
, 则无论对同一个字符串调用多少次exec()
, 也只会返回第一个匹配的信息.
如果设置了全局标记g
, 则每次调用 exec()
方法会在字符串中向前搜索下一个匹配项.
例: 非全局, 调用 exec()
只返回第一个匹配项("cat")
。lastIndex
在非全局模式下始终不变
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
例: 全局标记g
, 每次调用 exec()
都会返回字符串中的下一个匹配项,直到搜索到字符串末尾。注意模式的 lastIndex
属性每次都会变化。在全局匹配模式下,每次调用 exec()
都会更新 lastIndex
值,以反映上次匹配的最后一个字符的索引.
let text = "cat, bat, sat, fat";
let pattern = /.at/g;
let matches = pattern.exec(text); // 第一次调用
console.log(matches.index); // 0 匹配的字符从下标0开始
console.log(matches[0]); // cat
console.log(pattern.lastIndex); // 3 这次匹配的最后一个字符是下标3的,
matches = pattern.exec(text); // 第二次调用
console.log(matches.index); // 5 匹配的字符从下标5开始
console.log(matches[0]); // bat
console.log(pattern.lastIndex); // 8 这次匹配的最后一个字符是下标8的,
matches = pattern.exec(text); // 第三次调用
console.log(matches.index); // 10 匹配的字符从下标10开始
console.log(matches[0]); // sat
console.log(pattern.lastIndex); // 13 这次匹配的最后一个字符是下标13的,
(3) 区分模式pattern
是否设置粘附标记y
y
标记 (粘附模式, 表示只查找从lastIndex开始及之后的字符串)
如果模式设置了粘附标记 y
,则每次调用 exec()
就只会在 lastIndex
的位置上寻找匹配项。
粘附标记y
覆盖全局标记g
。
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 对应的字符开头(, bat, sat, fat)找不到匹配项,因此 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
2- test()
用于测试特定的数值序列
test()
接收一个字符串参数。如果输入的文本与模式匹配,则参数返回 true
,否则返回 false
。
这个方法适用于只想测试模式是否匹配,而不需要实际匹配内容的情况,经常用在 if
语句中,常用于验证用户输入,只在乎输入是否有效但不关心为什么无效。
3- toLocalString()
和 toString()
无论正则表达式是怎么创建的,继承的方法 toLocaleString()
和 toString()
都返回正则表达式的字面量表示。
例:模式pattern
是通过 RegExp
构造函数创建的,但 toLocaleString()
和 toString()
返回的都是其字面量的形式
let pattern = new RegExp("\\[bc\\]at", "gi");
console.log(pattern.toString()); // /\[bc\]at/gi
console.log(pattern.toLocaleString()); // /\[bc\]at/gi
4- valueOf()
正则表达式的 valueOf()
方法返回正则表达式本身
5.2.3 RegExp 构造函数属性(不建议在生产环境中使用)
RegExp
构造函数本身也有几个属性(在其他语言中,这种属性被称为静态属性)。
这些属性适用于作用域中的所有正则表达式,而且会根据最后执行的正则表达式操作而变化。
这些属性都有一个全名和一个简写, 可以通过两种不同的方式访问它们。
通过这些属性可以提取出与 exec()
和 test()
执行的操作相关的信息。
例1: RegExp 构造函数属性的全名使用
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.leftContext.length); // 16 注意包括空格
console.log(RegExp.rightContext); // summer
console.log(RegExp.rightContext.length); // 7 注意包括空格
console.log(RegExp.lastMatch); // short
console.log(RegExp.lastParen); // s
}
以上代码创建了一个模式,用于搜索任何后跟"hort"的字符,并把第一个字符放在了捕获组(.)
中。
不同属性包含的内容如下。
input
属性中包含原始的字符串。
leftConext
属性包含原始字符串中"short"之前的内容,rightContext
属性包含"short"之后的内容。
lastMatch
属性包含匹配整个正则表达式的上一个字符串,“short”。
lastParen
属性包含捕获组的上一次匹配,即"s"。
例2: RegExp 构造函数属性的简写使用
因为大多数简写形式都不是合法的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
属性取得的两个捕获组匹配的内容。
5.2.4 模式局限
5.3 原始值包装类型
1- 为了方便操作原始值,ECMAScript 提供了 3 种特殊的引用类型:Boolean
、Number
和 String
。
这些类型具有本章介绍的其他引用类型一样的特点,但也具有与各自原始类型对应的特殊行为。每当用到某个原始值的方法或属性时,后台都会创建一个相应原始包装类型的对象,从而暴露出操作原始值的各种方法。
let s1 = "some text";
let s2 = s1.substring(2); // "me text"
上例中s1 是一个包含字符串的变量,它是一个原始值。第二行紧接着在 s1 上调用了 substring()
方法,并把结果保存在 s2 中。
然而原始值本身不是对象,因此逻辑上不应该有方法。而实际上这个例子又确实按照预期运行了。这是因为后台进行了很多处理,从而实现了上述操作。具体来说,当第二行访问 s1 时,是以读模式访问的,也就是要从内存中读取变量保存的值。
在以读模式访问字符串值的任何时候,后台都会执行以下 3 步:
(1) 创建一个 String
类型的实例;
(2) 调用实例上的特定方法;
(3) 销毁实例。
这种行为可以让原始值拥有对象的行为。
可以把这 3 步想象成执行了如下 3 行 ECMAScript 代码:
let s1 = new String("some text");
let s2 = s1.substring(2); // 得到s2值 "me text"
s1 = null;
对布尔值和数值而言,以上 3 步也会在后台发生,只不过使用的是 Boolean
和 Number
包装类型而已。
引用类型与原始值包装类型的主要区别在于对象的生命周期。
在通过 new 实例化引用类型后,得到的实例会在离开作用域时被销毁,而自动创建的原始值包装对象则只存在于访问它的那行代码执行期间。这意味着不能在运行时给原始值添加属性和方法。
上例的第二行代码尝试给字符串 s1 添加了一个 color 属性。可是,第三行代码访问 color 属性时,它却不见了。原因就是第二行代码运行时会临时创建一个 String
对象,而当第三行代码执行时,这个对象已经被销毁了。实际上,第三行代码在这里创建了自己的 String
对象,但这个对象没有 color 属性。
可以显式地使用Boolean
、Number
和String
构造函数创建原始值包装对象(不过应该在确实必要时再这么做,否则容易让开发者疑惑,分不清它们到底是原始值还是引用值)。
在原始值包装类型的实例上调用 typeof
会返回"object",所有原始值包装对象在布尔转换中都会转换为布尔值 true
, (Boolean()
转型函数对所有的object
都返回 true
)。
回顾提到的前面的知识点
2- 另外,Object
构造函数作为一个工厂方法,能够根据传入值的类型返回相应原始值包装类型的实例。
如果传给 Object
的是字符串,则会创建一个 String
的实例。如果是数值,则会创建 Number
的实例。布尔值则会得到 Boolean
的实例。
let obj = new Object("some text");
console.log(obj instanceof String); // true
let obj2 = new Object(true);
console.log(obj2 instanceof Boolean); // true
let obj3 = new Object(12);
console.log(obj3 instanceof Number); // true
注意, 使用new
调用原始值包装类型的构造函数,与调用同名的转型函数并不相同。
例: 变量 number 中保存的是一个值为 25 的Number类型的原始数值,而变量 obj 中保存的是一个Number
的实例。
let value = "25";
let number = Number(value); // 转型函数
console.log(typeof number); // "number"
let obj = new Number(value); // 构造函数
console.log(typeof obj); // "object"
虽然不推荐显式创建原始值包装类型的实例,但它们对于操作原始值的功能是很重要的。每个原始值包装类型都有相应的一套方法来方便数据操作。
5.3.1 Boolean
1- 目标: 理解原始布尔值和 Boolean
对象之间的区别,强烈建议永远不要使用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 对象在布尔表达式中自动转换为true
let falseValue = false;
result = falseValue && true;
console.log(result); // false
上例将创建一个值为
false
的Boolean
对象。然后,在一个布尔表达式中通过&&
操作将这个对象与一个原始值true
组合起来。在布尔算术中,false && true
等于false
。可是,这个表达式是对falseObject
对象而不是对它表示的值(false)
求值。前面说过,所有对象在布尔表达式中都会自动转换为true
,因此falseObject
在这个表达式里实际上表示一个true
值。那么true && true
当然是true
。
2- 除此之外,原始值和引用值(Boolean
对象)还有几个区别。
首先,typeof
操作符对原始值返回"boolean"
,但对引用值返回"object"
。
同样,Boolean
对象是 Boolean
类型的实例,在使用instaceof
操作符时返回 true
,但对原始值则返回 false
(用 instanceof
检测原始值,则始终会返回 false
,因为原始值不是对象)
let falseObject = new Boolean(false);
let falseValue = false;
console.log(typeof falseObject); // object
console.log(typeof falseValue); // boolean
console.log(falseObject instanceof Boolean); // true
console.log(falseValue instanceof Boolean); // false
回顾提到的前面的知识点
5.3.2 Number
1- 与 Boolean
对象类似,Number
对象也为数值提供了重要能力。但是,考虑到两者存在同样的潜在问题,因此并不建议直接实例化 Number
对象。
Number
是对应数值的引用类型。要创建一个 Number
对象,就使用 Number
构造函数并传入一个数值,如
let numberObject = new Number(10);
与 Boolean
类型一样,Number
类型重写了 valueOf()
、toLocaleString()
和 toString()
方法。
valueOf()
方法返回 Number
对象表示的原始数值,toLocaleString()
和 toString()
方法返回数值字符串。toString()
方法可选地接收一个表示基数的参数,并返回相应基数形式的数值字符串。
let num = 10;
console.log(num.toString()); // "10" 默认10进制
console.log(num.toString(2)); // "1010" 2进制
console.log(num.toString(8)); // "12" 8进制
console.log(num.toString(10)); // "10" 10进制
console.log(num.toString(16)); // "a" 16进制
2- 除了继承的方法,Number
类型还提供了几个用于将数值格式化为字符串的方法: toFixed()
toExponential()
toPrecision()
为了以正确的小数位精确表示数值,这 3 个方法都会向上或向下舍入。
(1) toFixed()
方法, 接受一个参数用于表示结果中小数的位数, 返回包含指定小数点位数的数值字符串, 自动补0或者四舍五入
let num = 10;
console.log(num.toFixed(2)); // "10.00"
这里的 toFixed()
方法接收了参数 2,表示返回的数值字符串要包含两位小数。结果返回值为"10.00",小数位填充了 0。如果数值本身的小数位超过了参数指定的位数,则四舍五入到最接近的小数位:
let num = 10.005;
console.log(num.toFixed(2)); // "10.01"
toFixed()
自动舍入的特点可以用于处理货币。不过要注意的是,多个浮点数值的数学计算不一定得到精确的结果。比如,0.1 + 0.2 = 0.30000000000000004。
(2) toExponential()
方法, 接受一个参数用于表示结果中小数的位数, 返回以科学计数法表示的数值字符串.
let num = 10;
console.log(num.toExponential(1)); // "1.0e+1"
(3) toPrecision()
方法, 接受一个参数用于表示结果中数字的总位数, 根据情况返回最合理的输出结果, 可能是固定长度, 也可能是科学计数法形式.
本质上,toPrecision()
方法会根据数值和精度来决定调用 toFixed()
还是 toExponential()
。
例:
let num = 99;
console.log(num.toPrecision(1)); // "1e+2"
console.log(num.toPrecision(2)); // "99"
console.log(num.toPrecision(3)); // "99.0"
首先要用 1 位数字表示数值 99,得到"1e+2",也就是 100。因为 99 不能只用 1 位数字来精确表示,所以这个方法就将它舍入为 100,这样就可以只用 1 位数字(及其科学记数法形式)来表示了。用 2 位数字表示 99 得到"99",用 3 位数字则是"99.0"。
3- 除此之外,原始值和引用值(Number
对象)的几个区别:
原始数值在调用 typeof
时始终返回"number"
,而 Number 对象则返回"object"
。
Number
对象是 Number
类型的实例,而原始数值不是。
let numberObject = new Number(10);
let numberValue = 10;
console.log(typeof numberObject); // "object"
console.log(typeof numberValue); // "number"
console.log(numberObject instanceof Number); // true
console.log(numberValue instanceof Number); // false
4- isInteger()
方法与安全整数
ES6 新增了 Number.isInteger()
方法,用于辨别一个数值是否保存为整数。
有时候,小数位的 0 可能会让人误以为数值是一个浮点值。
console.log(Number.isInteger(1)); // true
console.log(Number.isInteger(1.00)); // true
console.log(Number.isInteger(1.01)); // false
回顾前面知识点 3.4.5 Number类型
为了鉴别整数是否在这个范围内,可以使用 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)); // true
5.3.3 String
String
是对应字符串的引用类型。要创建一个 String
对象,使用 String
构造函数并传入一个数值,如下例所示:
let stringObject = new String("hello world");
String
对象的方法可以在所有字符串原始值上调用。3个继承的方法 valueOf()、toLocaleString()
和 toString()
都返回对象的原始字符串值。
每个 String
对象都有一个 length
属性,表示字符串中字符的数量。
注意,即使字符串中包含双字节字符(而不是单字节的 ASCII 字符),也仍然会按单字符来计数。
String
类型提供了很多方法来解析和操作字符串。
方法 | 描述 |
---|---|
charAt() | 返回指定索引位置的字符 |
charCodeAt() | 返回指定索引位置字符的 Unicode 值 |
fromCharCode() | 接受任意多个Unicode 数值,并返回将所有Unicode 数值对应的字符拼接起来的字符串 |
codePointAt() | 接收指定的索引并返回该索引位置上的码点(code point) |
fromCodePoint() | 接收任意数量的码点,返回对应字符拼接起来的字符串. |
concat() | 连接两个或多个字符串,返回连接后的字符串 |
slice() | 提取字符串的片断,并在新的字符串中返回被提取的部分 |
substr() | 从起始索引号提取字符串中指定数目的字符 |
substring() | 提取字符串中两个指定的索引号之间的字符 |
indexOf() | 返回字符串中检索指定字符第一次出现的位置 |
lastIndexOf() | 返回字符串中检索指定字符最后一次出现的位置 |
includes() | 查找字符串中是否包含指定的子字符串 |
startsWith() | 查看字符串是否以指定的子字符串开头 |
endsWith() | 查看字符串是否以指定的子字符串结尾 |
trim() | 移除字符串首尾空白 |
repeat() | 复制字符串指定次数,并将它们连接在一起返回 |
padStart(), padEnd() | 复制字符串,如果小于指定长度,则在相应一边填充字符,直至满足长度条件 |
toLowerCase() | 把字符串转换为小写 |
toUpperCase() | 把字符串转换为大写 |
toLocaleLowerCase() | 根据主机的语言环境把字符串转换为小写,只有几种语言(如土耳其语)具有地方特有的大小写映射 |
toLocaleUpperCase() | 根据主机的语言环境把字符串转换为大写,只有几种语言(如土耳其语)具有地方特有的大小写映射 |
match() | 找到一个或多个正则表达式的匹配 |
replace() | 替换与正则表达式匹配的子串 |
search() | 检索与正则表达式相匹配的值 |
localeCompare() | 用本地特定的顺序来比较两个字符串 |
split() | 把字符串分割为子字符串数组 |
toString() | 返回字符串对象值 |
valueOf() | 返回某个字符串对象的原始值 |
(1) JavaScript 字符:length
属性 charAt()
方法 charCodeAt()
方法 codePointAt()
方法 fromCharCode()
方法 fromCodePoint()
方法
JavaScript 字符串由 16 位码元(code unit)组成。对多数字符来说,每 16 位码元对应一个字符。
即: 字符串的 length
属性表示字符串包含多少 16 位码元:
charAt(index)
方法返回给定索引位置index的字符。具体来说,这个方法查找指定索引位置index的 16 位码元,并返回该码元对应的字符。
let message = "abcde";
console.log(message.charAt(2)); // "c"
charCodeAt(index)
方法可以查看指定码元的字符编码。这个方法返回指定索引位置index的码元值,索引以整数指定。
let message = "abcde";
// Unicode "Latin small letter C"的编码是 U+0063
console.log(message.charCodeAt(2)); // 99
// 十进制 99 等于十六进制 63
console.log(99 === 0x63); // true
- 为正确解析既包含单码元字符又包含代理对字符的字符串,可以使用
codePointAt()
来代替charCodeAt()
。codePointAt()
接收指定的索引并返回该索引位置上的码点(code point)。码点是 Unicode 中一个字符的完整标识。比如,"c"的码点是 0x0063,而"☺"的码点是 0x1F60A。码点可能是 16 位,也可能是 32 位,而codePointAt()
方法可以从指定码元位置识别完整的码点。
fromCharCode()
方法用于根据给定的 UTF-16 码元创建字符串中的字符。这个方法可以接受任意多个数值,并返回将所有数值对应的字符拼接起来的字符串:
// Unicode "Latin small letter A"的编码是 U+0061
// Unicode "Latin small letter B"的编码是 U+0062
// Unicode "Latin small letter C"的编码是 U+0063
// Unicode "Latin small letter D"的编码是 U+0064
// Unicode "Latin small letter E"的编码是 U+0065
console.log(String.fromCharCode(0x61, 0x62, 0x63, 0x64, 0x65)); // "abcde"
// 0x0061 === 97
// 0x0062 === 98
// 0x0063 === 99
// 0x0064 === 100
// 0x0065 === 101
console.log(String.fromCharCode(97, 98, 99, 100, 101)); // "abcde"
- 与
charCodeAt()
有对应的codePointAt()
一样,fromCharCode()
也有一个对应的fromCodePoint()
。这个方法接收任意数量的码点,返回对应字符拼接起来的字符串.
- 这个示例存在疑问, 谷歌火狐等一众浏览器目前的执行结果,都将 笑脸字符识别为单码元字符.
(2) normalize()方法 规范化字符格式
某些 Unicode
字符可以有多种编码方式。有的字符既可以通过一个 BMP
字符表示,也可以通过一个代理对表示。
但比较操作符===
不在乎字符看起来是什么样的,因此下例中的 3 个字符互不相等。
为解决这个问题,Unicode
提供了4种规范化形式,可以将类似上面的字符规范化为一致的格式,无论底层字符的代码是什么。这4种规范化形式是:NFD(Normalization Form D)、NFC(Normalization Form C)、
NFKD(Normalization Form KD)和 NFKC(Normalization Form KC)。可以使用 normalize()
方法对字符串应用上述规范化形式,使用时需要传入表示哪种形式的字符串:"NFD"
、"NFC"
、"NFKD"
或"NFKC"
。
通过比较字符串与其调用 normalize()
的返回值,就可以知道该字符串是否已经规范化了:
let a1 = String.fromCharCode(0x00C5),
a2 = String.fromCharCode(0x212B),
a3 = String.fromCharCode(0x0041, 0x030A);
// U+00C5 是对 0+212B 进行 NFC/NFKC 规范化之后的结果
console.log(a1 === a1.normalize("NFD")); // false
console.log(a1 === a1.normalize("NFC")); // true
console.log(a1 === a1.normalize("NFKD")); // false
console.log(a1 === a1.normalize("NFKC")); // true
// U+212B 是未规范化的
console.log(a2 === a2.normalize("NFD")); // false
console.log(a2 === a2.normalize("NFC")); // false
console.log(a2 === a2.normalize("NFKD")); // false
console.log(a2 === a2.normalize("NFKC")); // false
// U+0041/U+030A 是对 0+212B 进行 NFD/NFKD 规范化之后的结果
console.log(a3 === a3.normalize("NFD")); // true
console.log(a3 === a3.normalize("NFC")); // false
console.log(a3 === a3.normalize("NFKD")); // true
console.log(a3 === a3.normalize("NFKC")); // false
选择同一种 normalize()
规范化形式可以让比较操作符返回正确的结果:
let a1 = String.fromCharCode(0x00C5),
a2 = String.fromCharCode(0x212B),
a3 = String.fromCharCode(0x0041, 0x030A);
console.log(a1.normalize("NFD") === a2.normalize("NFD")); // true
console.log(a2.normalize("NFKC") === a3.normalize("NFKC")); // true
console.log(a1.normalize("NFC") === a3.normalize("NFC")); // true
console.log(a1.normalize("NFKD") === a3.normalize("NFKD")); // true
(3) 字符串操作方法 concat() 、slice()、substr()和 substring()
【1】 字符串拼接方法: concat()
和 +
更常用的方式是使用加号操作符(+
)多数情况下,对于拼接多个字符串来说,使用加号更方便。
let stringValue = "hello ";
let result = stringValue.concat("world");
console.log(result); // "hello world" stringValue 调用 concat()方法的结果是得到"hello world"
console.log(stringValue); // "hello" 但stringValue 的值保持不变。
let stringValue = "hello ";
let result = stringValue.concat("world", "!");
console.log(result); // "hello world!"
console.log(stringValue); // "hello"
【2】从字符串中提取子字符串的方法:slice()
、substr()
和 substring()
slice()
、substr()
和 substring()
不会修改调用它们的字符串,而只会返回提取到的原始新字符串值。
string.slice(start,end)
string.substr(start,length)
string.substring(start, end)
方法会自动将较小的参数作为起点,将较大的参数作为终点。
第一个参数表示字符串开始的位置 start
第二个参数end
表示提取结束的位置(即该位置之前的字符都被提取出来, [start, end) 前闭后开), length
表示返回的子字符串长度
任何情况下,省略第二个参数 end/length
都意味着提取到字符串末尾。
let stringValue = "hello world";
console.log(stringValue.slice(3)); // "lo world"
console.log(stringValue.substring(3)); // "lo world"
console.log(stringValue.substr(3)); // "lo world"
console.log(stringValue.slice(3, 7)); // "lo w"
console.log(stringValue.substring(3,7)); // "lo w"
console.log(stringValue.substr(3, 7)); // "lo worl"
当某个参数是负值时,
slice(start,end)
方法将所有负值参数都当成字符串长度加上负参数值。
substr(start,length)
方法将第一个负参数值当成字符串长度加上该值,将第二个负参数值length转换为 0。
substring(start, end)
方法会将所有负参数值都转换为 0。
let stringValue = "hello world";
console.log(stringValue.length); // 11
console.log(stringValue.slice(-3)); // "rld" 同slice(8)
console.log(stringValue.substring(-3)); // "hello world" 同substring(0)
console.log(stringValue.substr(-3)); // "rld" 同substr(8)
console.log(stringValue.slice(3, -4)); // "lo w" 同 slice(3, 7)
console.log(stringValue.substring(3, -4)); // "hel" 同 substring(3, 0) 同substring(0, 3) 自动把参数小的作为起点,参数大的作为终点.
console.log(stringValue.substr(3, -4)); // "" (empty string) 同substr(3, 0) 即length为0, 返回的字符串包含0个字符, 返回空字符串
(4) 字符串位置方法 indexOf(searchvalue, start)
和 lastIndexOf(searchvalue, start)
用于在字符串中定位子字符串:indexOf(searchvalue, start)
和 lastIndexOf(searchvalue, start)
, 从字符串中搜索传入的字符串,并返回位置(如果没找到,则返回-1)。
indexOf()
方法从字符串开头开始查找子字符串,而 lastIndexOf()
方法从字符串末尾开始查找子字符串。
start
是方法可选的第二个参数,表示开始搜索的位置, 即indexOf()
会从这个参数指定的位置开始向字符串末尾搜索,忽略该位置之前的字符;lastIndexOf()
则会从这个参数指定的位置开始向字符串开头搜索,忽略该位置之后直到字符串末尾的字符。
let stringValue = "hello world";
console.log(stringValue.indexOf("o")); // 4 从前数的第一个o hello的o
console.log(stringValue.lastIndexOf("o")); // 7 从后数的第一个o world的o
let stringValue = "hello world";
console.log(stringValue.indexOf("o", 6)); // 7 从位置 6(字符"w")开始向后搜索字符串,在位置 7 找到了"o"
console.log(stringValue.lastIndexOf("o", 6)); // 4 它从位置 6 开始反向搜索至字符串开头,找到了"hello"中的"o"
使用第二个参数start
并循环调用indexOf()
或 lastIndexOf()
,就可以在字符串中找到所有的目标子字符串.
let stringValue = "Lorem ipsum dolor sit amet, consectetur adipisicing elit";
let positions = new Array();
let pos = stringValue.indexOf("e");
while(pos > -1) {
positions.push(pos);
pos = stringValue.indexOf("e", pos + 1);
}
console.log(positions); // [3,24,32,35,52]
这个例子逐步增大开始搜索的位置,通过 indexOf()
遍历了整个字符串。首先取得第一个"e"的位置,然后进入循环,将上一次的位置加 1 再传给 indexOf()
,确保搜索到最后一个子字符串实例之后。每个位置都保存在 positions 数组中,可供以后使用.
(5) 字符串包含方法 startsWith()
、endsWith()
、includes()
ECMAScript 6 增加了 3 个用于判断字符串中是否包含另一个字符串的方法:startsWith()
、endsWith()
和 includes()
, 从字符串中搜索传入的字符串,并返回一个表示是否包含的布尔值。
startsWith()
检查开始于索引 0 的匹配项
endsWith()
检查开始于索引(string.length - substring.length)
的匹配项
includes()
检查整个字符串
startsWith(searchvalue, start)
和 includes(searchvalue, start)
方法接收可选的第二个参数start,表示开始搜索的位置。如果传入第二个参数,则意味着这两个方法会从指定位置向着字符串末尾搜索,忽略该位置之前的所有字符。
endsWith(searchvalue, end)
方法接收可选的第二个参数,表示应该当作字符串末尾的位置。如果不提供这个参数,那么默认就是字符串长度。
let message = "foobarbaz";
console.log(message.startsWith("foo")); // true
console.log(message.startsWith("foo", 1)); // false
console.log(message.includes("bar")); // true
console.log(message.includes("bar", 4)); // false
let message2 = "foobarbaz";
console.log(message2.endsWith("bar")); // false
console.log(message2.endsWith("bar", 6)); // true
(6) trim()
方法, 去除字符串两边的空白
ECMAScript 在所有字符串上都提供了 trim()
方法。这个方法会创建字符串的一个副本,删除前、后所有空格符,再返回结果。
由于 trim()
返回的是字符串的副本,因此原始字符串不受影响,即原本的前、后空格符都会保留。
另外,trimLeft()
和 trimRight()
方法分别用于从字符串开始和末尾清理空格符.
let stringValue = " hello world ";
let trimmedStringValue = stringValue.trim();
console.log(stringValue); // " hello world "
console.log(trimmedStringValue); // "hello world"
(7) 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
(8) 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" 长度2小于字符串foo长度,返回原始字符串foo
console.log(stringValue.padEnd(8, "bar")); // "foobarba"
console.log(stringValue.padEnd(2)); // "foo"
(9) 字符串迭代与解构 [Symbol.iterator] 、…
字符串的原型上暴露了一个@@iterator
方法,表示可以迭代字符串的每个字符。可以通过[Symbol.iterator]
属性和next()
方法进行手动迭代, 或者for-of
循环等。
可以像下面这样手动使用迭代器:
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"]
(10) 字符串大小写转换 toLowerCase()、toLocaleLowerCase()、toUpperCase()和toLocaleUpperCase()
大小写转换,包括 4 个方法:toLowerCase()、toLocaleLowerCase()、toUpperCase()和toLocaleUpperCase()
。
toLowerCase()
和toUpperCase()
方法是原来就有的方法,与java.lang.String
中的方法同名。
toLocaleLowerCase()
和 toLocaleUpperCase()
方法旨在基于特定地区实现。在很多地区,地区特定的方法与通用的方法是一样的。但在少数语言中(如土耳其语),Unicode 大小写转换需应用特殊规则,要使用地区特定的方法才能实现正确转换。下面是几个例子:
let stringValue = "hello world";
console.log(stringValue.toLocaleUpperCase()); // "HELLO WORLD"
console.log(stringValue.toUpperCase()); // "HELLO WORLD"
console.log(stringValue.toLocaleLowerCase()); // "hello world"
console.log(stringValue.toLowerCase()); // "hello world"
这里,toLowerCase()
和 toLocaleLowerCase()
都返回 hello world,而 toUpperCase()
和toLocaleUpperCase()
都返回 HELLO WORLD。通常,如果不知道代码涉及什么语言,则最好使用地区特定的转换方法。
(11) 字符串模式匹配方法 match() search() replace() split()
String 类型专门为在字符串中实现模式匹配设计了几个方法。
【1】match()
方法
match()
方法本质上跟 RegExp
对象的 exec()
方法相同。
match()
方法接收一个参数,可以是一个正则表达式字符串,也可以是一个RegExp
对象.
match()
方法返回的数组与 RegExp
对象的 exec()
方法返回的数组是一样的:第一个元素是与整个模式匹配的字符串,其余元素则是与表达式中的捕获组匹配的字符串(如果有的话)
区分模式pattern是否设置全局标记g
match()
方法将检索字符串 String Object,以找到一个或多个与参数regexp
匹配的文本。
如果 regexp
没有标志 g
,那么 match()
方法就只能在 String Object 中执行一次匹配。如果没有找到任何匹配的文本, match()
将返回 null
。
否则,它将返回一个数组,存放找到的所有匹配文本。
【2】search()
方法
search()
方法始终从字符串开头向后匹配模式,接受正则表达式字符串或 RegExp 对象的参数,返回模式第一个匹配的位置索引,如果没找到则返回 -1
let text = "cat, bat, sat, fat";
let pos = text.search(/at/);
console.log(pos); // 1
【3】replace() 方法
语法: string.replace(searchvalue,newvalue)
replace()
方法接收两个参数,第一个参数searchvalue
可以是一个 RegExp
对象或一个字符串(这个字符串不会转换为正则表达式),第二个参数newvalue
替换的文本可以是一个字符串或一个函数。
如果第一个参数searchvalue是字符串,那么只会替换第一个子字符串。要想替换所有子字符串,第一个参数必须为正则表达式并且带全局标记g
let text = "cat, bat, sat, fat";
let result = text.replace("at", "ond");
console.log(result); // "cond, bat, sat, fat"
result = text.replace(/at/g, "ond");
console.log(result); // "cond, bond, sond, fond"
replace()
的第二个参数也可以是一个函数。
在只有一个匹配项时,这个函数会收到 3 个参数:与整个模式匹配的字符串匹配项、匹配项在字符串中的开始位置,整个字符串。
在有多个捕获组的情况下,每个匹配捕获组的字符串也会作为参数传给这个函数,后面两个参数还是 ‘与整个模式匹配的开始位置’ 和 ‘原始字符串’。
这个函数返回一个字符串,表示应该把匹配项替换成什么。
使用函数作为replace()
的第二个参数可以更细致地控制替换过程.
例: 函数 htmlEscape() 用于将一段 HTML 中的 4 个字符替换成对应的实体:小于号、大于号、和号,还有双引号(都必须经过转义)。实现这个任务最简单的办法就是用一个正则表达式查找这些字符,然后定义一个函数,根据匹配的每个字符分别返回特定的 HTML 实体.
【4】split()
方法
split()
方法根据传入的分隔符参数将字符串拆分成数组, 作为分隔符的参数可以是字符串,也可以是 RegExp
对象(字符串分隔符不会被这个方法当成正则表达式), 还可以传入第二个参数,即数组大小,确保返回的数组不会超过指定大小。
let colorText = "red,blue,green,yellow";
let colors1 = colorText.split(","); // 基于逗号进行拆分 ["red", "blue", "green", "yellow"]
let colors2 = colorText.split(",", 2); // 限制数组元素为两个 ["red", "blue"]
// 使用正则表达式可以得到一个包含逗号的数组
let colors3 = colorText.split(/[^,]+/); // ["", ",", ",", ",", ""]
正则表达式语法:
^
表示取反,
[^abc] 查找任何不在方括号之间的字符。
(12) localeCompare()
方法 比较两个字符串
localeCompare()
方法比较两个字符串,返回如下 3 个值中的一个。
如果按照字母表顺序,字符串应该排在字符串参数前头,则返回负值(通常是-1,具体还要看与实际值相关的实现)
如果字符串与字符串参数相等,则返回 0。
如果按照字母表顺序,字符串应该排在字符串参数后头,则返回正值(通常是 1,具体还要看与实际值相关的实现)
let stringValue = "yellow";
console.log(stringValue.localeCompare("brick")); // 1
console.log(stringValue.localeCompare("yellow")); // 0
console.log(stringValue.localeCompare("zoo")); // -1
因为返回的具体值可能因具体实现而异,所以最好像下面的示例中一样使用localeCompare()
, 将localeCompare()
的结果与0比较大小以获取字符串的比较结果, 保证在所有实现中都能正确判断字符串顺序:
function determineOrder(value) {
let result = stringValue.localeCompare(value);
if (result < 0) {
console.log(`The string 'yellow' comes before the string '${value}'.`);
} else if (result > 0) {
console.log(`The string 'yellow' comes after the string '${value}'.`);
} else {
console.log(`The string 'yellow' is equal to the string '${value}'.`);
}
}
determineOrder("brick");
determineOrder("yellow");
determineOrder("zoo");
localeCompare()
的独特之处在于,实现所在的地区(国家和语言)决定了这个方法如何比较字符串。在美国,英语是 ECMAScript 实现的标准语言,localeCompare()
区分大小写,大写字母排在小写字母前面。但其他地区未必是这种情况。
5.4 单例内置对象
5.4.1 Global
包括 URL
编码方法和eval()
方法
5.4.2 Math
Math
对象提供了很多辅助执行简单或复杂数学计算的方法
(1) 最值方法 min()
和 max()
方法
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
let values = [1, 2, 3, 4, 5, 6, 7, 8];
// 获取数组中的最大值和最小值,可以使用扩展操作符
let maxvalue = Math.max(...values);
console.log(maxvalue);
(2) 舍入方法 Math.ceil()
、Math.floor()
、Math.round()
和 Math.fround()
。
Math.ceil()
方法始终向上舍入为最接近的整数。 进一法
Math.floor()
方法始终向下舍入为最接近的整数。去尾法
Math.round()
方法执行四舍五入。
Math.fround()
方法返回数值最接近的单精度(32 位)浮点值表示
(3) 随机方法 random()
Math.random()
方法返回一个 0~1 范围内的随机数,其中包含 0 但不包含 1,即前闭后开 [0,1)
可有基于如下公式使用 Math.random()
从一组整数中随机选择一个数:
number = Math.floor(Math.random() * total_number_of_choices + first_possible_value)
从 1~10 范围内随机选择一个数,共十个可能的值,最小值为1
let num = Math.floor(Math.random() * 10 + 1);
从 2~10 范围内随机选择一个数,共九个可能的值 total_number_of_choices 为 9,最小值first_possible_value 为2
let num = Math.floor(Math.random() * 9 + 2);
通过函数来算出可选总数和最小可能的值:
function selectFrom(lowerValue, upperValue) {
let choices = upperValue - lowerValue + 1;
return Math.floor(Math.random() * choices + lowerValue);
}
let num = selectFrom(2,10);
console.log(num); // 2~10 范围内的值,其中包含 2 和 10
let colors = ["red", "green", "blue", "yellow", "black", "purple", "brown"];
let color = colors[selectFrom(0, colors.length-1)];
函数selectFrom()
接收两个参数:应该返回的最小值和最大值。通过将这两个值相减再加 1 得到可选总数,然后再套用上面的公式。使用selectFrom()函数,从一个数组中随机选择一个元素就很容易
注意 Math.random()
方法在这里出于演示目的是没有问题的。如果是为了加密而需要生成随机数(传给生成器的输入需要较高的不确定性),那么建议使用 window.crypto.getRandomValues()
。