内容均摘自JavaScript高级程序设计第四版,仅用于记录学习过程。
第五章 基本引用类型
- 5.1 理解引用类型
- 5.2 Date
- 5.3 RegExp
- 5.4 原始值包装类型
- 5.4.1 Boolean
- 5.4.2 Number
- 5.4.3 String
- 5.4.3.1 JavaScript 字符
- 5.4.3.2 拼接字符串方法 concat()
- 5.4.3.3 提取子字符串的方法 slice()、substr()、substring()
- 5.4.3.4 定位子字符串位置的方法 indexOf() 、lastIndexOf()
- 5.4.3.5 包含字符串的方法 startsWith()、endsWith()、includes()
- 5.4.3.6 trim() 方法
- 5.4.3.7 repeat() 方法
- 5.4.3.8 padStart() 和 padEnd() 方法
- 5.4.3.9 字符串迭代与解构
- 5.4.3.10 字符串大小写转换
- 5.4.3.11 字符串模式匹配方法
- 5.4.3.12 replace()
- 5.4.3.13 split()
- 5.4.3.14 localeCompare()
- 5.5 单例内置对象
- 5.6 小结
5.1 理解引用类型
引用值
(或对象
)是某个特定的引用类型
的实例
- 在ECMAScript 中,
引用类型
是把数据和功能组织到一起的结构 - 常被人
错误
地称"类
" 引用类型
有时也被称为对象定义
,因为它们描述了自己的对象应有的属性和方法
新对象通过使用new
操作符后跟一个构造函数
来创建,构造函数
就是用来创建新对象
的函数,比如下面这行代码:
let now = new Date();
- 创建了引用类型
Date
的一个新实例,并将它保存在变量now
中 Date()
是构造函数,负责创建一个只有默认属性和方法的简单对象
5.2 Date
Date
类型将日期保存为1970年1月1日零时
至今所经过的毫秒数
创建日期对象,使用new
操作符来调用Date
构造函数:
let now = new Date();
- 不给
Date
构造函数传参数的情况下,创建的对象将保存当前
日期和时间 - 若要基于其他日期和时间创建日期对象,必须传入其毫秒表示,ECMAScript 为此提供了两个辅助方法:
Date.parse()
和Date.UTC()
5.2.1 常用方法
5.2.1.1 Date.parse()
Date.parse()
:接收一个表示日期的字符串参数
,尝试将这个字符串转换为表示该日期的毫秒数
- 支持的日期格式:
- “月/日/年”,如"5/23/2021"
- “月名 日,年”,如"May 23,2021"
- “周几 月名 日 年 时:分:秒 时区”,如"Tue May 23 2021 00:00:00 GMT-0700"
若直接把表示日期的字符串传给Date()
,Date
会在后台调用Date.parse()
比如,要创建一个表示"2021年5月23日"的日期对象,可以使用以下代码:
let date = new Date(Date.parse("5/23/2021"));
// 等效于
let date = new Date("5/23/2021");
⚠️:若传的字符串不表示日期,则Date.parse()
返回NaN
5.2.1.2 Date.UTC()
Date.UTC()
也是返回日期的毫秒数
- 参数:年、月(以0为起点)、日(1~31)、时(0~23)、分、秒、毫秒
(只有年和月是必需的,日不提供则默认为1,其他不提供则默认为0)
Date.UTC()
也会被Date()
隐式调用,但隐式调用下创建的是本地日期
,不是GMT日期
下面是两个例子:
// GMT时间 2000年 1月 1日 0点
let date = new Date(Date.UTC(2000, 0));
// 等效于
// 本地时间 2000年 1月 1日 0点
let date = new Date(2000, 0);
// GMT时间 2021年 8月 1日 5点 55分 55秒
// 注意这里的月不是8,是7,以0为起点
let date = new Date(Date.UTC(2021, 7, 1, 5, 55, 55));
5.2.1.3 Date.now()
返回表示方法执行时
的日期和时间的毫秒数
这个方法可以用在代码分析中:
// 起始时间
let start = Date.now();
// 调用函数
doSomething();
// 结束时间
let stop = Date.now();
// 计算用时
result = stop - start;
5.2.2 继承方法
与其他引用类型一样,Date
重写了toLocaleString()
、toString()
、valueOf()
方法
toLocaleString()
:返回与浏览器运行的本地环境
一致的日期和时间,格式中包含AM(上午)
或PM(下午)
,但不包含时区信息,具体格式可能因浏览器而不同toString()
:返回带时区信息
的日期和时间
下面给出这两种方法返回的示例:
2021年8月2日零点
// toLocalString()
8/2/2021 12:00:00 AM
// toString()
Mon Aug 2 2021 00:00:00 GMT-0800 (Pacific Standard Time)
valueOf()
:返回日期的毫秒表示
let date1 = new Date(2021, 0, 1); // 1月1日
let date2 = new Date(2021, 1, 1); // 2月1日
console.log(date1 < date2); // true
5.2.3 日期格式化方法
toDateString()
:显示日期中的周几、月、日、年(格式特定于实现)toTimeString()
:显示日期中的时、分、秒、时区(格式特定于实现)toLocalDateString()
:显示日期中的周几、月、日、年(格式特定于实现和地区)toLocaleTimeString()
:显示时期中的时、分、秒(格式特定于实现和地区)toUTCString()
:显示完整的UTC日期(格式特定于实现)
这些方法的输出会因浏览器而异,因此不能用于在用户界面上一致地显示日期。
5.2.4 日期/时间组件方法
Date
引用类型的其他方法涉及取得
或设置
日期值的特定部分
⚠️:UTC日期
指的是没有时区偏移(将日期转换为GMT)时的日期
5.3 RegExp
ECMAScript 通过 RegExp
类型支持正则表达式
正则表达式的创建:
let expression = /pattern/flags;
pattern
(模式)可以是任何正则表达式,包括字符类、限定符、分组、向前查找、反向引用
每个正则表达式可以不带或带多个flags
(标记),用于控制正则表达式的行为
匹配模式的标记:
g
:全局模式,查找字符串的全部
内容,而不是找到第一个匹配的内容就结束I
:不区分大小写,查找匹配时忽略大小写m
:多行模式,查找到一行文本末尾时会继续查找y
:粘附模式,只查找从lastIndex
开始及之后的字符串u
:Unicode模式,启用Unicode
匹配s
:dotAll模式,匹配任何字符,包括\n
或\r
// 匹配字符串中的所有"at"
let p1 = /at/g;
// 匹配第一个"bat"或"cat",武略大小写
let p2 = /[bc]at/i;
元字符在模式中必须转义,因为元字符在正则表达式中都有多种特殊功能,所以要匹配这些字符本身就必须使用反斜杠
来转义
包括:
() [] {} \ ^ $ | ? * + .
// 匹配第一个"[bc]at",忽略大小写
let p1 = /\[bc\]at/i;
// 匹配所有的".at",忽略大小写
let p2 = /\.at/gi;
上面例子中的正则表达式都是使用字面量形式定义的
也可以使用 RegExp
构造函数来创建
- 接收两个参数:
模式
字符串和标记
字符串
// 匹配第一个"bat"或"cat",忽略大小写
let p = /[bc]at/i;
// 等效于
let p = new RegExp("[bc]at", "i");
- 因为
RegExp
引用类型的模式参数是字符串,所以在某些情况下需要二次转义
- 使用
RegExp
也可以基于已有的正则表达式实例,并可选择性地修改它们的标记:
const r1 = /cat/g;
const r2 = new RegExp(r1);
const r3 = new RegExp(r1, "i");
console.log(r2); // "/cat/g"
console.log(r3); // "/cat/i"
5.3.1 RegExp实例属性
每个 RegExp 实例都有下列属性:
global
:布尔值,是否设置了g
标记ignoreCase
:布尔值,是否设置了i
标记unicode
:布尔值,是否设置了u
标记sticky
:布尔值,是否设置了y
标记multiline
:布尔值,是否设置了m
标记dotAll
:布尔值,是否设置了s
标记lastIndex
:整数,在源字符串中下一次搜索的开始位置source
:正则表达式的字面量字符串,没有开头和结尾的斜杠flags
:正则表达式的标记字符串
通过这些属性可以全面了解正则表达式的信息,不过实际开发中用的不多
5.3.2 RegExp 实例方法
5.3.2.1 exec()
RegExp
实例的主要方法是exec()
,主要用于配合捕获组
使用
- 只接收一个参数,要作为
模式
的字符串 - 如果找到了匹配项,返回包含第一个匹配信息的数组
- 如果没找到匹配项,返回
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 从字符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"
这个例子中,模式
包含两个捕获组
:
- 最内部的匹配项
" and baby"
- 外部的匹配项
" and dad"
或" and dad and baby"
数组的第一个元素是匹配的整个字符串
,第二个元素是匹配第一个捕获组
的字符串,第三个元素是匹配第二个捕获组
的字符串。
5.3.2.1.1 全局标记
如果模式设置了全局标记
,则每一次调用exex()
都会返回一个匹配的信息,若不设置全局标记
,无论调用多少次exex()
,也只会返回第一个匹配的信息
看下例:
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
上面例中的模式没有设置全局标记
,因此调用exex()
只会返回第一个匹配项("cat"
)
⚠️:lastIndex
在非全局模式下始终不变
(lastIndex
表示在查找字符串中下一次搜索开始的位置)
如果在这个模式上设置了g
标记,则每次调用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
...
这次模式设置了全局标记
,因此每次调用 exec()
都会返回字符串中的下一个匹配项
,直到搜索到 字符串末尾
⚠️:lastIndex
在全局模式下每次调用exec()
都会更新
(lastIndex
表示在查找字符串中下一次搜索开始的位置)
5.3.2.1.2 粘附标记
如果模式设置了粘附标记 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对应的字符是",",找不到匹配项,因此返回null
// 没找到匹配项,于是将lastIndex设置为0
matches = pattern.exec(text);
console.log(matches); // null
console.log(pattern.lastIndex); // 0
// 向前设置lastIndex可以让粘附的模式找到下一个匹配项:
pattern.lastIndex = 5;
matches = pattern.exec(text);
console.log(matches.index); // 5
console.log(matches[0]); // bat
console.log(pattern.lastIndex); // 8
5.3.2.2 test()
正则表达式的另一个方法是test()
- 接收一个字符串参数
- 如果输入的
文本
与模式
匹配,则返回true
,否则返回false
test()
常用于if
语句中:
let text = "000-00-0000";
let pattern = /\d{3}-\d{2}-\d{4}/;
if (pattern.test(text)) {
console.log("The pattern was matched.");
}
上面的例子中,正则表达式用于测试特定的序列
这个用法常用于验证用户输入
,此时我们只在乎输入是否有效,不关心为什么无效
⚠️:
无论正则表达式是怎么创建的,toLocaleString()
和 toString()
都返回正则表达式的字面量表示
,valueOf()
方法返回正则表达式本身
比如:
let pattern = new RegExp("\\[bc\\]at", "gi");
console.log(pattern.toString()); // /\[bc\]at/gi
console.log(pattern.toLocaleString()); // /\[bc\]at/gi
console.log(pattern.valueOf()); // /\[bc\]at/gi
5.3.3 RegExp构造函数属性
RegExp构造函数
本身也有几个属性(在其他语言中,这种属性被称为静态属性
)
这些属性适用于作用域中的所有正则表达式
并且这些属性可以通过两种不同的方式访问(全名和简写)
5.4 原始值包装类型
ECMAScript 提供了 3 种特殊的引用类型: Boolean
、Number
、String
这些类型具有本章介绍的其他引用类型一样的特点,也具有与各自原始类型对应的特殊行为
每当用到某个原始值
的方法和属性
时,后台都会创建
一个相应
的原始包装类型
的对象
,从而暴露
出操作原始值
的各种方法
let s1 = "some text";
let s2 = s1.substring(2);
变量s1
包含一个字符串,它是一个原始值
第二行在变量s1
上调用了substring()
方法,把结果保存在变量s2
中
我们知道,原始值本身不是对象,逻辑上不应该有方法,这是因为后台进行了很多处理,从而实现上述操作。
- 具体来说,当第二行访问变量
s1
时,是以读模式
访问的,也就是要从内存中读取
变量s1
保存的值 - 在以
读模式
访问字符串的时候,后台都会执行以下3步:- 创建一个
String
类型的实例
- 调用
实例
上的特定方法
销毁
实例
- 创建一个
// 上面的3步类似于执行以下代码
let s1 = new String("some text");
let s2 = s1.substring(2);
s1 = null;
这种行为可以让原始值拥有对象的行为
引用类型与原始值包装类型的主要区别在于对象
的生命周期
- 通过
new
实例化引用类型
后,得到的实例会在离开作用域
时被销毁
,而自动创建
的原始值包装对象
只存在于访问它的那行代码执行期间,这意味着不能在运行时给原始值添加属性和方法
这里的第二行代码尝试给let s1 = "some text"; s1.color = "red"; console.log(s1.color); // undefined
s1
添加一个color
属性,但在第三行代码访问color
属性时,它却不见了
原因:第二行代码运行时会临时创建
一个String
对象,而当第三行代码执行时,这个对象已经被销毁了。实际上,第三行代码在这里创建了自己的String
对象,但这个对象没有color
属性。
可以显式地使用Boolean
、Number
、String
构造函数创建原始值包装对象,确定是必要时再这么做,否则容易让开发者分不清到底它们是原始值还是引用值
Object
构造函数作为一个工厂方法
,可以根据传入值
的类型返回相应原始值包装类型
的实例
let obj1 = new Object("some text");
console.log(obj1 instanceof String); // true
let obj2 = new Object(1);
console.log(obj2 instanceof Number); // true
let obj3 = new Object(true);
console.log(obj3 instanceof Boolean); // true
⚠️:使用new
调用原始包装类型的构造函数,与调用同名的转型函数并不一样
let value = "25";
let num1 = Number(value); // 调用转型函数
console.log(typeof num1); // "number"
let num2 = new Number(value); //.调用原始包装类型的构造函数
console.log(typeof num2); // "object"
在本例中,变量num1
保存的是一个值为25的原始数值,而变量num2
保存的是一个Number
的实例
不推荐显式
创建原始值包装类型
的实例
,虽然它们对于操作原始值的功能很重要
5.4.1 Boolean
Boolean
是布尔值的引用类型
要创建一个Boolean对象
,使用Boolean()
,并传入 true
或 false
,如下例所示:
let booleanObj = new Boolean(true);
5.4.1.1 在布尔表达式中使用Boolean对象
Boolean
对象在布尔表达式
中使用时容易引起误会,用得很少:
let falseObj = new Boolean(false);
let result = falseObj && true;
console.log(result); // true
let falseVal = false;
result = falseVal && true;
console.log(result); // false
在本例中,第2行对falseObj
和true
进行布尔运算,但这个运算是对falseObj
对象,而不是对它表示的值false
求值,这里就要⚠️:所有对象在布尔表达式中都会自动转换为true
所以true && true
当然也为true
5.4.1.2 原始布尔值和布尔对象的区别
原始值和引用值(Boolean对象
)还有一些区别:
typeof
操作符对原始值返回"boolean"
,对引用值返回object
instanceof
操作符对原始值返回false
,对引用值返回true
console.log(falseObj instanceof Boolean); // true
console.log(falseVal instanceof Boolean); // false
理解原始布尔值
和Boolean对象
的区别很重要
强烈建议永远不要使用Boolean对象
5.4.2 Number
Number
是数值的引用类型
要创建一个Number对象
,使用Number()
,并传入 一个数值,如下例所示:
let numberObj = new Number(10);
Number
引用类型重写了valueOf()
、toLocaleString()
、toString()
valueOf()
返回Number
对象表示的原始数值
toLocaleString()
、toString()
返回数值字符串toString()
可以接收一个表示基数的参数
let num = 10;
console.log(num.toString()); // "10"
console.log(num.toString(2)); // "1010"
console.log(num.toString(16)); // "a"
5.4.2.1 将数值格式化为字符串的方法
toFixed()
:返回包含指定小数点位数
的数值字符串,此方法会自动四舍五入
let num = 10;
console.log(num.toFixed(2)); // "10.00"
toExponential()
:返回以科学计数法
表示的数值字符串
let num = 10;
console.log(num.toExponential(1)); // "1.0e+1"
console.log(num.toExponential(3)); // "1.000e+1"
toPrecision()
:根据情况返回最合理的输出结果,接收一个 参数,表示结果中数字的总位数
(不包含指数)
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
,99
不能只用1位数字来精确表示,所以这个方法将它舍入为100
本质上,toPrecision()
方法会根据数值
和精度
来决定调用 toFixed()
还是 toExponential()
5.4.2.2 原始数值和数值对象的区别
typeof
操作符对原始值返回"number"
,对引用值返回object
instanceof
操作符对原始值返回false
,对引用值返回true
let numObj = new Number(10);
let numVal = 10;
console.log(numObj instanceof Number); // true
console.log(numVal instanceof Number); // false
理解原始数值
和Number对象
的区别很重要
同样建议不要使用Number对象
5.4.2.3 isInteger()方法与安全整数
ES6新增了Number.isInteger()
方法,用于辨别一个数值是否保存为整数
console.log(Number.isInteger(1)); // true
console.log(Number.isInteger(1.00)); // true
在Number.MIN_SAFE_INTERGER
(-253 + 1)到Number.MAX_SAFE_INTERGER
(253 - 1)范围内,二进制
值可以表示为一个整数
值
超出此范围的数值,即使尝试保存为整数,IEEE 754 编码格式也意味着二进制值可能会表示一个完全不同的数值
可以使用Number.isSageInteger()
鉴别整数是否在这个范围内
console.log(Number.isSafeInteger(-1 * (2 ** 53))); // false
console.log(Number.isSafeInteger(-1 * (2 ** 53) + 1)); // true
5.4.3 String
String
是字符串的引用类型
要创建一个String对象
,使用String()
,并传入 一个字符串,如下例所示:
let stringObj = new String("hello world");
String对象
的方法可以在所有的字符串原始值
上调用,3个继承的方法valueOf()
、toLocaleString()
、toString()
都返回对象的原始字符串值
length
属性表示字符串中字符的数量
,需要⚠️:即使字符串中包含双字节字符,也仍然会按单字符来计数
String
类型提供了很多方法来解析和操作字符串:
5.4.3.1 JavaScript 字符
字符串
由16位码元
组成,对多数字符来说,每16位
码元对应1个
字符。
- 也就是说
length
属性表示字符串
包含多少16位码元
:
let message = "abc";
console.log(message.length); // 3
charAt()
方法返回给定索引位置
的字符,具体来说,这个方法查找指定索引位置的16位码元,并返回该码元对应的字符:
let message = "abc";
console.log(message.charAt(2)); // "c"
5.4.3.2 拼接字符串方法 concat()
concat()
:用于将一个或多个字符串
拼接成一个新字符串
let stringVal = "hello ";
let result = stringVal.concat("world");
console.log(result); // "hello world"
console.log(stringValue); // "hello"
⚠️:stringVal
的值是不变的
concat()
也可以接收任意多个参数,可以一次性拼接多个字符串:
let stringVal = "hello ";
let result = stringVal.concat("world","!","hahaha");
console.log(result); // "hello world!hahaha"
(但更常用的还是+
拼接多个字符串)
5.4.3.3 提取子字符串的方法 slice()、substr()、substring()
slice()
、substr()
、substring()
都会返回调用它们的字符串的一个子字符串
,都接收1或2个参数,都不会修改原字符串
第一个参数
:表示子字符串开始
的位置第二个参数
:- 对
slice()
和substring()
而言,表示提取结束的位置(该位置之前的字符会被提取) - 对
substr()
而言,表示提取的子字符串的长度
- 对
(若省略第二个参数,默认提取到字符串末尾)
⚠️:接收两个参数时,以较小的那个参数作为起点,较大的参数作为终点
let stringVal = "hello world";
console.log(stringVal.slice(3)); // "lo world"
console.log(stringVal.slice(3,7)); // "lo w" 下标7字符之前,下标3字符之后的字符
console.log(stringVal.substring(3,7)); // "lo w"
console.log(stringVal.substr(3,7)); // "lo worl" 要返回的子字符串长度为7,从下标3字符开始提取
当某个参数
为负数
时:
slice()
:将所有负数
参数都当成字符串长度+负数值
let stringVal = "hello world";
console.log(stringVal.slice(-3)); // "rld" -3参数会被转换为长度11+(-3)=8
console.log(stringVal.slice(3, -4)); // "lo w" -4参数会被转换为长度11+(-4)=7,相当于slice(3,7)
substring()
:将所有负数
参数都当成0
let stringVal = "hello world";
console.log(stringVal.substring(-3)); // "hello world" -3参数会被转换为0
console.log(stringVal.substring(3, -4)); // "hel" -4参数会被转换为0,相当于substring(3,0),这又等价于substring(0,3)
substr()
:将第一
个负参数值当成字符串长度+此值
,第二
个负参数值当成0
let stringVal = "hello world";
console.log(stringVal.substr(-3)); // "rld" -3参数会被转换成长度11+(-3)=8
console.log(stringVal.substr(3, -4)); // "" 空字符串,因为-4参数被转换成0,也就是提取的字符串长度为0
5.4.3.4 定位子字符串位置的方法 indexOf() 、lastIndexOf()
indexOf()
和 lastIndexOf()
从字符串中搜索传入的字符串
,并返回位置
(如果没找到
,则返回-1
)
区别在于:
indexOf()
从字符串开头
开始查找子字符串
lastIndexOf()
从字符串末尾
开始查找子字符串
let stringVal = "hello world";
console.log(stringVal.indexOf("o")); // 4
console.log(stringVal.lastIndexOf("o")); // 7
indexOf()
和 lastIndexOf()
都接收可选的第二个参数
,表示开始搜索
的位置
像这样使用第二个参数并循环调用 indexOf()
或 lastIndexOf()
,可以在字符串中找到所有的目标子字符串,如下所示:
let stringVal = "Pain makes us stronger";
let positions = [];
let pos = stringVal.indexOf("s");
while(pos != -1) {
positions.push(pos);
pos = stringVal.indexOf("s", pos+1);
}
console.log(positions); // [9,12,14]
本例中,首先取得第一个 "s"
的位置,然后进入循环,将上一次的位置+1
再传给indexOf()
将每一个"s"
的位置都保存在positions
数组中
5.4.3.5 包含字符串的方法 startsWith()、endsWith()、includes()
ES6新增了3个用于判断字符串中是否包含另一个字符串的方法:
这些方法都会从字符串中搜索
传入的字符串,并返回
一个表示是否包含
的布尔值
区别在于:
startsWith()
:检查开始于索引0
的匹配项endsWith()
:检查开始于索引string.length - substring.length
的匹配项includes()
:检查整个
字符串
let message = "foobarbaz";
console.log(message.startsWith("foo")); // true
console.log(message.startsWith("bar")); // false
console.log(message.endsWith("baz")); // true
console.log(message.endsWith("bar")); // false
console.log(message.includes("bar")); // true
console.log(message.includes("qux")); // false
startsWith()
、includes()
接收可选的第二个参数,表示开始搜索的位置,这两方法会从指定位置向字符串末尾搜索,忽略该位置前的所有字符endsWith()
接收可选的第二个参数,表示应该当作字符串末尾的位置
let message = "foobarbaz";
console.log(message.endsWith("bar", 6)); // true
5.4.3.6 trim() 方法
trim()
方法会创建字符串的一个副本,删除
前后所有空格符
,再返回结果
let stringVal = " hello world ";
let trimmedStringVal = stringVal.trim();
console.log(stringVal); // " hello world "
console.log(trimmedStringVal); // "hello world"
还有trimLeft()
:从字符串开始
清理空格符
trimRight()
:从字符串末尾
清理空格符
5.4.3.7 repeat() 方法
repeat()
接收一个整数参数
,表示要将字符串复制
多少次,然后返回拼接所有副本
后的结果
let stringVal = "ha ";
console.log(stringVal.repeat(5)); // "ha ha ha ha ha "
5.4.3.8 padStart() 和 padEnd() 方法
padStart()
和 padEnd()
会复制
字符串,如果小于
指定长度,则在相应一边
填充字符,直至满足长度
条件
这两个方法接收两个参数
- 第一个参数是
长度
- 第二个参数是可选的
填充字符
,默认为空格
let stringVal = "foo";
console.log(stringVal.padStart(6)); // " foo"
console.log(stringVal.padStart(9,"!")); // "!!!!!!foo"
console.log(stringVal.padEnd(9,"!")); // "foo!!!!!!"
第二个参数并不限于一个字符
,可提供多个字符的字符串,则会将其拼接
并截断
以匹配指定长度
如果长度小于
或等于
字符串长度,则会返回原始字符串
let stringVal = "foo";
console.log(stringVal.padStart(8, "bar")); // "barbafoo"
console.log(stringVal.padStart(2)); // "foo"
5.4.3.9 字符串迭代与解构
字符串的原型上暴露了一个@@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 letter of "abc") {
console.log(letter);
}
// a
// b
// c
有了这个迭代器之后,字符串就可以通过解构操作符
来解构了,比如更方便地把字符串
分割为字符数组
:
let message = "abc";
console.log([...message]); // ["a", "b", "c"]
5.4.3.10 字符串大小写转换
toLowerCase()
toLocaleLowerCase()
toUpperCase()
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"
toLocaleLowerCase()
和 toLocaleUpperCase()
方法旨在基于特定地区实现
在很多地区,地区特定的方法与通用的方法是一样的。但在少数语言中(如土耳其语),要使用地区特定的方法才能实现正确转换
5.4.3.11 字符串模式匹配方法
String
类型为在字符串中实现模式匹配
提供了几个方法
match()
:本质上跟RegExp
对象的exec()
方法相同match()
方法接收一个参数,可以是一个正则表达式字符串,也可以是一个RegExp
对象
let text = "cat, bat, sat, fat";
let pattern = /.at/;
let matches = text.match(pattern); console.log(matches.index); // 0
console.log(matches[0]); // "cat"
console.log(pattern.lastIndex); // 0
match()
返回的数组
与exec()
返回的数组是一样的:第一
个元素是与整个模式匹配
的字符串
,其余元素则是与表达式中的捕获组
匹配的字符串(如果有的话)
search()
:接收唯一的参数,可以是一个正则表达式字符串,也可以是一个RegExp
对象- 返回模式第一个匹配的
位置索引
,没找到返回-1
search()
始终从字符串开头
向后匹配模式
- 返回模式第一个匹配的
let text = "cat, bat, sat, fat";
let pos = text.search(/at/);
console.log(pos); // 1 即"at"的第一个字符在字符串中的位置
5.4.3.12 replace()
为简化子字符串替换
操作,ECMAScript 提供了 replace()
方法
- 接收两个参数
- 第一个参数可以是一个
RegExp 对象
或一个字符串
(这个字符串不会转换为正则表达式) - 第二个参数可以是
一个字符串
或一个函数
let text = "cat, bat, sat, fat";
let result = text.replace("at", "ond");
console.log(result); // "cond, bat, sat, fat"
如果第一
个参数是字符串
,那只会替换第一个子字符串
要想替换所有子字符串
,第一
个参数必须为正则表达式
并且带全局标记
let text = "cat, bat, sat, fat";
let result = text.replace(/at/g, "ond");
console.log(result); // "cond, bond, sond, fond"
5.4.3.13 split()
最后一个与模式匹配相关的字符串方法是 split()
这个方法会根据传入的分隔符
将字符串拆
分成数组
- 接收两个参数
- 第一个参数作为
分隔符
,可以是字符串
,也可以是RegExp
对象 - 第二个可选参数是
数组的大小
,确保返回的数组不会超过指定大小
let colorTxt = "red, blue, green";
let color1 = colorTxt.split(","); // ["red", "blue", "green"]
let color2 = colorTxt.split(",", 2); // ["red", "blue"] 这里限制了数组长度为2
5.4.3.14 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
"brick"
按字母表顺序应该排在"yellow"前头,因此localeCompare()
返回 1
5.5 单例内置对象
ECMA-262 对内置对象的定义是:
“任何由 ECMAScript 实现提供、与宿主环境无关,并在 ECMAScript 程序开始执行时
就存在
的对象”
这意味着:开发者不用显式地实例化内置对象,因为它们已经实例化好了
前面我们已经解除了大部分内置对象,包括Object
、Array
、String
这里要介绍另外两个单例内置对象:Global
、Math
5.5.1 Global
Global
对象最特别,因为代码不会显式
地访问它
在全局作用域
中定义的变量和函数都会变成Global
对象的属性,前面介绍的函数包括isNaN()
、isFinite()
、parseInt()
等,实际上都是Global
对象的方法
Global
对象上还有一些其他方法:
5.5.1.1 URL编码方法
encodeURI()
、encodeURIComponent()
用于编码统一资源标识符
(URI),以传给浏览器
有效的URI
不能包含某些字符,比如空格
encodeURI()
:对整个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";
console.log(encodeURI(uri));
// "http://www.wrox.com/illegal%20value.js#start"
console.log(encodeURIComponent(uri));
// "http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.js%23start"
本例中,使用encodeURI()
编码,只有空格被替换为%20
,而使用encodeURIComponent()
编码,所有非字母字符
都替换成了相应的编码形式
与 encodeURI()
和 encodeURIComponent()
相对的是 decodeURI()
和 decodeURIComponent()
decodeURI()
只对使用encodeURI()
编码过的字符解码decodeURIComponent()
解码所有被encodeURIComponent()
编码的字符,基本上就是解码所有特殊值
5.5.1.2 eval()
eval()
是一个完整的ECMAScript 解释器
接收一个参数,即要执行的JavaScript字符串
eval("console.log('hi')");
// 等效于
console.log('hi');
当解释器
发现 eval()
调用时,会将参数解释为实际的 ECMAScript 语句
,然后将其插入
到该位置
5.5.1.3 Global 对象属性
5.5.1.4 获取Global对象
- 通过
window
对象,
浏览器将window
对象实现为Global
对象的代理
,因此所有全局作用域
中声明的变量和函数都变成了window
的属性 - 另一种获取
Global
对象的方式:
let global = function() {
return this;
}();
这段代码创建了一个立即调用的函数,返回了this
的值
当一个函数在没有明确成为某个对象的方法
,或通过call() / apply() 指定this值
的情况下执行的时候,this
值就是Global
对象
5.5.2 Math
5.5.2.1 Math对象的属性
5.5.2.2 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 max = Math.max(...val);
5.5.2.3 舍入方法
把小数值舍入为整数:
Math.ceil()
:始终向上
舍入为最接近
的整数
Math.floor()
:始终向下
舍入为最接近
的整数
Math.round()
:四舍五入Math.fround()
:返回数值最接近的单精度(32 位)浮点值表示
5.5.2.4 random() 方法
Math.random()
方法返回一个0~1
范围内的随机数
,包含0但不包含1
可以基于如下公式使用 Math.random()
从一组整数中随机选择
一个数:
number = Math.floor(Math.random() * totalNumber + minVal)
totalNumber
为可选的总数,minVal
为最小的可能值
这里使用了 Math.floor()
方法,因为 Math.random()
始终返回小数
因此,如果想从1~10
范围内随机选择一个数:
可选的总数有10个值,最小的可能值为1
let num = Math.floor(Math.random() * 10 + 1);
5.5.2.5 其他方法
5.6 小结
js中的对象
称为引用值
,几种内置的引用类型
可用于创建特定类型
的对象
引用类型
与类
相似,但实现不同Date
引用类型提供关于日期和时间的信息RegExp
引用类型是支持正则表达式的接口
js比较独特的一点是:函数实际上是 Function
类型的实例
,也就是说函数也是对象。因
为函数也是对象,所以函数也有方法,可以用于增强其能力。
由于原始值包装类型
,js中的原始值
可以被当成对象
使用,有3种原始值包装类型
Boolean
、Number
、String
- 每种包装类型都映射到同名的原始类型
- 以
读模式
访问原始值
时,后台会实例化
一个原始值包装类型
的对象,借助这个对象可以操作响应的数据 - 涉及原始值的语句执行完毕后,包装对象就会被
销毁
当代码开始执行时,全局上下文
中会存在两个内置对象: Global
和 Math
Global
对象无法直接访问,但浏览器将其实现为window
对象,所有全局变量和函数都是Global
对象的属性