JavaScript学习笔记(3):基本引用类型

Date

Date类型将日期保存为自协调世界时(UTC)时间1970年1月1日午夜(零时)至今所经过的毫秒数。使用这种存储格式,Date类型可以精确表示1970年1月1日之前及之后285616年的日期。

基本方法

创建日期

使用new操作符调用Date构造函数创建日期对象。在不给Date构造函数传参数的情况下,创建的对象将保存当前日期和时间,静态方法Date.now()方法也是获得当前的时间毫秒数。要基于其他日期和时间创建日期对象,必须传入其毫秒表示。

更方便的方法是使用Date.parse()Date.UTC()来返回一个时间的毫秒表示,用以初始化一个Date对象。

Date.parse()方法接收一个表示日期的字符串参数,尝试将这个字符串转换为表示该日期的毫秒数。可接收的参数格式有:

  • “月/日/年”,如"1/1/2020"
  • “月名 日, 年”,如"May 23, 2019"
  • “周几 月名 日 年 时:分:秒 时区”,如"Tue May 232019 00:00:00 GMT-0700"
  • ISO 8601扩展格式“YYYY-MM-DDTHH:mm:ss.sssZ”,如"2019-05-23T00:00:00"

如果传给Date.parse()的字符串并不表示日期,则该方法会返回NaN。

Date.UTC()方法也返回日期的毫秒表示,但使用的是跟Date.parse()不同的信息来生成这个值。

传给Date.UTC()的参数是年、零起点月数(011)、日(131)、时(0~23)、分、秒和毫秒。这些参数中,只有年和月是必需的。

如果直接把表示日期的字符串传给Date构造函数,那么Date会自动调用Date.parse()

如果把传给Date.UTC()的参数传给Date构造函数,同样会默认调用Date.UTC(),但是区别是显式使用Date.UTC()生成的是格林威治标准时间,而直接使用Date构造函数生成的是本地时区的时间。

有以下例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let dt1 = new Date();
let dt2 = new Date(Date.now());
let dt3 = new Date(Date.parse("5/1/2020"));
let dt4 = new Date(Date.parse("May 1, 2020"));
let dt5 = new Date(Date.parse("我不是日期"));
let dt6 = new Date(Date.UTC(2020,4,1,13,14,5,21));
let dt7 = new Date("5/1/2020");
let dt8 = new Date(2020,4,1,13,14,5,21);
console.log(Number(dt1)); // 1638845562036
console.log(Number(dt2)); // 1638845562036
console.log(Number(dt3)); // 1588262400000
console.log(Number(dt4)); // 1588262400000
console.log(Number(dt5)); // NaN
console.log(Number(dt6)); // 1588338845021
console.log(Number(dt7)); // 1588262400000
console.log(Number(dt8)); // 1588338845021

重写的继承方法

Date类型重写了toLocaleString()toString()valueOf()方法。

  • toLocaleString()方法:返回与浏览器运行的本地环境一致的日期和时间,格式中包含针对时间的AM(上午)或PM(下午),但不包含时区信息(具体格式可能因浏览器而不同)。
  • toString()方法:返回带时区信息的日期和时间,时间是以24小时制(0~23)表示的。
  • valueOf()方法:返回的是日期的毫秒表示,操作符(如小于号和大于号)可以直接使用它返回的值。

对于上述日期对象,有以下例子:

1
2
3
console.log(dt1); // Tue Dec 07 2021 10:56:26 GMT+0800 (中国标准时间)
console.log(dt1.toLocaleString()); // 2021/12/7 上午10:56:26
console.log(dt1.valueOf()); // 1638845786774

将日期对象直接输出在控制台会自动调用它的toString()方法。

日期格式化方法

Date类型有几个专门用于格式化日期的方法,它们都会返回字符串:

  • toDateString():显示日期中的周几、月、日、年(格式特定于实现);
  • toTimeString():显示日期中的时、分、秒和时区(格式特定于实现);
  • toLocaleDateString():显示日期中的周几、月、日、年(格式特定于实现和地区);
  • toLocaleTimeString():显示日期中的时、分、秒(格式特定于实现和地区);
  • toUTCString():显示完整的UTC日期(格式特定于实现)。

有以下例子:

