第5章 基本引用类型
第五章 基本引用类型
Date
let now = new Date(); // Fri Apr 14 2023 13:51:39 GMT+0800 (中国标准时间)
let someDate = new Date(Date.parse('May 23, 2019')); // Thu May 23 2019 00:00:00 GMT+0800 (中国标准时间)
let someDate2 = new Date('May 23, 2019'); // Thu May 23 2019 00:00:00 GMT+0800 (中国标准时间) 因为Date会在后台调用Date.parse()
// Date.UTC(年,月(0-11),日(1-31),时(0-23),分,秒,毫秒)
let allFives = new Date(Date.UTC(2005, 4, 5, 17, 55, 55)); // Fri May 06 2005 01:55:55 GMT+0800 (中国标准时间)
let allFives2 = new Date(2005, 4, 5, 17, 55, 55); // Thu May 05 2005 17:55:55 GMT+0800 (中国标准时间)
// Date.now()方法 返回表示方法执行时日期和时间的毫秒数
// 起始时间
let start = Date.now();
// 调用函数
doSomething();
// 结束时间
let stop = Date.now(),
result = stop - start;
GMT时间:
格林威治平时 规定太阳每天经过位于英国伦敦郊区的皇家格林威治天文台的时间为中午12点。格林威治平时 (GMT, Greenwich Mean Time) 作为世界时间标准(UT, Universal Time)。由此也确定了全球24小时自然时区的划分,所有时区都以和 GMT 之间的偏移量做为参考。1972年之前,格林威治时间(GMT)一直是世界时间的标准。1972年之后,GMT 不再是一个时间标准了。
UTC时间:
UTC 是现在全球通用的时间标准,全球各地都同意将各自的时间进行同步协调。UTC 时间是经过平均太阳时(以格林威治时间GMT为准)、地轴运动修正后的新时标以及以秒为单位的国际原子时所综合精算而成。
GMT是前世界标准时,UTC是现世界标准时。
UTC 比 GMT更精准,以原子时计时,适应现代社会的精确计时。
但在不需要精确到秒的情况下,二者可以视为等同。
每年格林尼治天文台会发调时信息,基于UTC。
RegExp
语法:
let expression = /pattern/flags;
pattern:可以是任何简单或者复杂的正则表达式,包括字符类、限定符、分组、向前查找和反向引用。
flags:0个或者多个,用于控制正则表达式的行为
g:全局模式,表示查找字符串的全部内容,而不是找到第一个匹配的内容就结束。
i:不区分大小写,表示在查找匹配时忽略 pattern 和字符串的大小写。
m:多行模式,表示查找到一行文本末尾时会继续查找。
y:粘附模式,表示只查找从 lastIndex 开始及之后的字符串。
u:Unicode 模式,启用 Unicode 匹配。
s:dotAll 模式,表示元字符.匹配任何字符(包括\n 或\r)。
下述例子都是使用字面量形式定义的,正则表达式可以使用RegExp构造函数来创建,它接收两个参数:模式字符串和(可选)标记字符串。
// 匹配字符串中的所有"at"
let pattern1 = /at/g;
// 匹配第一个"bat"或"cat",忽略大小写
let pattern2 = /[bc]at/i;
// 匹配所有以"at"结尾的三字符组合,忽略大小写
let pattern3 = /.at/gi;
所有的元字符在模式中也必须转义 ( [ { \ ^ $ | ) ] } ? * + . 即在所有元字符之前添加反斜杠/
下述例子使用正则表达式构建
// 匹配第一个"bat"或"cat",忽略大小写
let pattern1 = /[bc]at/i;
// 跟 pattern1 一样,只不过是用构造函数创建的 注意:参数都是字符串形式
let pattern2 = new RegExp("[bc]at", "i");
因为RegExp的模式参数是字符串,所以在某些情况下需要二次转义。
/\[bc\]at/ 对应的字符串 “\\[bc\\]at”
RegExp实例方法:exec() 主要用于配合捕获组使用,返回数组是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"
RegExp另一个方法:test(),接收一个字符串参数,如果输入的文本与模式匹配,则参数 返回 true,否则返回 false。这个方法适用于只想测试模式是否匹配,而不需要实际匹配内容的情况。
RegExp构造函数属性
| 全 名 | 简 写 | 说 明 |
|---|---|---|
| input | $_ | 最后搜索的字符串(非标准特性) |
| lastMatch | $& | 最后匹配的文本 |
| lastParen | $+ | 最后匹配的捕获组(非标准特性) |
| leftContext | $` | input 字符串中出现在 lastMatch 前面的文本 |
| rightContext | $’ | input 字符串中出现在 lastMatch 后面的文本 |
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
}
原始值包装类型
ES提供了3种特殊的引用类型:Boolean、Number、String,这些类型具有引用类型一样的特点,但也具有与各自原始类型对应的特殊行为。
let s1 = "some text";
let s2 = s1.substring(2);
上述代码后台中的过程:
1、创建一个String类型的实例
2、调用实例上的特定方法
3、销毁实例
引用类型与原始值包装类型的主要区别在于对象的生命周期。在通过 new 实例化引用类型后,得到的实例会在离开作用域时被销毁,而自动创建的原始值包装对象则只存在于访问它的那行代码执行期间。这意味着不能在运行时给原始值添加属性和方法。
使用 new 调用原始值包装类型的构造函数,与调用同名的转型函数并不一样。
let value = "25";
let number = Number(value); // 转型函数
console.log(typeof number); // "number"
let obj = new Number(value); // 构造函数
console.log(typeof obj); // "object"
Number
let num = 10;
// toString方法可选地接收一个表示基数的参数,并返回相应基数形式的数值字符串。
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"
// 位数超过则四舍五入
let num = 10.005;
console.log(num.toFixed(2)); // "10.01"
另一个用于格式化数值的方法是 toExponential(),返回以科学记数法(也称为指数记数法)表 示的数值字符串。
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 个方法都会向上或向下舍入。
原始数值在调用 typeof 时始终返回”number”,而 Number 对象则返回”object”。类似地,Number 对象是 Number 类型的实例,而原始数值不是。
// Number.isInteger()方法,用于辨别一个数值是否保存为整数。
// 注意:小数位的0可能会让人误认为数值是一个浮点数
console.log(Number.isInteger(1)); // true
console.log(Number.isInteger(1.00)); // true
console.log(Number.isInteger(1.01)); // false
String
js字符串由16位码元(code unit)组成,对多数字符来说,每16位码元对应一个字符。换 句话说,字符串的 length 属性表示字符串包含多少 16 位码元:
let message = "abcde";
console.log(message.length); // 5
console.log(message.charAt(2)); // "c"
字符串操作方法:
concat():将一个或者多个字符串拼接成一个新字符串
let stringValue = "hello ";
let result = stringValue.concat("world", "!");
console.log(result); // "hello world!"
console.log(stringValue); // "hello"
拼接字符串更加常用的方式是使用加号操作符+。
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"
当然还可以传参为负数的情况,但是我现在没看懂,可以之后再补上。
字符串位置方法:
都有两个参数:第一个参数表示需要查找的字符,第二个参数(可选)表示开始搜索的位置。找到了返回当前字符的位置,没找到返回-1。
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]
字符串包含方法:
startsWith()、 endsWith()和 includes(),它们的区别在于,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()方法可以接收第二个参数,表示开始搜索的位置,如果传入第二个参数,则意味着这两个方法会从指定位置向着字符串末尾搜索,忽略该位置之前的所有字符。
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
endsWith()接收可选的第二个参数表示字符串末尾的位置,如果不提供则默认是字符串的长度
let message = "foobarbaz";
console.log(message.endsWith("bar")); // false
console.log(message.endsWith("bar", 6)); // true
trim()、trimLeft()、trimRight()
注意:trim会生成原来字符串的副本,因此就算有所改动,改动前后的字符都会被保留。
let stringValue = " hello world ";
let trimmedStringValue = stringValue.trim();
console.log(stringValue); // " hello world "
console.log(trimmedStringValue); // "hello world"
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()方法会复制字符串,如果小于指定长度,则在相应一边填充字符,直至 满足长度条件。这两个方法的第一个参数是长度,第二个参数是可选的填充字符串,默认为空格
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......"
console.log(stringValue.padStart(8, "bar")); // "barbafoo"
console.log(stringValue.padStart(2)); // "foo"
console.log(stringValue.padEnd(8, "bar")); // "foobarba"
console.log(stringValue.padEnd(2)); // "foo"
字符串迭代与解构
字符串的原型上暴露了一个@@iterator方法,表示可以迭代字符串的每个字符
手动使用该迭代器
let message = "abc";
let stringIterator = message[Symbol.iterator]();
console.log(stringIterator.next()); // {value: "a", done: false}
console.log(stringIterator.next()); // {value: "b", done: false}
console.log(stringIterator.next()); // {value: "c", done: false}
console.log(stringIterator.next()); // {value: undefined, done: true}
在for-of循环中可以通过这个迭代器按序访问每个字符
for(const c of "abcdef") {
console.log(c);
}
// a
// b
// c
// d
// e
有了该迭代器,可以更加方便地把字符串分割为字符数组
let message = "abcde";
console.log([...message]); // ['a','b','c','d','e']
字符串大小写转换
toLowerCase()、toUpperCase():原来就有,与java.lang.String中的方法同名
toLocaleLowerCase()、toLocaleUpperCase():旨在基于特定地区实现(在很多地区,地区特定的方法与通用的方法是一样的。但在少数语言中(如土耳其语), Unicode 大小写转换需应用特殊规则,要使用地区特定的方法才能实现正确转换。如果不知道代码涉及什么语言,则最好使用地 区特定的转换方法。)
字符串模式匹配方法
match() :接收一个参数,可以是一个正则表达式字符串,也可以是一个RegExp对象,返回的数组与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():参数为正则表达 式字符串或 RegExp 对象,返回模式第一个匹配的位置索引,如果没找到则返回1。search() 始终从字符串开头向后匹配模式。
let text = "cat, bat, sat, fat";
let pos = text.search(/at/);
console.log(pos); // 1
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"
第二个参数还可以是一个函数,函数中会有3个参数:1)匹配的字符串 2)匹配项在字符串中的位置 3)整个字符串
该函数应该返回一个字符串,表示应该把匹配项替换成什么,使用函数作为第二个参数可以更细致地控制替换过程。
function htmlEscape(text) {
return text.replace(/[<>"&]/g, function(match, pos, originalText) {
switch(match) {
case "<":
return "<";
case ">":
return ">";
case "&":
return "&";
case "\"":
return """;
}
});
}
console.log(htmlEscape("<p class=\"greeting\">Hello world!</p>"));
// "<p class="greeting">Hello world!</p>"
split():根据传入的分隔符将字符串拆分成数组。作为分隔符的参数可以是字符串,也可以是 RegExp 对象。(字符串分隔符不会被这个方法当成 正则表达式。)还可以传入第二个参数,即数组大小,确保返回的数组不会超过指定大小。
localeCompare():比较两个字符串(通常返回以下三个值中的一个)
如果按照字母表顺序,字符串应该排在字符串参数前头,则返回负值。(通常是-1,具体还要看 与实际值相关的实现。)
如果字符串与字符串参数相等,则返回 0。
如果按照字母表顺序,字符串应该排在字符串参数后头,则返回正值。(通常是 1,具体还要看 与实际值相关的实现。)
单例内置对象
内置对象:任何由ECMAScript实现提供、与宿主环境无关,并在ECMAScript程序开始执行时就存在的对象。前面我们已经接触了大部分内置对象,包括 Object、Array 和 String。
Global
Global 对象是 ECMAScript 中最特别的对象,因为代码不会显式地访问它,
1、URL 编码方法
encodeURI():对整个 URI 进行编码
encodeURIComponent():用于编码 URI 中单独的组件
2、eval()
可能是整个 ECMAScript 语言中最强大的了,这个方法就是一个完 整的 ECMAScript 解释器,它接收一个参数,即一个要执行的 ECMAScript(JavaScript)字符串。
eval("console.log('hi')");
// 与下面的代码等价
console.log("hi");
当解释器发现 eval()调用时,会将参数解释为实际的 ECMAScript 语句,然后将其插入到该位置。 通过 eval()执行的代码属于该调用所在上下文,被执行的代码与该上下文拥有相同的作用域链。这意 味着定义在包含上下文中的变量可以在 eval()调用内部被引用
let msg = "hello world";
eval("console.log(msg)"); // "hello world"
// 类似地,还可以在eval方法中定义函数
eval("function sayHi() { console.log('hi'); }");
sayHi();
注意:通过eval定义的变量和函数都不会被提升,严格模式下,在eval内部创建的变量和函数都无法被外部访问,也就是说,上述两个例子会报错。
window对象:虽然 ECMA-262 没有规定直接访问 Global 对象的方式,但浏览器将 window 对象实现为 Global 对象的代理。因此,所有全局作用域中声明的变量和函数都变成了 window 的属性。
Math
min()和 max()方法用于确定一组数值中的最小值和最大值。这两个方法都接收任意多个参数
let max = Math.max(3, 54, 32, 16);
console.log(max); // 54
let min = Math.min(3, 54, 32, 16);
console.log(min); // 3
Math.ceil() 向上取整、Math.floor()向下取整、Math.round()四舍五入 和 Math.fround() 返回数值最接近的单精度(32位)浮点值表示:舍入为整数
Math.random()方法返回一个 0~1 范围内的随机数,其中包含 0 但不包含 1。
从 1~10 范围内随机选择一个数:
// Math.random始终返回小数,即便乘以一个数再加上一个数也是小数
let num = Math.floor(Math.random() * 10 + 1);
小结
由于原始值包装类型的存在,JavaScript 中的原始值可以被当成对象来使用。有 3 种原始值包装类 型:Boolean、Number 和 String。它们都具备如下特点。
每种包装类型都映射到同名的原始类型。
以读模式访问原始值时,后台会实例化一个原始值包装类型的对象,借助这个对象可以操作相 应的数据。
涉及原始值的语句执行完毕后,包装对象就会被销毁。
当代码开始执行时,全局上下文中会存在两个内置对象:Global 和 Math。其中,Global 对象在 大多数 ECMAScript 实现中无法直接访问。不过,浏览器将其实现为 window 对象。所有全局变量和函 数都是 Global 对象的属性。Math 对象包含辅助完成复杂计算的属性和方法。





