第五章 基本引用类型
引用值(或者对象)是某个特定引用类型的实例
与类的区别:引用类型是把数据和功能组合到一起的结构,经常被误以为是类,但是js缺少类基本的一些结构特征,比如接口和类。
补充:虽然js缺少类基本的一些解构特征,但是它任然可以被称为是OOP,这是因为类并不是决定一个语言是否是OOP的标准,决定OOP的标准是符合三大特性,即使实现三大特性的方式不一样,只要符合三大特性,都可以称为OOP
引用类型也被称为对象定义
对象被认为是某个特定引用类型的实例
新对象的创建方式:new + 对象的构造函数;
let now = new Date();
Date
Date 类型将日期保存为自协调世界时(UTC,Universal Time Coordinated)时间 1970 年 1 月 1 日午夜(零时)至今所经过的毫秒数
表示范围:1970 年 1 月 1 日之前及之后 285 616 年
创建方式:
let now = new Date();
返回数据:调用构造函数后,如果不传入任何值,就会返回当前日期
要基于其他日期和时间创建日期对象,必须传入其毫秒表示(UNIX 纪元 1970 年 1 月 1 日午夜之后的毫秒数)
由于毫秒数非常不便于人为创建日期,因此有两种方法可以用于毫秒数的转换:Date.parse()和 Date.UTC()
1.Date.parse()
Date.parse()方法接收一个表示日期的字符串参数,尝试将这个字符串转换为表示该日期的毫秒数
ECMA-262 第 5 版定义了 Date.parse()应该支持的日期格式
所有实现都必须支持下列日期格式:
“月/日/年”,如"5/23/2019";
“月名 日, 年”,如"May 23, 2019";
“周几 月名 日 年 时:分:秒 时区”,如"Tue May 23 2019 00:00:00 GMT-0700";
ISO 8601 扩展格式“YYYY-MM-DDTHH:mm:ss.sssZ”,如 2019-05-23T00:00:00(只适用于兼容 ES5 的实现)。
let someDate = new Date(Date.parse("May 23, 2019"));
如果传给 Date.parse()的字符串并不表示日期,则该方法会返回 NaN
如果直接把表示日期的字符串传给 Date 构造函数,那么 Date 会在后台调用 Date.parse()
注意 不同的浏览器对 Date 类型的实现有很多问题。比如,很多浏览器会选择用当前日期替代越界的日期,因此有些浏览器会将"January 32, 2019"解释为"February 1,2019"。Opera 则会插入当前月的当前日,返回"January 当前日, 2019"。就是说,如果是在 9 月 21 日运行代码,会返回"January 21, 2019"。
2.Date.UTC()
Date.UTC()方法也返回日期的毫秒表示
传给 Date.UTC()的参数是年、零起点月数(1 月是 0,2 月是 1,以此类推)、日(1~31)、时(0~23)、分、秒和毫秒
参数中只有前两个是必须的
如果不提供日,那么默认为 1 日。其他参数的默认值都是 0
// GMT 时间 2000 年 1 月 1 日零点
let y2k = new Date(Date.UTC(2000, 0));
// GMT 时间 2005 年 5 月 5 日下午 5 点 55 分 55 秒
let allFives = new Date(Date.UTC(2005, 4, 5, 17, 55, 55));
注意使用UTC()方法时应注意有时区导致的传入参数需要格式化
let allFives = new Date(Date.UTC(2005, 4, 5, 17, 55, 55));// 输出Fri May 06 2005 01:55:55 GMT+0800 (中国标准时间)
// 时区格式化
let allFives = new Date(Date.UTC(2005, 4, 5, 17-8, 55, 55));// Thu May 05 2005 17:55:55 GMT+0800 (中国标准时间)
Date.UTC()也会被 Date 构造函数隐式调用,但有一个区别:这种情况下创建的是本地日期,不是GMT 日期。
let allFives = new Date(2005, 4, 5, 17, 55, 55);// Thu May 05 2005 17:55:55 GMT+0800 (中国标准时间)
ECMAScript 还提供了 Date.now()方法,返回表示方法执行时日期和时间的毫秒数,可以用于分析代码执行的时间
// 起始时间
let start = Date.now();
// 调用函数
doSomething();
// 结束时间
let stop = Date.now(),
result = stop - start;
另外一个分析时间更方便的方法:
console.time("myTimer");
// 执行一些需要测量时间的操作
console.timeLog("myTimer");
// myTimer: 12731.320068359375 ms
1.继承的方法
与其他类型一样,Date 类型重写了 toLocaleString()、toString()和 valueOf()方法。
toLocaleString()方法
返回与浏览器运行的本地环境一致的日期和时间,这通常意味着格式中包含针对时间的 AM(上午)或 PM(下午),但不包含时区信息(具体格式可能因浏览器而不同)
new Date().toLocaleString()
// '2023/12/20 09:14:40'
toString()方法
通常返回带时区信息的日期和时间,而时间也是以 24 小时制(0~23)表示的
new Date().toString()
// 'Wed Dec 20 2023 09:16:07 GMT+0800 (中国标准时间)'
现代浏览器在这两个方法的输出上已经趋于一致。在比较老的浏览器上,每个方法返回的结果可能在每个浏览器上都是不同的。这些差异意味着toLocaleString()和 toString()可能只对调试有用,不能用于显示
valueOf()方法
这个方法被重写后返回的是日期的毫秒表示,因此,操作符(如小于号和大于号)可以直接使用它返回的值
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
2.日期格式化方法
toDateString()显示日期中的周几、月、日、年(格式特定于实现);
toTimeString()显示日期中的时、分、秒和时区(格式特定于实现);
toLocaleDateString()显示日期中的周几、月、日、年(格式特定于实现和地区);
toLocaleTimeString()显示日期中的时、分、秒(格式特定于实现和地区);
toUTCString()显示完整的 UTC 日期(格式特定于实现)。
这些方法都返回时间的字符串表示,并且会因浏览器而异。因此不能用于在用户界面上一致地显示日期。
注意 还有一个方法叫 toGMTString(),这个方法跟 toUTCString()是一样的,目的是为了向后兼容。不过,规范建议新代码使用 toUTCString()。
3.时间/日期组件方法
注意表中“UTC 日期”,指的是没有时区偏移(将日期转换为 GMT)时的日期。
RegExp
ECMAScript 通过 RegExp 类型支持正则表达式
正则表达式使用类似 Perl 的简洁语法来创建:
let expression = /pattern/flags;
表达式解析:
pattern(模式)可以是任何简单或复杂的正则表达式,包括字符类、限定符、分组、向前查找和反向引用
flags(标记)用于控制正则表达式的行为,每个正则表达式可以带零个或多个
标记的类型:
g:全局模式,表示查找字符串的全部内容,而不是找到第一个匹配的内容就结束。
i:不区分大小写,表示在查找匹配时忽略 pattern 和字符串的大小写。
m:多行模式,表示查找到一行文本末尾时会继续查找。
y:粘附模式,表示只查找从 lastIndex 开始及之后的字符串。
u:Unicode 模式,启用 Unicode 匹配。
s:dotAll 模式,表示元字符.匹配任何字符(包括\n 或\r)。
所有元字符在模式中也必须转义,包括:
( [ { \ ^ $ | ) ] } ? * + .
元字符在正则表达式中都有一种或多种特殊功能,所以要匹配上面这些字符本身,就必须使用反斜杠来转义。
// 匹配第一个"bat"或"cat",忽略大小写
let pattern1 = /[bc]at/i;
// 匹配第一个"[bc]at",忽略大小写
let pattern2 = /\[bc\]at/i;
// 匹配所有以"at"结尾的三字符组合,忽略大小写
let pattern3 = /.at/gi;
// 匹配所有".at",忽略大小写
let pattern4 = /\.at/gi;
创建正则的方式:
字面量形式,/pattern/flags;
构造函数,new RegExp(),它接收两个参数:模式字符串和(可选的)标记字符串,两个参数都是字符串
由于匹配模式参数是字符串,所以在某些情况下需要二次转义
所有元字符都必须二次转义,包括转义字符序列,如\n(\转义后的字符串是\\,在正则表达式字符串中则要写成\\\\)。
使用 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"
1.RegExp实例属性
global:布尔值,表示是否设置了 g 标记。
ignoreCase:布尔值,表示是否设置了 i 标记。
unicode:布尔值,表示是否设置了 u 标记。
sticky:布尔值,表示是否设置了 y 标记。
lastIndex:整数,表示在源字符串中下一次搜索的开始位置,始终从 0 开始。
multiline:布尔值,表示是否设置了 m 标记。
dotAll:布尔值,表示是否设置了 s 标记。
source:正则表达式的字面量字符串(不是传给构造函数的模式字符串),没有开头和结尾的
斜杠。
flags:正则表达式的标记字符串。始终以字面量而非传入构造函数的字符串模式形式返回(没有前后斜杠)。
2.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
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"
如果模式设置了全局标记,则每次调用 exec()方法会返回一个匹配的信息。如果没有设置全局标记,则无论对同一个字符串调用多少次 exec(),也只会返回第一个匹配的信息
const re = /.a/g;
re.exec('cabada');
// ['ca', index: 0, input: 'cabada', groups: undefined]
re.exec('cabada');
// ['ba', index: 2, input: 'cabada', groups: undefined]
re.exec('cabada');
// ['da', index: 4, input: 'cabada', groups: undefined]
re.exec('cabada');
const re = /.a/;
re.exec('cabada')
// ['ca', index: 0, input: 'cabada', groups: undefined]
re.exec('cabada')
// ['ca', index: 0, input: 'cabada', groups: undefined]
re.exec('cabada')
// ['ca', index: 0, input: 'cabada', groups: undefined]
如果模式设置了粘附标记 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
test()方法
接收一个字符串参数。如果输入的文本与模式匹配,则参数返回 true,否则返回 false。
这个方法适用于只想测试模式是否匹配的情况,常用在if语句中
let text = "001-02-0001";
let reg = /\d{3}-\d{2}-\d{4}/g;
if(reg.test(text)){
console.log('匹配正常');
}
// 匹配正常
无论正则表达式如何创建的,继承的toLocalString()方法和toString()方法都返回正则表达式的字面量表示
let pattern = new RegExp("\\[bc\\]at", "gi");
console.log(pattern.toString()); // /\[bc\]at/gi
console.log(pattern.toLocaleString()); // /\[bc\]at/gi
注意,正则表达式的 valueOf()方法返回正则表达式本身
3.RegExp构造函数属性
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.rightContext); // summer
console.log(RegExp.lastMatch); // short
console.log(RegExp.lastParen); // s
}
这些属性都可以使用简写来替代,但是由于大多数简写形式都不是合法的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
}
注意,RegExp 构造函数的所有属性都没有任何 Web 标准出处,因此不要在生产环境中使用它们
4.局限模式
下列特性还没有得到ECMAScript的支持
\A 和\Z 锚(分别匹配字符串的开始和末尾)
联合及交叉类
原子组
x(忽略空格)匹配模式
条件式匹配
正则表达式注释
原始值包装类型
为了方便操作原始值,ECMAScript 提供了 3 种特殊的引用类型:Boolean、Number 和 String。
每当用到某个原始值的方法或属性时,后台都会创建一个相应原始包装类型的对象,从而暴露出操作原始值的各种方法
let s1 = "some text";
let s2 = s1.substring(2);
当第二行访问 s1 时,是以读模式访问的,也就是要从内存中读取变量保存的值。在以读模式访问字符串值的任何时候,后台都会执行以下 3 步:
创建一个 String 类型的实例;
调用实例上的特定方法;
销毁实例.
可以近似的转换为
let s1 = new String("some text");
let s2 = s1.substring(2);
s1 = null;
引用类型与原始值包装类型的主要区别在于对象的生命周期。在通过 new 实例化引用类型后,得到的实例会在离开作用域时被销毁,而自动创建的原始值包装对象则只存在于访问它的那行代码执行期间。这意味着不能在运行时给原始值添加属性和方法
可以显式地使用 Boolean、Number 和 String 构造函数创建原始值包装对象
const str = new String('abc');
在原始值包装类型的实例上调用 typeof 会返回"object",所有原始值包装对象都会转换为布尔值 true。
另外,Object 构造函数作为一个工厂方法,能够根据传入值的类型返回相应原始值包装类型的实例。
const str = new String('abc');
console.log(str instanceof String);
注意,使用 new 调用原始值包装类型的构造函数,与调用同名的转型函数并不一样
let value = "25";
let number = Number(value); // 转型函数
console.log(typeof number); // "number"
let obj = new Number(value); // 构造函数
console.log(typeof obj); // "object"
Boolean
Boolean 是对应布尔值的引用类型。要创建一个 Boolean 对象,就使用 Boolean 构造函数并传入true 或 false
const isTest = 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
一般使用字面量,使用包装类型创建的布尔值,容易出错
Number
Number 是对应数值的引用类型。要创建一个Number 对象,就使用 Number 构造函数并传入一个数值
const num = new Number(10);
valueOf()方法返回 Number 对象表示的原始数值
toLocaleString()和 toString()方法返回数值字符串
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()方法
toFixed()方法返回包含指定小数点位数的数值字符串
let num = 10;
console.log(num.toFixed(2)); // "10.00"
// 超过参数的指定位
let num = 10.005;
console.log(num.toFixed(2)); // "10.01"
如果数值本身的小数位超过了参数指定的位数,则四舍五入到最接近的小数位
注意,toFixed()方法可以表示有 0~20 个小数位的数值。某些浏览器可能支持更大的范围,但这是通常被支持的范围。
toExponential()方法
.toExponential()方法返回以科学记数法(也称为指数记数法)表示的数值字符串,接收一个参数,表示结果中小数的位数
let num = 10;
console.log(num.toExponential(1)); // "1.0e+1"
toPrecision()
toPrecision()方法接收一个参数,表示结果中数字的总位数(不包含指数),然后根据情况返回最合理的输出结果,可能是固定长度,也可能是科学记数法形式
let num = 99;
console.log(num.toPrecision(1)); // "1e+2"
console.log(num.toPrecision(2)); // "99"
console.log(num.toPrecision(3)); // "99.0"
本质上,toPrecision()方法会根据数值和精度来决定调用 toFixed()还是 toExponential()
为了以正确的小数位精确表示数值,这 3 个方法都会向上或向下舍入
注意,oPrecision()方法可以表示带 1~21 个小数位的数值。某些浏览器可能支持更大的范围,但这是通常被支持的范围。
考虑到两者存在同样的潜在问题,因此并不建议直接实例化 Number 对象。
3.isInteger()方法与安全整数
ES6 新增了 Number.isInteger()方法,用于辨别一个数值是否保存为整数,这个方法返回一个布尔值表示是否是整数
console.log(Number.isInteger(1)); // true
console.log(Number.isInteger(1.00)); // true
console.log(Number.isInteger(1.01)); // false
小数位上全是0的情况。被视为整数
IEEE 754 数值格式有一个特殊的数值范围,在这个范围内二进制值可以表示一个整数值
范围从 Number.MIN_SAFE_INTEGER(253 + 1)到 Number.MAX_SAFE_INTEGER(253 1)。对超出这个范围的数值,即使尝试保存为整数,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)); // true|
String
String 是对应字符串的引用类型。要创建一个String 对象,使用 String 构造函数并传入一个字符串值
const str = new String('ac');
String 对象的方法可以在所有字符串原始值上调用
3个继承的方法 valueOf()、toLocaleString()和 toString()都返回对象的原始字符串值
每个 String 对象都有一个 length 属性,表示字符串中字符的数量。
const str = 'string';
console.log(str.length);// 6
注意,即使字符串中包含双字节字符(而不是单字节的 ASCII 字符),也仍然会按单字符来计数
1.JavaScript字符
JavaScript 字符串由 16 位码元(code unit)组成。对多数字符来说,每 16 位码元对应一个字符。
charAt()方法:传入指定数值,返回指定数值的索引处的字符。具体来说,这个方法查找指定位置的16位码元,并返回码元对应的字符
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 码元,返回对应码元的字符
对于 U+0000~U+FFFF 范围内的字符,length、charAt()、charCodeAt()和 fromCharCode()返回的结果都跟预期是一样的。在这个范围之内,每个字符都是用16位,而这几个方法也都基于 16 位码元完成操作。只要字符编码大小与码元大小一一对应,这些方法就能如期工作
console.log(String.fromCharCode(0x61, 0x62, 0x63, 0x64, 0x65));
// abcde
这个对应关系在扩展到 Unicode 增补字符平面时就不成立了。
16 位只能唯一表示65 536 个字符。这对于大多数语言字符集是足够了,在 Unicode 中称为基本多语言平面(BMP)。
为了表示更多的字符,Unicode 采用了一个策略,即每个字符使用另外 16 位去选择一个增补平面。这种每个字符使用两个 16 位码元的策略称为代理对
由于length、charAt()、charCodeAt()和 fromCharCode()是基于16位码元来操作的,当涉及到增补平面时,字符不再是16位码元,而是使用了两个16位码元的代理对,因此会出现误差
let message = "ab😊de";
console.log(message.length); // 6
console.log(message.charAt(1)); // b
console.log(message.charAt(2)); // <?>
console.log(message.charAt(3)); // <?>
console.log(message.charAt(4)); // d
console.log(message.charCodeAt(1)); // 98
console.log(message.charCodeAt(2)); // 55357
console.log(message.charCodeAt(3)); // 56842
console.log(message.charCodeAt(4)); // 100
console.log(String.fromCodePoint(0x1F60A))
console.log(String.fromCharCode(97, 98, 55357, 56842, 100, 101));
fromCharCode()方法中虽然传入了两个16位码元(55357, 56842),但是实际上它们是一对代理对,表示笑脸符号
2.标准格式化方法normalize()
某些 Unicode 字符可以有多种编码方式。有的字符既可以通过一个 BMP 字符表示,也可以通过一个代理对表示
// U+00C5:上面带圆圈的大写拉丁字母 A
console.log(String.fromCharCode(0x00C5)); // Å
// U+212B:长度单位“埃”
console.log(String.fromCharCode(0x212B)); // Å
// U+004:大写拉丁字母 A
// U+030A:上面加个圆圈
console.log(String.fromCharCode(0x0041, 0x030A)); // Å
比较操作符不在乎字符看起来怎么样,它只会比较字符对应的码元表示
let a1 = String.fromCharCode(0x00C5),
a2 = String.fromCharCode(0x212B),
a3 = String.fromCharCode(0x0041, 0x030A);
console.log(a1, a2, a3); // Å, Å, Å
console.log(a1 === a2); // false
console.log(a1 === a3); // false
console.log(a2 === a3); // false
// 尽管它们长得一模一样,也不会相等
Unicode提供了 4种规范化形式,可以将类似上面的字符规范化为一致的格式,无论底层字符的代码是什么
NFD(Normalization Form D)
NFC(Normalization Form C)
NFKD(Normalization Form KD)
NFKC(Normalization Form KC)
normalize()方法:传入一个字符串参数,该参数是4种规范形式简写的其中一种,返回格式化后的结果
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
3.字符串操作方法
拼接方法:concat()和”+“
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!"
console.log(stringValue); // "hello"
虽然 concat()方法可以拼接字符串,但更常用的方式是使用加号操作符(+)
提取字串方法:slice()、substr()和 substring()
方法共性:
都接收一或两个参数,第一个参数表示子字符串开始的位置
都返回调用它们的字符串的一个子字符串
不会修改调用它们的字符串,而只会返回提取到的原始新字符串值
方法差异:
差异点一:第二个参数的意义不同,slice()和substring()第二个参数都表 示提取的子串的结束位置,而substr()方法的第二个参数则表示子串数量
任何情况下,省略第二个参数都意味着提取到字符串末尾。
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()方法将所有负值参数都当成字符串长度加上负参数值,相加仍然是负数时,则按0算
str.slice(-start,-end) === str.slice(str.length-start,str.length-end);
substr()方法将第一个负参数值当成字符串长度加上该值,将第二个负参数值转换为 0
str.slice(-start,-end) === str.slice(str.length-start,0) ==== '';
substring()方法会将所有负参数值都转换为 0,这个方法还会把较小的参数作为子串的起始点
let stringValue = "hello world";
console.log(stringValue.slice(-3)); // "rld"
console.log(stringValue.substring(-3)); // "hello world"
console.log(stringValue.substr(-3)); // "rld"
console.log(stringValue.slice(3, -4)); // "lo w"
console.log(stringValue.substring(3, -4)); // "hel" === stringValue.substring(0,3)
console.log(stringValue.substr(3, -4)); // "" (empty string)
4. 字符串位置方法
有两个方法用于在字符串中定位子字符串:indexOf()和 lastIndexOf()
这两个方法从字符串中搜索传入的字符串,并返回位置(如果没找到,则返回-1)
两者的区别在于,indexOf()方法从字符串开头开始查找子字符串,而 lastIndexOf()方法从字符串末尾开始查找子字符串
let stringValue = "hello world";
console.log(stringValue.indexOf("o")); // 4
console.log(stringValue.lastIndexOf("o")); // 7
这两个方法都可以接收可选的第二个参数,表示开始搜索的位置
let stringValue = "hello world";
console.log(stringValue.indexOf("o", 6)); // 7
console.log(stringValue.lastIndexOf("o", 6)); // 4
循环找到字符串中的目标字符串位置
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]
5. 字符串包含方法
ECMAScript 6 增加了 3 个用于判断字符串中是否包含另一个字符串的方法:startsWith()、endsWith()和 includes()
传入一个字符串参数,返回是否匹配的布尔值
它们之间的区别在于,startsWith()和endsWith()分别只检测从0或者从length-1位置开始的字符是否匹配,这意味着从这两个位置开始的子串如果不匹配,就会返回false。而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")); // false
console.log(message.endsWith("bar", 6)); // true
6. trim()方法
ECMAScript 在所有字符串上都提供了 trim()方法。这个方法会创建字符串的一个副本,删除前、后所有空格符,再返回结果
let stringValue = " hello world ";
let trimmedStringValue = stringValue.trim();
console.log(stringValue); // " hello world "
console.log(trimmedStringValue); // "hello world"
由于 trim()返回的是字符串的副本,因此原始字符串不受影响
trimLeft()和 trimRight()方法分别用于从字符串开始和末尾清理空格符
3.Global属性
Global对象的属性非常多,像undefined、NaN 和 Infinity 等特殊值都是 Global 对象的属性
此外,所有原生引用类型构造函数,都是Global的对象
也就是说,可以这样创建原生引用类型构造函数:
const date = new window.Date();
console.log(date)
// VM22748:2 Fri Dec 15 2023 18:53:15 GMT+0800 (中国标准时间)
4.window对象
虽然ECMA-262没有规定直接访问Global对象的方式,但是浏览器将window对象实现为global的代理
因此,所有在全局作用域中声明的函数和变量都是window的属性
var color = 'blue';
function showColor(){
console.log(window.color);
}
window.showColor();// blue
另一种获取Global对象的通用方式是通过在全局作用域获取this值
const global = (function(){return this})();
Math
ECMAScript 提供了 Math 对象作为保存数学公式、信息和计算的地方
注意,Math 对象上提供的计算要比直接在 JavaScript 实现的快得多,因为 Math 对象上的计算使用了 JavaScript 引擎中更高效的实现和处理器指令。但使用 Math 计算的问题是精度会因浏览器、操作系统、指令集和硬件而异
1.Math的属性
Math的属性主要用于保存一些数学中的特殊值
2.min()和max()方法
min()和max()方法用于确定一组数值中的最小值或最大值。这两个方法都接收任意多个参数
Math.max(...[1,3,2,11,2,6]);
// 11
Math.min(...[1,3,2,11,2,6]);
// 1
// 等同于
Math.min(1,3,2,11,2,6);
由上可知这两个方法可以对数组最值进行快速求值
3.舍入方法
有四个方法用于可以把小数值舍入为整数值
Math.ceil()方法:ceil可以翻译为天花板 ,所以这个方法始终向上舍入为最接近的整数
Math.floor()方法:floor翻译为地板,所以这个方法始终向下舍入为最接近的整数
Math.round()方法执行四舍五入
Math.fround()方法返回数值最接近的单精度(32 位)浮点值表示
console.log(Math.ceil(1.00001));// 2
console.log(Math.floor(1.9)); // 1
console.log(Math.round(1.49));// 1
console.log(Math.fround(1.1));// 1.100000023841858
4.random()随机数生成方法
Math.random()方法随机返回一个0-1范围内的随机数,返回的随机数包含0,但不包含1
let font = [{name:'a'},{name:'b'},{name:'c'},{name:'d'},{name:'e'},{name:'f'},{name:'g'},{name:'h'},{name:'i'},{name:'h'}]
font[Math.floor(Math.random()*10)]
// {name: 'd'}
font[Math.floor(Math.random()*10)]
// {name: 'e'}
font[Math.floor(Math.random()*10)]
// {name: 'h'}
font[Math.floor(Math.random()*10)]
// {name: 'i'}
font[Math.floor(Math.random()*10)]
// {name: 'g'}
font[Math.floor(Math.random()*10)]
// {name: 'd'}
random()方法始终返回一个小数,通过舍入方法可以取到整数
可以基于如下公式使用 Math.random()从一组整数中随机选择一个数:
number = Math.floor(Math.random() * total_number_of_choices + first_possible_value)
其中total_number_of_choices可以限定number的取值范围。Math.random()生成的随机数永远都在包含0不包含1之间,因此随机小数和total_number_of_choices的乘积永远不会大于 total_number_of_choices,因此取值范围是[0-total_number_of_choices-1]
// [1,10]范围
let number = Math.floor(Math.random() * 10 + 1);
// [2,10]范围
let number = 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
注意,如果是为了加密而需要生成随机数(传给生成器的输入需要较高的不确定性),那么建议使用 window.crypto.getRandomValues()
书外补充:
计算机中没有真正的随机数,产出的随机数都是伪随机数,都是通过伪随机数生成器(Pseudo-Random Number Generators,PRNGs)生成,伪随机数生成器以一个称为“种子”(seed)的初始值为输入,然后使用确定性的算法生成一个序列的数字。由于算法的确定性,给定相同的种子,生成器将始终产生相同的序列。这是一种有序且可重复的序列,而不是真正的随机性
5.其他方法
即便这些方法都是由 ECMA-262 定义的,对正弦、余弦、正切等计算的实现仍然取决于浏览器,因为计算这些值的方式有很多种。结果,这些方法的精度可能因实现而异。
第五章小结
JavaScript中的对象称为引用值,几种内置引用类型可以用于创建特定类型的对象
引用值与传统面向对象编程语言中的类相似,但是实现不同,并且不具有传统面向对象编程中的类和接口的概念
Date类型提供关于日期和时间的信息,包括当前日期、时间极其相关计算
RegExp类型是ECMAScript支持正则表达式的接口,提供大多数基础的和高级的正则表达式
JavaScript比较特殊的一点是函数实际上是Function的实例,也就是说函数也是对象。因为函数也是对象,所以它也具有方法,可以用于增强其能力
由于原始值包装类型的存在,JavaScript中部分原始值也可以当作对象来使用,有三种原始值的包装类型:Boolean、Number和String,注意String既是原始值的包装类型,也是一个内置对象
每种包装类型都映射到同名的原始类型。
以读模式访问原始值时,后台会实例化一个原始值包装类型的对象,借助这个对象可以操作相应的数据。
涉及原始值的语句执行完毕后,包装对象就会被销毁
当代码开始执行时,全局上下文会存在两个内置对象:Global和Math。其中,Global对象在大多数ECMAScript实现中都无法直接访问,浏览器将其实现为window对。所有全局变量和属性都是Global对象的属性。Math对象包含辅助完成复杂计算的属性和方法
评论区