1
2
3
4
5
6
7
let dt = new Date();
console.log(dt.toDateString()); // Tue Dec 07 2021
console.log(dt.toTimeString()); // 11:02:35 GMT+0800 (中国标准时间)
console.log(dt.toLocaleDateString()); // 2021/12/7
console.log(dt.toLocaleTimeString()); // 上午11:02:35
console.log(dt.toUTCString()); // Tue, 07 Dec 2021 03:02:35 GMT
console.log(dt.toJSON()); // 2021-12-07T03:02:35.844Z

这些方法的效果同样可能因浏览器而异。

其他还有一些时间和日期方法,比如获得日期中的时分秒的值、设置时分秒中的某个值等,需要用到时再查API即可。

RegExp

使用正则表达式

ECMAScript通过RegExp类型支持正则表达式。正则表达式使用类似Perl的简洁语法来创建:

let expression = /pattern/flags;

这个正则表达式的pattern(模式)可以是任何简单或复杂的正则表达式,包括字符类、限定符、分组、向前查找和反向引用。每个正则表达式可以带零个或多个flags(标记),用于控制正则表达式的行为。下面给出了表示匹配模式的标记:

  • g:全局模式,表示查找字符串的全部内容,而不是找到第一个匹配的内容就结束。
  • i:不区分大小写,表示在查找匹配时忽略pattern和字符串的大小写。
  • m:多行模式,表示查找到一行文本末尾时会继续查找。
  • y:粘附模式,表示只查找从lastIndex开始的字符串。
  • u: Unicode模式,启用Unicode匹配。
  • s:dotAll模式,表示元字符.匹配任何字符(包括\n或\r)。

除了直接创建之外,正则表达式还可以使用构造函数来创建。

有以下例子:

1
2
3
4
let p1 = /at/g; // 匹配字符串中所有的"at"
let p2 = /[bc]at/i; // 匹配第一个"bat"或"cat",忽略大小写
let p3 = /.at/gi; // 将全局模式和忽略大小写结合
let p4 = new RegExp(".at", "gi"); // 使用构造函数创建,等价于p3

正则表达式实例属性

每个正则表达式实例有如下属性:

  • global:布尔值,表示是否设置了g标记。
  • ignoreCase:布尔值,表示是否设置了i标记。
  • unicode:布尔值,表示是否设置了u标记。
  • sticky:布尔值,表示是否设置了y标记。
  • lastIndex:整数,表示在源字符串中下一次搜索的开始位置,始终从0开始。
  • multiline:布尔值,表示是否设置了m标记。
  • dotAll:布尔值,表示是否设置了s标记。
  • source:正则表达式的字面量字符串(不是传给构造函数的模式字符串),没有开头和结尾的斜杠。
  • flags:正则表达式的标记字符串。始终以字面量而非传入构造函数的字符串模式形式返回(没有前后斜杠)。

通过这些属性可以获得正则表达式的信息,但是实际中使用不多。

正则表达式实例方法

exec方法

RegExp实例的主要方法是exec(),主要用于配合捕获组使用。

这个方法只接收一个参数,即要应用模式的字符串。

如果找到了匹配项,则返回包含第一个匹配信息的数组;如果没找到匹配项,则返回null。

返回的数组是包含两个额外属性index和input的Array实例:index是字符串中匹配模式的起始位置,input是要查找的字符串。这个数组的第一个元素是匹配整个模式的字符串,其他元素是与表达式中的捕获组匹配的字符串。如果模式中没有捕获组,则数组只包含第一个元素。

有以下例子:

1
2
3
4
5
6
7
8
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()方法会返回下一个匹配的信息,并更新lastIndex的值。如果没有设置全局标记,则无论对同一个字符串调用多少次exec(),也只会返回第一个匹配的信息。

test方法

test()方法接收一个字符串参数,如果输入的文本与模式匹配,则参数返回true,否则返回false。

这个方法适用于只想测试模式是否匹配,而不需要实际匹配内容的情况,因此经常用在if语句中。

其他

正则表达式构造函数属性

正则表达式的构造函数上有一些属性,是类似于静态属性的存在,这些属性适用于作用域中的所有正则表达式,而且会根据最后执行的正则表达式操作而变化,主要有下:

全名 简写 说明
input $_ 最后搜索的字符串(非标准特性)
lastMatch $& 最后匹配的文本
lastParen $+ 最后匹配的捕获组(非标准特性)
leftContext $ input字符串中出现在lastMatch前面的文本
rightContext $' input字符串中出现在lastMatch后面的文本

