第五章 基本引用类型
本章内容
- 理解对象
- 基本JavaScript 数据类型
- 原始值与原始值包装类型
引用值(或者对象)是某个特定引用类型的实例。
对象被认为是某个特定引用类型的实例。新对象通过使用new 操作符后跟一个构造函数(constructor)来创建。
let now = new Date();
5.1 Date
Date 类型将日期保存为自协调世界时(UTC,Universal Time Coordinated)时间1970 年1 月1 日午夜(零时)至今所经过的毫秒数。
执行结果
let timenow = new Date();
console.log(timenow);
// Thu Aug 05 2021 10:53:50 GMT+0900 (日本标准时间)
- 在不给Date 构造函数传参数的情况下,创建的对象将保存当前日期和时间。
- 基于其他日期和时间创建日期对象,必须传入其毫秒表示(ECMAScript
为此提供了两个辅助方法:Date.parse()
和Date.UTC()
)
Date.parse()方法接收一个表示日期的字符串参数,尝试将这个字符串转换为表示该日期的毫秒数
例
let someDate = new Date(Date.parse("May 23, 2019"));
如果传给Date.parse()的字符串并不表示日期,则该方法会返回NaN。
如果直接把表示日期的字符串传给Date 构造函数,那么Date 会在后台调用Date.parse()。
let someDate = new Date("May 23, 2019");
Date.UTC()方法也返回日期的毫秒表示,但使用的是跟Date.parse()不同的信息来生成这个值。传给Date.UTC()的参数是年、零起点月数(1 月是0,2 月是1,以此类推)、日(1~31)
、时(0~23)、
分、秒和毫秒。
// GMT 时间2000 年1 月1 日零点
let y2k = new Date(Date.UTC(2000, 0));
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)表示的。
timenow.toLocaleString()
"2021/8/5上午10:53:50"
timenow.toString()
"Thu Aug 05 2021 10:53:50 GMT+0900 (日本标准时间)"
Date 类型的valueOf()方法根本就不返回字符串,这个方法被重写后返回的是日期的毫秒表示。
timenow.valueOf()
1628128430777
操作符(如小于号和大于号)可以直接使用它返回的值。比如下面的例子:
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
5.1.2 日期格式化方法
Date 类型有几个专门用于格式化日期的方法,它们都会返回字符串:
- toDateString()显示日期中的周几、月、日、年(格式特定于实现);
- toTimeString()显示日期中的时、分、秒和时区(格式特定于实现);
- toLocaleDateString()显示日期中的周几、月、日、年(格式特定于实现和地区);
- toLocaleTimeString()显示日期中的时、分、秒(格式特定于实现和地区);
- toUTCString()显示完整的UTC 日期(格式特定于实现)。
这些方法的输出与toLocaleString()和toString()一样,会因浏览器而异。因此不能用于在用户界面上一致地显示日期。
5.1.3 日期/时间组件方法
Date 类型剩下的方法直接涉及取得或设置日期值的特定部分,详见P106。
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;
正则表达式的创建:
除了字面量形式外,正则表达式也可以使用RegExp 构造函数来创建,它接收两个参数:模式字符串和(可选的)标记字符串。任何使用字面量定义的正则表达式也可以通过构造函数来创建,比如:
// 匹配第一个"bat"或"cat",忽略大小写
let pattern1 = /[bc]at/i;
// 跟pattern1 一样,只不过是用构造函数创建的
let pattern2 = new RegExp("[bc]at", "i");
这里的pattern1 和pattern2 是等效的正则表达式。
二次转义
因为RegExp 的模式参数是字符串,所有元字符都必须二次转义,包括转义字符序列,如\n(\转义后的字符串是\,在正则表达式字符串中则要写成\\)。
例如字面量模式下的/\.at/
,添加的反斜杠\
表示对.
进行转义,但在字符串模式中又需要添加一个反斜杠对第一次转义用的反斜杠进行二次转义,再去掉字面量模式下起始和终止的两个斜杠,结果为'\\.at'
。
使用RegExp 也可以基于已有的正则表达式实例,并可选择性地修改它们的标记:
const re1 = /cat/g;
console.log(re1); // "/cat/g"
const re2 = new RegExp(re1);
console.log(re2); // "/cat/g"
const re3 = new RegExp(re1, "i");
console.log(re3); // "/cat/i"
5.2.2 RegExp 实例方法
RegExp 实例的主要方法是exec()
,主要用于配合捕获组使用。这个方法只接收一个参数,即要应用模式的字符串。如果找到了匹配项,则返回包含第一个匹配信息的数组;如果没找到匹配项,则返回null
。
返回的数组包含两个额外的属性:index 和input。index 是字符串中匹配的起始位置,input 是要进行匹配的字符串(即exec接收的字符串)。
这个数组的第一个元素是匹配整个模式的字符串,其他元素是与表达式中的捕获组匹配的字符串。如果模式中没有捕获组,则数组只包含一个元素。
这里捕获组就是通过正则表达式中小括号匹配的子表达式,与括号内部匹配到的作为捕获组,添加到返回的数组中。
let text = 'a cat go and a cat come';
l4 = /a\s((cat)\sgo)/g;
l4.exec(text)
// (3) ["a cat go", "cat go", "cat", index: 0, input: "a cat go and a cat come", groups: undefined]
如上例,括号内部匹配的字符串会添加到数组中,index为0表示了从第一个字符开始匹配。
一次只会匹配一个符合条件的,若要匹配多个,则需全局标记g并多次调用。
全局标记g
对于匹配模式中的全局标记g,若没有设置全局标记,则无论对同一个字符串调用多少次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在非全局模式下始终不变。
设置全局标记后,exec()方法会将上次成功匹配后的位置记录在 lastIndex
属性中。使用此特性,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
粘附标记y
如果模式设置了粘附标记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
如上例,第一轮正常运行,第二轮由于y标记的效果只在lastindex即3处匹配,然后失败返回null。
这个粘附的性质覆盖了全局标记自动往后查找下一个匹配字符的能力,将位置固定,不许走。
test()
方法判断匹配
正则表达式的另一个方法是test(),接收一个字符串参数。如果输入的文本与模式匹配,则参数返回true,否则返回false。这个方法适用于只想测试模式是否匹配,而不需要实际匹配内容的情况。test()经常用在if 语句中:
let email = '40299xxxx@qq.com'
let matchEmail = /\w+@\w+\.[com]/;
if (matchEmail.test(email)) {
console.log("The Email address was matched.");
}
// The Email address 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 构造函数本身也有几个属性。(在其他语言中,这种属性被称为静态属性。)这些属性适用于作用域中的所有正则表达式,而且会根据最后执行的正则表达式操作而变化。
每个属性都有一个全名和一个简写,可以通过两种不同的方式访问它们。
属性名可以替换成简写形式,通过使用中括号语法来访问,
如下面的例子所示,因为大多数简写形式都不是合法的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
}
获取子表达式匹配的字符。
5.3 原始值包装类型
为了方便操作原始值,ECMAScript 提供了3 种特殊的引用类型:Boolean、Number 和String。每当用到某个原始值的方法或属性时,后台都会创建一个相应原始包装类型的对象,从而暴露出操作原始值的各种方法。
let s1 = "some text";
let s2 = s1.substring(2);
对于原始值的s1,原始值本身不是对象,因此逻辑上不应该有方法。而实际上
这个例子又确实按照预期运行了。这是因为后台进行了很多处理,从而实现了上述操作。
后台都会执行以下3 步:
(1) 创建一个String 类型的实例;
(2) 调用实例上的特定方法;
(3) 销毁实例。
可以把这3 步想象成执行了如下3 行ECMAScript 代码:
let s1 = new String("some text");
let s2 = s1.substring(2);
s1 = null;
这种行为可以让原始值拥有对象的行为。对布尔值和数值而言,以上3 步也会在后台发生,只不过使用的是Boolean 和Number 包装类型而已。
引用类型与原始值包装类型的主要区别在于对象的生命周期。在通过new 实例化引用类型后,得到的实例会在离开作用域时被销毁,而自动创建的原始值包装对象则只存在于访问它的那行代码执行期间。这意味着不能在运行时给原始值添加属性和方法。
比如下面的例子:
let s1 = "some text";
s1.color = "red";
console.log(s1.color); // undefined
这里的第二行代码尝试给字符串s1 添加了一个color 属性。可是,第三行代码访问color 属性时,它却不见了。原因就是第二行代码运行时会临时创建一个String 对象,而当第三行代码执行时,这个对象已经被销毁了。
实际上,第三行代码在这里创建了自己的String 对象,但这个对象没有color 属性。
即对原始值调用方法或者修改属性时,会临时创建一个对象,执行这句代码(因此程序不会报错),但这个对象会在这句代码结束时自动销毁,之后再次调用方法修改属性等操作又会创建一个新的对象,对先前对象的操作不会保存。
Object
构造函数作为一个工厂方法,能够根据传入值的类型返回相应原始值包装类型的实例。比如:
let obj = new Object("some text");
console.log(obj instanceof String); // true
如果传给Object 的是字符串,则会创建一个String 的实例。如果是数值,则会创建Number 的实例。布尔值则会得到Boolean 的实例。
new调用原始值包装类型的构造函数和调用同名的转型函数效果不一样:
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
Boolean 是对应布尔值的引用类型。要创建一个Boolean 对象,就使用Boolean 构造函数并传入true 或false,如下例所示:
let booleanObject = new Boolean(true);
Boolean 的实例会重写valueOf()方法,返回一个原始值true 或false。toString()方法被调用时也会被覆盖,返回字符串”true”或”false”。
let falseObject = new Boolean(false);
let result = falseObject && true;
console.log(result); // true
let falseValue = false;
result = falseValue && true;
console.log(result); // false
第一个例子中,虽然falseObject的值是false,但进行与操作的是falseObject这个对象而不是值,所有对象在布尔表达式中都会被自动转换为true,因此结果为true。
因此,理解原始布尔值和Boolean 对象之间的区别非常重要,强烈建议永远不要使用后者。
5.3.2 Number
Number 是对应数值的引用类型。要创建一个Number 对象,就使用Number 构造函数并传入一个数值,如下例所示:
let numberObject = new Number(10);
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
toFixed()
方法返回包含指定小数点位数的数值字符串,多余的部分使用四舍五入处理,如:
let num = 10;
console.log(num.toFixed(2)); // "10.00
toExponential()
,返回以科学记数法(也称为指数记数法)表示的数值字符串。
let num = 10;
console.log(num.toExponential(1)); // "1.0e+1"
toPrecision()
方法会根据情况返回最合理的输出结果,可能是固定长度,也可能是科学记数法形式。
isInteger()方法与安全整数
ES6 新增了Number.isInteger()
方法,用于辨别一个数值是否保存为整数。
console.log(Number.isInteger(1)); // true
console.log(Number.isInteger(1.00)); // true
console.log(Number.isInteger(1.01)); // false
原始数值在调用typeof 时始终返回”number”,而Number 对象则返回”object”。类似地,Number对象是Number 类型的实例,而原始数值不是。
IEEE 754 数值格式有一个特殊的数值范围,在这个范围内二进制值可以表示一个整数值。这个数值范围从Number.MIN_SAFE_INTEGER
(-2^53 + 1)到Number.MAX_SAFE_INTEGER
(2^53 – 1).
为了鉴别整数是否在这个范围内,可以使用Number.isSafeInteger()方法:
console.log(Number.isSafeInteger(-1 * (2 ** 53))); // false
console.log(Number.isSafeInteger(-1 * (2 ** 53) + 1)); // true
5.3.3 String
String 是对应字符串的引用类型。要创建一个String 对象,使用String 构造函数并传入一个数值,如下例所示:
let stringObject = new String("hello world");
String 对象的方法可以在所有字符串原始值上调用。3 个继承的方法valueOf()、toLocaleString()和toString()都返回对象的原始字符串值。
每个String 对象都有一个length
属性,表示字符串中字符的数量。
charAt()
方法返回给定索引位置的字符,由传给方法的整数参数指定。具体来说,这个方法查找指定索引位置的16 位码元,并返回该码元对应的字符:
let message = "abcde";
console.log(message.charAt(2)); // "c"
字符串操作方法
concat()
用于将一个或多个字符串拼接成一个新字符串。
let stringValue = "hello ";
let result = stringValue.concat("world");
console.log(result); // "hello world"
console.log(stringValue); // "hello"
原本的字符串值不变。
concat()方法可以接收任意多个参数,因此可以一次性拼接多个字符串,
let stringValue = "hello ";
let result = stringValue.concat("world", "!");
console.log(result); // "hello world!"
更常用的方式是使用加号操作符(+)。而且多数情况下,对于拼接多个字符串来说,使用加号更方便。
ECMAScript 提供了3 个从字符串中提取子字符串的方法:slice()
、substr()
和substring()
。
这3 个方法都返回调用它们的字符串的一个子字符串,而且都接收一或两个参数。第一个参数表示子字符串开始的位置,
对slice()和substring()而言,第二个参数是提取结束的位置(即该位置之前的字符会被提取出来)。对substr()而言,第二个参数表示返回的子字符串数量。
任何情况下,省略第二个参数都意味着提取到字符串末尾。与concat()方法一样,slice()、substr()和substring()也不会修改调用它们的字符串,而只会返回提取到的原始新字符串值。
传递负参数时,
slice()
方法视为倒数,类似于python的负索引;substring()
方法将所有负参数值都转换为0;substr()
方法的第一个参数为负被视为从后向前倒数,第二个参数为负会被转换为0;
字符串位置方法
有两个方法用于在字符串中定位子字符串:indexOf()
和lastIndexOf()
。这两个方法从字符串中搜索传入的字符串,并返回位置(如果没找到,则返回-1)。
两者区别如下所示:
let stringValue = "hello world";
console.log(stringValue.indexOf("o")); // 4
console.log(stringValue.lastIndexOf("o")); // 7
indexOf()方法从字符串开头开始查找子字符串,而lastIndexOf()方法从字符串末尾开始查找子字符串,返回的是分别第一次出现的位置。
这两个方法都可以接收可选的第二个参数,表示开始搜索的位置。这意味着,indexOf()会从这个参数指定的位置开始向字符串末尾搜索,忽略该位置之前的字符;lastIndexOf()则会从这个参数指定的位置开始向字符串开头搜索,忽略该位置之后直到字符串末尾的字符。
在查找的过程中,更新第二个参数,就可以找到所有的匹配字符。
字符串包含方法
ECMAScript 6 增加了3 个用于判断字符串中是否包含另一个字符串的方法:startsWith()
、endsWith()
和includes()
,并返回一个布尔值。
它们的区别在于,
- startsWith()检查开始于索引0 的匹配项,即与开头部分开始逐一进行匹配(第二个参数指定开始位置),如下例,开头是foo所以返回true,不是bar所以返回false;
let message = "foobarbaz";
console.log(message.startsWith("foo")); // true
console.log(message.startsWith("bar")); // false
- endsWith()检查开始于索引
(string.length - substring.length)
的匹配项,第二个参数当作末尾位置,不提供时默认为字符串长度,同理下例中结尾不是bar返回false;
console.log(message.endsWith("baz")); // true
console.log(message.endsWith("bar")); // false
- includes()检查整个字符串,第二个参数指定开始位置,整个字符串出现就返回true,否则返回false。
console.log(message.includes("bar")); // true
console.log(message.includes("qux")); // false
trim()
方法
ECMAScript 在所有字符串上都提供了trim()方法。这个方法会创建字符串的一个副本,删除开头和结尾部分的所有空格符,再返回结果。比如:
let stringValue = " hello world ";
let trimmedStringValue = stringValue.trim();
console.log(stringValue); // " hello world "
console.log(trimmedStringValue); // "hello world"
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)。
字符串迭代与解构
字符串的原型上暴露了一个@@iterator 方法,表示可以迭代字符串的每个字符。
在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"]
字符串大小转换
toLowerCase()
、toLocaleLowerCase()
、toUpperCase()
和toLocaleUpperCase()
toLocaleLowerCase()和toLocaleUpperCase()方法旨在基于特定地区实现。
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"
字符串模式匹配方法
match()
方法接收一个参数,可以是一个正则表达式字符串,也可以是一个RegExp 对象。这个方法本质上跟RegExp 对象的exec()方法相同。
match()方法返回的数组与RegExp 对象的exec()方法返回的数组是一样的:第一个元素是与整个模式匹配的字符串,其余元素则是与表达式中的捕获组匹配的字符串(如果有的话)。
let text = "cat, bat, sat, fat";
let pattern = /.at/;
// 等价于pattern.exec(text)
let matches = text.match(pattern);
console.log(matches.index); // 0
console.log(matches[0]); // "cat"
console.log(pattern.lastIndex); // 0
另一个查找模式的字符串方法是search()
。这个方法唯一的参数与match()方法一样:正则表达式字符串或RegExp 对象。这个方法返回模式第一个匹配的位置索引,如果没找到则返回-1。search()始终从字符串开头向后匹配模式。
let text = "cat, bat, sat, fat";
let pos = text.search(/at/);
console.log(pos); // 1
子字符串替换操作,ECMAScript 提供了replace()
方法。
这个方法接收两个参数,第一个参数可以是一个RegExp 对象或一个字符串(这个字符串不会转换为正则表达式),
第二个参数可以是一个字符串或一个函数。
如果第一个参数是字符串,那么只会替换第一个子字符串。
要想替换所有子字符串,第一个参数必须为正则表达式并且带全局标记,
如下面的例子所示:
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"
第二个参数是字符串的情况下,有几个特殊的字符序列,可以用来插入正则表达式操作的值。
使用这些特殊的序列,可以在替换文本中使用之前匹配的内容,如下面的例子所示:
let text = "cat, bat, sat, fat";
result = text.replace(/(.at)/g, "word ($1)");
console.log(result); // word (cat), word (bat), word (sat), word (fat)
自己的分析
$1表示第一个捕获组的字符串,即第一个子表达式。
replace函数自动多次调用匹配函数,因为匹配函数每次只会返回一次匹配的结果,所以在这里一直使用$1来获取其中捕获的字符串,根据表达式写法每一轮获取的都是捕获的第一个子字符串。
就可以自动获得每一个匹配的子字符串(而不是$1,$2, $3, $4这种写法)。
replace()的第二个参数可以是一个函数。为了与replace函数传递的参数相对应:
- 在只有一个匹配项时,这个函数会收到3 个参数:与整个模式匹配的字符串、匹配项在字符串中的开始位置,以及整个字符串。
- 在有多个捕获组的情况下,每 个匹配捕获组的字符串也会作为参数传给这个函数,但最后两个参数还是与整个模式匹配的开始位置和 原始字符串。
这个函数应该返回一个字符串,表示应该把匹配项替换成什么。
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(/[^,]+/); // ["", ",", ",", ",", ""]
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
5.4 内置对象
5.4.1 Global
ECMA-262 规定Global对象为一种兜底对象,它所针对的是不属于任何对象的属性和方法。事实上,不存在全局变量或全局函数这种东西。
在全局作用域中定义的变量和函数都会变成Global 对象的属性 。
1. URL编码方法
encodeURI()
和encodeURIComponent()
方法用于编码统一资源标识符(URI),以便传给浏览器。有效的URI 不能包含某些字符,比如空格。使用URI 编码方法来编码URI 可以让浏览器能够理解它们,同时又以特殊的UTF-8 编码替换掉所有无效字符。
ecnodeURI()方法用于对整个URI 进行编码,比如”www.wrox.com/illegal value.js”。而encodeURIComponent()方法用于编码URI 中单独的组件,比如前面URL 中的”illegal value.js”。
这两个方法的主要区别是,encodeURI()不会编码属于URL 组件的特殊字符,比如冒号、斜杠、问号、井号,而encodeURIComponent()会编码它发现的所有非标准字符。
let uri = "http://www.wrox.com/illegal value.js#start";
// "http://www.wrox.com/illegal%20value.js#start"
console.log(encodeURI(uri));
// "http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.js%23start"
console.log(encodeURIComponent(uri));
与encodeURI()和encodeURIComponent()相对的是decodeURI()
和decodeURIComponent()
。
decodeURI()只对使用encodeURI()编码过的字符解码,,decodeURIComponent()解码所有被encodeURIComponent()编码的字符。
let uri = "http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.js%23start";
// http%3A%2F%2Fwww.wrox.com%2Fillegal value.js%23start
console.log(decodeURI(uri));
// http:// www.wrox.com/illegal value.js#start
console.log(decodeURIComponent(uri));
这里,uri 变量中包含一个使用encodeURIComponent()编码过的字符串。首先输出的是使用decodeURI()解码的结果,可以看到只用空格替换了%20。然后是使用decodeURIComponent()解码的结果,其中替换了所有特殊字符,并输出了没有包含任何转义的字符串。(这个字符串不是有效的URL。)
2. eval()
方法
这个方法就是一个完整的ECMAScript 解释器,它接收一个参数,即一个要执行的ECMAScript(JavaScript)字符串。
eval("console.log('hi')");
上面这行代码的功能与下一行等价:
console.log("hi");
当解释器发现eval()调用时,会将参数解释为实际的ECMAScript 语句,然后将其插入到该位置。通过eval()执行的代码属于该调用所在上下文,被执行的代码与该上下文拥有相同的作用域链。这意味着定义在包含上下文中的变量可以在eval()调用内部被引用,比如下面这个例子:
let msg = "hello world";
eval("console.log(msg)"); // "hello world"
这里,变量msg 是在eval()调用的外部上下文中定义的,而console.log()显示了文本”hello world”。这是因为第二行代码会被替换成一行真正的函数调用代码。
类似地,可以在eval()内部定义一个函数或变量,然后在外部代码中引用,如下所示:
eval("function sayHi() { console.log('hi'); }");
sayHi();
同样对于变量:
eval("var msg = 'hello world';");
console.log(msg); // Reference Error: msg is not defined
注意这里使用的是var声明,因为let声明的作用域是块,在外部调用会报错:
eval("let msg = 'hello world';");
console.log(msg); // Reference Error: msg is not defined
在严格模式下,在eval()内部创建的变量和函数无法被外部访问。换句话说,之前的例子会报错。同样,在严格模式下,赋值给eval 也会导致错误:
"use strict";
eval = "hi"; // 导致错误
3. Global对象属性
Global 对象有很多属性,其中一些前面已经提到过了。像undefined、NaN 和Infinity 等特殊值都是Global 对象的属性。此外,所有原生引用类型构造函数,比如Object 和Function,也都是Global 对象的属性。
4. window对象
虽然ECMA-262 没有规定直接访问Global 对象的方式,但浏览器将window 对象实现为Global对象的代理。因此,所有全局作用域中声明的变量和函数都变成了window 的属性。
5.4.2 Math
min()和max()方法
min()和max()方法用于确定一组数值中的最小值和最大值。这两个方法都接收任意多个参数。
要知道数组中的最大值和最小值,可以像下面这样使用扩展操作符:
let values = [1, 2, 3, 4, 5, 6, 7, 8];
let max = Math.max(...val);
舍入方法
用于把小数值舍入为整数的4 个方法:Math.ceil()
、Math.floor()
、Math.round()
和Math.fround()
。这几个方法处理舍入的方式如下所述。
- Math.ceil()方法始终向上舍入为最接近的整数。
- Math.floor()方法始终向下舍入为最接近的整数。
- Math.round()方法执行四舍五入。
- Math.fround()方法返回数值最接近的单精度(32 位)浮点值表示。
random方法
Math.random()方法返回一个0~1 范围内的随机数,其中包含0 但不包含1。
总结
JavaScript 中的对象称为引用值,几种内置的引用类型可用于创建特定类型的对象。
- 引用值与传统面向对象编程语言中的类相似,但实现不同。
- Date 类型提供关于日期和时间的信息,包括当前日期、时间及相关计算。
- RegExp 类型是ECMAScript 支持正则表达式的接口,提供了大多数基础的和部分高级的正则表 达式功能。
JavaScript 比较独特的一点是,函数实际上是Function 类型的实例,也就是说函数也是对象。因为函数也是对象,所以函数也有方法,可以用于增强其能力。
由于原始值包装类型的存在,JavaScript 中的原始值可以被当成对象来使用。有3 种原始值包装类型:Boolean、Number 和String。它们都具备如下特点。
- 每种包装类型都映射到同名的原始类型。
- 以读模式访问原始值时,后台会实例化一个原始值包装类型的对象,借助这个对象可以操作相 应的数据。
- 涉及原始值的语句执行完毕后,包装对象就会被销毁。
当代码开始执行时,全局上下文中会存在两个内置对象:Global 和Math。其中,Global 对象在大多数ECMAScript 实现中无法直接访问。不过,浏览器将其实现为window 对象。所有全局变量和函数都是Global 对象的属性。Math 对象包含辅助完成复杂计算的属性和方法。
今天的文章JavaScript高级程序设计—第五章 基本引用类型分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/60509.html