未实现特性

JavaScript中有一些不支持的正则表达式的高级特性,主要包括:

  • \A和\Z锚(分别匹配字符串的开始和末尾);
  • 联合及交叉类;
  • 原子组;
  • x(忽略空格)匹配模式;
  • 条件式匹配;
  • 正则表达式注释。

原始值包装类型

当用到某个原始值的方法或属性时,后台都会创建一个相应的原始包装类型的临时对象,从而暴露出操作原始值的各种方法,使得我们也可以对原始值使用各种方法。

原始值包装类型和普通类型的区别只在于对象的生命周期。使用new创建的对象实例会在离开作用域之后被销毁,而代码执行过程中自动创建的包装对象只存在于访问它的那行代码的执行期间,用后就被销毁了。

Object包装

使用new来新建一个Object对象并给它传递一个原始值,它会自动将该原始值包装为对应类型的对象:

1
2
3
4
5
6
7
8
9
let s = new Object("Dasen");
let n = new Object(2);
let b = new Object(true);
console.log(typeof s); // object
console.log(typeof n); // object
console.log(typeof b); // object
console.log(s instanceof String); // true
console.log(n instanceof Number); // true
console.log(b instanceof Boolean); // true

这里要注意这与调用转型函数不一样,这里不是类型转换,而是包装。

String

String对象的方法可以在所有字符串原始值上调用。3个继承的方法valueOf()toLocaleString()toString()都返回对象的原始字符串值。

每个String对象都有一个length属性,表示字符串中字符的数量。

String包装类型提供了许多方法来操作字符串。

字符与编码

对于UTF16字符,有以下方法:

  • charAt()方法:返回给定索引位置的字符。
  • charCodeAt()方法:可以查看指定码元的字符编码,这个方法返回指定索引位置的码元值。
  • fromCharCode()方法:用于根据给定的UTF-16码元创建字符串中的字符。这个方法可以接受任意多个数值,并返回将所有数值对应的字符拼接起来的字符串。

有以下例子:

1
2
3
4
5
let s = "我是大森";
console.log(s.charAt(1)); // 是
console.log(s.charCodeAt(2)); // 22823
console.log(s.charCodeAt(3).toString(16)); // 68ee
console.log(String.fromCharCode(22823,0x68EE)); // 大森

对于U+0000~U+FFFF范围内的字符,lengthcharAt()charCodeAt()fromCharCode()返回的结果都跟预期是一样的,因为这些操作都是基于UTF16编码的字符串操作的,而对于UTF32编码(即对每个超出范围的字符添加一个16位的增补平面表示),这些操作还把它们作为16位编码来操作,就会把32位编码拆开,就出现了错误,如:

1
2
3
4
5
let s = "我是大森😊";
console.log(s.charAt(4)); //
console.log(s.charAt(5)); //
console.log(s.charCodeAt(4)); // 55357
console.log(s.charCodeAt(5)); // 56842

这时要想正确操作,需要将一个32位编码视为两个16位编码构成的代理对,并使用codePointAt()来代替charCodeAt(),使用fromCodePoint()来代替fromCharCode(),这些操作会识别出完整的32位编码:

1
2
3
4
5
6
7
8
9
let s = "我是大森😊";
console.log(s.charAt(4)); //
console.log(s.charAt(5)); //
console.log(s.charCodeAt(4)); // 55357
console.log(s.charCodeAt(5)); // 56842
console.log(s.codePointAt(4)); // 128522
console.log(s.codePointAt(5)); // 56842
console.log(String.fromCharCode(55357,56842)); // 😊
console.log(String.fromCodePoint(128522)); // 😊

对于字符串的迭代器,不用关心编码的问题,它总是能返回正确的结果:

1
2
3
4
5
6
7
8
9
let s = "我是大森😊";
for(const c of s) {
console.log(c);
}
// 我
// 是
// 大
// 森
// 😊

字符串操作方法

(1)字符串操作方法:

  • concat()方法:用于将一个或多个字符串拼接成一个新字符串。当然更推荐使用加号拼接字符串。
  • slice()方法:获得子串。两个参数分别是开始和结束的索引区间(左闭右开);当两个参数中有负值时,负值解释为字符串长度加上这个负值。
  • substring()方法:获得子串。两个参数分别是开始和结束的索引区间(左闭右开);当两个参数中有负值时,负值都会被当作0;
  • substr()方法:获得子串。两个参数分别是开始索引和子串长度;当两个参数中有负值时,第一个参数负值会解释为字符串长度加上这个负值;第二个参数的负值会被当作0。

有如下例子:

1
2
3
4
5
6
7
8
9
10
11
12
let s = "Hello";
console.log(s.concat(" world", "!")); // Hello world!
console.log(s.slice(1,4)); // ell
console.log(s.slice(1)); // ello
console.log(s.slice(-4,-1)); // ell
console.log(s.substring(1,4)); // ell
console.log(s.substring(1)); // ello
console.log(s.substring(-4,4)); // Hell
console.log("输出空串:", s.substring(-4,-1)); // 输出空串:
console.log(s.substr(1,3)); // ell
console.log(s.substr(-4,3)); // ell
console.log("输出空串:", s.substr(-4,-1)); // 输出空串:

(2)字符串位置方法:

  • indexOf()方法:查找字符串,返回查找的索引,没找到返回-1。第二个可选的参数是开始查找的索引。
  • lastIndexOf()方法:查找字符串,但从后往前查找,返回查找的索引,没找到返回-1。第二个可选的参数是开始查找的索引。

有以下例子:

1
2
3
4
5
let s = "Hello world!";
console.log(s.indexOf("o")); // 4
console.log(s.lastIndexOf("o")); // 7
console.log(s.indexOf("o",5)); // 7
console.log(s.lastIndexOf("o",6)); // 4

(3)字符串包含方法:
ECMAScript 6增加了三个用于判断字符串中是否包含另一个字符串的方法:

  • startsWith()方法:当子串存在于字符串开头时返回true,否则返回false。
  • endsWith()方法:当子串存在于字符串末尾时返回true,否则返回false。
  • includes()方法:当子串存在于字符串中时返回true,否则返回false。

有以下例子:

1
2
3
4
5
6
let s = "hello";
console.log(s.startsWith("he")); // true
console.log(s.startsWith("lo")); // false
console.log(s.endsWith("he")); // false
console.log(s.endsWith("lo")); // true
console.log(s.includes("ell")); // true

(4)大小写转换方法:

  • toLowerCase()方法:转为小写。
  • toLocaleLowerCase()方法:依据具体地区的规则转为小写。一般与toLowerCase()相同。
  • toUpperCase()方法:转为大写。
  • toLocaleUpperCase()方法:依据具体地区的规则转为大写。一般与toUpperCase()相同。

(5)字符串模式匹配方法:

  • match()方法:接收一个字符串或正则表达式,返回的结果与正则表达式的exec方法一样。
  • search()方法:参数同上,但返回第一个匹配的索引位置,没找到返回-1。
  • replace()方法:第一个参数同上,第二个参数为一个字符串或一个函数;如果第一个参数为字符串,那么只会替换第一个匹配的子串,如果想替换全部,则需要提供一个标记为全局模式的正则表达式;第二个参数为一个函数时,这个函数会收到三个参数:匹配的字符串(或捕获组)、匹配项的开始位置、整个字符串,返回一个字符串,表示替换为的字符串。
  • split()方法:第一个参数为字符串或正则表达式,依据此来分割原字符串,返回一个数组,第二个参数指定返回的数组大小不超过多少。

(6)其他方法:

  • trim()方法:去除字符串前后空白符。
  • trimRight()方法:去除右侧空白符。
  • trimLeft()方法:去除左侧空白符。
  • repeat()方法:字符串重复若干次。参数为重复的次数。
  • padStart()方法:补字符串到指定长度,给定字符串重复补在开头。
  • padEnd()方法:补字符串到指定长度,给定字符串重复补在末尾。
  • localeCompare()方法:比较字符串,按照字母表顺序(取决于各国语言的字母表),原字符串排在参数字符串前面返回-1(或其他负值),原字符串排在参数字符串后面返回1(或其他正值),相等返回0。

有以下例子:

1
2
3
console.log("   hello \n ".trim()); // hello
console.log("呐呐".repeat(5)); // 呐呐呐呐呐呐呐呐呐呐
console.log("Dasen".padEnd(10,"-").padStart(15,"-")); // -----Dasen-----

Boolean

要创建一个Boolean对象,就使用Boolean构造函数并传入true或false。

Boolean的实例会重写valueOf()方法,返回一个原始值true或false。toString()方法被调用时也会被覆盖,返回字符串"true""false"

使用Boolean对象会导致无论是真值还是假值包装之后都会被判断为真值:

1
2
3
4
5
6
7
8
9
10
let b1 = new Boolean(true);
let b2 = new Boolean(false);
if (b1) {
console.log("b1解释为真");
}
if (b2) {
console.log("b2解释为真");
}
// b1解释为真
// b2解释为真

Number

与Boolean类型一样,Number类型重写了valueOf()toLocaleString()toString()方法。valueOf()方法返回Number对象表示的原始数值,另外两个方法返回数值字符串。toString()方法可选地接收一个表示基数的参数,并返回相应基数形式的数值字符串。

该包装类型还具有以下常用方法:

  • toFixed()方法:返回包含指定小数点位数的数值字符串。
  • toExponential()方法:返回以科学记数法(也称为指数记数法)表示的数值字符串。
  • toPrecision()方法:会根据情况返回最合理的输出结果,可能是固定长度,也可能是科学记数法形式。这个方法接收一个参数,表示结果中数字的总位数(不包含指数)。本质上,toPrecision()方法会根据数值和精度来决定调用toFixed()还是toExponential()

为了以正确的小数位精确表示数值,这3个方法都会进行四舍五入。

有如下例子:

1
2
3
4
5
6
let n = new Number(3);
let f = new Number(3.1415926535);
console.log(n.toString(2)); // 11
console.log(f.toFixed(2)); // 3.14
console.log(f.toExponential()); // 3.1415926535e+0
console.log(f.toPrecision(5)); // 3.1416

这些包装类型的方法都可以在Number原始值上调用。

单例内置对象

内置对象的定义是“任何由ECMAScript实现提供、与宿主环境无关,并在ECMAScript程序开始执行时就存在的对象”。主要有两个单例内置对象:Global和Math。我们主要关住它们提供的属性和方法。

Math

属性

Math的属性主要用于保存一些数学中的特殊值:

  • Math.E:自然对数的底数e。
  • Math.LN10:ln10的值。
  • Math.LN2:ln2的值。
  • Math.LOG2E:2为底,log e的值。
  • Math.LOG10E:10为底,log e的值。
  • Math.PI:圆周率pi的值。
  • Math.SQRT1_2:1/2的平方根。
  • Math.SQRT2:根号2。

最大最小值方法

max()min()函数,可以接收任意数量的值,返回其中的最大最小值。

舍入方法

用来对小数取整的舍入方法:

  • Math.ceil()方法:向上取整。
  • Math.floor()方法:向下取整。
  • Math.round()方法:四舍五入。
  • Math.fround()方法:返回数值最接近的单精度(32位)浮点值表示。

其他方法

还有一些其他的常用方法:

  • random()方法:随机数方法,返回一个0~1之间的随机数,包括0但不包括1。
  • abs(x)方法:随机数方法,返回x的绝对值。
  • pow(x,y)方法:返回x的y次方。
  • sqrt(x)方法:返回x的平方根。

Global

ECMA-262没有规定直接访问Global对象的方式,但浏览器将window对象实现为Global对象的代理,浏览器中的window对象实际上就是Global对象。对象上主要有像undefined和NaN这样的特殊值,以及内置类型的构造函数如Array和Object等,还有一些函数如isNaN()parseInt()等。

URL编码方法

URL中不能含有某些特殊字符(比如空格),encodeURI()encodeURIComponent()方法用于编码统一资源标识符(URI),以便传给浏览器,同时又以特殊的UTF-8编码替换掉所有无效字符。

encodeURI()将会编码整个URL,将整个URL传入给它,它会忽略掉斜杠、井号等URL中固有的字符,而encodeURIComponent()方法用来编码URL的一部分,只需要把需要编码的URL片段作为参数,它会编码所有不应该在URL域中出现的字符。

与它们相对解码方法是decodeURI()decodeURIComponent()

eval方法

它接收一个字符串,会将字符串解释为JS代码来执行。它之中运行的代码有一个单独的作用域挂在当前作用域链的前端,因此在其中是可以顺着作用域访问到前面的代码中的变量的。

eval中声明的函数在之后的代码中也能够调用,但在严格模式下不可以。




* 你好,我是大森。如果文章内容帮到了你,你可通过下方付款二维码支持作者 *