跳转至

JavaScript内建对象

约 6678 个字 513 行代码 1 张图片 预计阅读时间 29 分钟

解构赋值

基本使用

在前面进行赋值操作时,每一次只能对一个变量进行赋值,如果有很多变量,则需要依次赋值,这个过程就会很繁琐。在JavaScript中,为了解决这个问题,提出了一种新的赋值方式:解构赋值

解构赋值的基本语法如下:

JavaScript
1
[变量1, 变量2, 变量3, ...] = [值1, 值2, 值3, ...]

例如下面的代码:

JavaScript
1
2
let [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1 2 3

如果右侧为数组或者是对象也可以使用解构赋值:

JavaScript
1
2
3
let arr = [1, 2, 3];
let [a, b, c] = [arr[0], arr[1], arr[2]];
console.log(a, b, c); // 1 2 3

但是如果右侧是数组或者对象,手动取出其中的值再进行赋值不够优雅,所以也可以直接将数组引用或者对象引用给左侧的变量,例如下面的代码:

JavaScript
1
2
3
let arr = [1, 2, 3];
let [a, b, c] = arr;
console.log(a, b, c); // 1 2 3

当数组引用给左侧的变量集时,数组中的元素会依次赋值给左侧对应的变量,所以如果数组中的元素个数少于左侧的变量数,则多余的变量值为undefined,例如下面的代码

JavaScript
1
2
3
let arr = [1, 2];
let [a, b, c] = arr;
console.log(a, b, c); // 1 2 undefined

有了解构赋值,如果函数返回一个数组,则接收函数的返回值也可以使用解构赋值同时用多个变量接收函数的返回值,函数的返回值会依次填充变量,例如下面的代码:

JavaScript
1
2
3
4
5
6
function fn() {
    return [1,2,3];
}

let [a, b, c] = fn();
console.log(a, b, c); // 1 2 3

解构赋值用于变量交换

在使用解构赋值交换变量中的值前,先想想之前交换变量的写法:

JavaScript
1
2
3
4
5
6
7
8
let a = 10;
let b = 20;

let temp = a;
a = b;
b = temp;

console.log(a, b); // 20 10

有了解构赋值后,就可以写成下面的代码:

JavaScript
1
2
3
4
5
6
let a = 10;
let b = 20;

[a, b] = [b, a];

console.log(a, b); // 20 10

这个代码看似会产生变量的覆盖,但是实际上在赋值符号的右侧方括号中,变量b代表的实际上是20,变量a代表的实际上是10,在整个赋值过程中,右侧的值使用都是字面量,不会改变,所以上面的解构赋值相当于[a, b] = [20, 10];

对象的解构赋值

前面提到了原始值和数组的解构赋值,下面考虑如果将对象中的值通过解构赋值给指定的变量

首先需要明白的是,前面原始值和数组的解构赋值,左侧的变量名可以随意,但是在对象中,左侧的变量名必须和对象中对应的对象名相同

其次,前面原始值和数组的解构赋值使用的都是[],但是对于对象的解构赋值来说,需要使用{}

例如下面的代码:

JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
}

let point = new Point(1, 2);

let {x, y} = point;
console.log(x, y); // 1 2
// 变量名与属性名不一致,解构赋值失败
let {a, b} = point;
console.log(a, b)// undefined undefined

假设现在不希望直接使用类内的属性作为外部的变量名,则可以使用别名的方式,在对象的解构赋值中,在左侧的与类属性同名的变量后加上:+别名就可以为对应的属性取别名,后面再使用对应的变量时就可以直接使用别名,例如下面的代码:

JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
}

let point = new Point(1, 2);

// 使用别名
let {x:a, y:b} = point;
console.log(a, b); // 1 2

与数组一样,如果类对象中没有指定的属性,则对应的变量就是undefined,这就是为什么上面如果不给外部属性变量取别名,就必须用与类对象中的属性名相同的变量名的原因

对象的序列化与JSON

在JavaScript中,有的时候希望将对象中的内容可以保存在本地或者让其他语言也可以识别通过JavaScript创建的对象,就需要用到对象的序列化

在JavaScript中,对象使用时都是存在于计算机的内存中的,序列化指将对象转换为一个可以存储的格式,而在JavaScript中,对象的序列化通常是将一个对象转换为字符串(JSON字符串)

对象的序列化一般除了上面的用途外还有一种用途:编写配置文件,例如常玩vscode的玩家应该经常看到setting.json文件

介绍完JavaScript中的对象的序列化后,下面讲解如何进行对象序列化:在JavaScript中,有一个工具类 JSON (JavaScript Object Notation) JS对象表示法,JavaScript的对象通过序列化后会转换为一个字符串,这个字符串我们称其为JSON字符串

在JSON工具类,有两个方法:

  1. JSON.stringify():其作用是将JavaScript对象转换成JSON字符串,其参数传递一个对象,返回值是一个字符串,这个字符串满足JSON的规范
  2. JSON.parse():其作用是将符号JSON字符串规范的字符串转换成JavaScript对象,其参数传递一个JSON字符串

例如下面的代码:

JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Person {
    constructor() {
        this.name = 'mark';
        this.age = 18;
    }
    sayHello() {
        console.log('hello');
    }
}

let person = new Person();

// 调用stringify方法,将对象转换为JSON字符串
const str = JSON.stringify(person);
// 调用parse方法,将JSON字符串转换为对象
const obj = JSON.parse(str);

console.log(str); // {"name":"mark","age":18}
console.log(obj); // { name: 'mark', age: 18 }

JSON字符串除了可以使用特定的工具类转换以外,还可以手动编写,但是要符合JSON字符串的规范,其规范如下:

  1. JSON字符串有两种类型: JSON对象 {} JSON数组 []
  2. JSON字符串的属性名必须使用双引号引起来
  3. JSON字符串的类型是对象或者数组时,其中可以使用的属性值(元素)
    • 数字(Number)
    • 字符串(String) 必须使用双引号
    • 布尔值(Boolean)
    • 空值(Null)
    • 对象(Object {}
    • 数组(Array []
  4. JSON的格式和JavaScript对象的格式基本上一致的, 注意:JSON字符串如果属性是最后一个,则不要再加,

使用JSON进行对象的深拷贝

因为JSON字符串转JavaScript对象的过程是深拷贝,所以可以使用JSON进行对象的深拷贝,例如下面的代码:

JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
let obj = {
    name: "孙悟空",
    friend: {
        name: "猪八戒",
    },
};

// 对obj进行浅复制
const obj2 = Object.assign({}, obj);

console.log(obj2); // { name: '孙悟空', friend: { name: '猪八戒' } }
// 修改obj2.friend.name的值
obj2.friend.name = "沙和尚";
// 影响到了obj.friend.name的值
console.log(obj); // { name: '孙悟空', friend: { name: '沙和尚' } }
console.log(obj2); // { name: '孙悟空', friend: { name: '沙和尚' } }

obj = {
    name: "孙悟空",
    friend: {
        name: "猪八戒",
    },
}

// 对obj进行深复制
const obj3 = structuredClone(obj);
console.log(obj3); // { name: '孙悟空', friend: { name: '猪八戒' } }
// 修改obj3.friend.name的值
obj3.friend.name = "沙和尚";
// 不会影响到obj.friend.name的值
console.log(obj); // { name: '孙悟空', friend: { name: '猪八戒' } }
console.log(obj3); // { name: '孙悟空', friend: { name: '沙和尚' } }

obj = {
    name: "孙悟空",
    friend: {
        name: "猪八戒",
    },
};
// 利用JSON来完成深复制
const str = JSON.stringify(obj);
const obj4 = JSON.parse(str);
// 修改obj4.friend.name的值
obj4.friend.name = "沙和尚";
// 不会影响到obj.friend.name的值
console.log(obj); // { name: '孙悟空', friend: { name: '猪八戒' } }
console.log(obj4); // { name: '孙悟空', friend: { name: '沙和尚' } }

Map容器

基本介绍和使用

与其他变成语言一样,Map用来存储键值对结构的数据(key-value), 而Object中存储的数据就可以认为是一种键值对结构,但是二者也存在一定的区别:

  • Object中的属性名只能是字符串或符号,如果传递了一个其他类型的属性名,JavaScript解释器会自动将其转换为字符串
  • Map中任何类型的值都可以称为数据的key

例如下面的Object对象:

JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const obj2 = {};

const obj = {
    "name":"mark",
    'age':18,
    [Symbol()]:"this is Symbol",
    [obj2]:"this is object"
}

console.log(obj[obj2]); // this is object
console.log(obj[{}]); // this is object

上面代码中,需要注意的是最后一个[obj2],取objobj2的属性值时,使用obj[obj2]没问题,但是同样可以使用obj[{}]obj2的属性值,因为obj2本质还是一个空对象,有的时候并不想出现这种情况,此时就可以考虑使用Map

Map中常用的方法如下:

  1. 构造方法Map(),直接new则代表创建一个空Map
  2. size:Map对象属性,获取Map中键值对的数量
  3. set(key, value) :Map对象方法,向Map中添加键值对
  4. get(key):Map对象方法,根据key获取值,返回key对应的value
  5. delete(key):Map对象方法,删除key对应的数据,返回true或者false
  6. has(key):Map对象方法,检查Map中是否包含指定键,返回true或者false
  7. clear():Map对象方法,删除全部的键值对

例如下面的代码:

JavaScript
1
2
3
4
5
6
7
8
9
let map = new Map();

map.set("name", "mark");
map.set("age", 18);

console.log(map.size); // 2
console.log(map.get("name")); // mark
console.log(map.has("age")); // true
console.log(map.delete("age")); // true

现在使用Map解决前面的问题:

JavaScript
1
2
3
4
5
6
7
const obj2 = {}

let map = new Map();
map.set(obj2, "this is object");

console.log(map.get(obj2)); // this is object
console.log(map.get({})); // undefined

可以使用数组中的静态方法Array.from()将Map中的内容存储到数组,其参数传递一个数组对象,例如下面的代码:

JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let map = new Map();

map.set("mark", 18);
map.set("tom", 20);
map.set("jerry", 22);

let arr = Array.from(map);
// 等价于
// let arr = [...map];
console.log(arr); // [ [ 'mark', 18 ], [ 'tom', 20 ], [ 'jerry', 22 ] ]

此时Map的每一个键值对就是一个一维数组,整个数组arr就是一个二维数组

Map对象的遍历

前面提到for-of可以遍历数组,同样,可以使用for-of遍历Map对象,因为本质for-of中的of后面跟的就是可迭代对象

需要注意,不同于数组,数组使用for-of时,for后面的变量代表是数组中的元素,而Map使用for-of时,for后面的变量代表的是Map中的键值对

例如下面的代码:

JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
let map = new Map();

map.set("mark", 18);
map.set("tom", 20);
map.set("jerry", 22);

// 遍历map
for(let keyValue of map) {
    console.log(keyValue);
}
// 相当于遍历map.entries()
// for(let ketValue of map.entries()) {
//     console.log(ketValue);
// }

输出结果
[ 'mark', 18 ]
[ 'tom', 20 ]
[ 'jerry', 22 ]

如果想获取到Map对象中的key或者value时,则可以使用for-of遍历keys()方法的结果或者values()方法的结果,例如下面的结果:

JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
let map = new Map();

map.set("mark", 18);
map.set("tom", 20);
map.set("jerry", 22);

// 遍历key
for(let key of map.keys()) {
    console.log(key);
}
// 遍历value
for(let value of map.values()) {
    console.log(value);
}

输出结果
mark
tom
jerry
18
20
22

Set容器

Set容器和Map容器类似,Set用来创建一个集合,不同点在于Set中的key和value相同,与数组的不同点是,Set不允许有相同的元素

Set容器基本使用如下:

  1. 使用Set()构造,直接使用new Set()时创建一个空Set对象;如果在构造函数中传入可迭代的对象时,则Set会根据可迭代对象中的值构造Set,此时的Set对象不为空
  2. size:Set对象的属性,获取Set对象中元素的数量
  3. add(key):Set对象的方法,向Set对象中添加元素
  4. has(key):Set对象的方法,检查指定的元素
  5. delete(key):Set对象的方法,删除指定元素

除了上面提到的构造方式创建Set对象以外,还可以直接向Set的构造方法中传入一个数组对象引用,即使用数组构造一个Set对象

例如下面的代码:

JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 创建一个空的Set
let set = new Set();
console.log(set); // Set {}
// 创建一个有初始值的Set
let set2 = new Set([1]); 
console.log(set2); // Set { 1 }
// 创建一个有初始值的Set
let arr = [1, 2, 3];
let set3 = new Set(arr);
console.log(set3); // Set { 1, 2, 3 }

在上面的代码中,对于第二种方式,注意不可以是let set2 = new Set(1);,因为原始值1不是可迭代对象

其他方法的使用与Map类似,此处不再演示

Math工具类

在JavaScript中,存在一个Math工具类,其中提供了数学运算相关的一些常量和方法,具体内容可以见官方文档

下面基于官方文档对于常用的方法进行演示:

  1. Math.PI:Math类中的常量,表示圆周率
  2. Math.abs():Math类中的静态方法,表示对参数中的数值取绝对值
  3. Math.min():Math类中的静态方法,表示在参数的多个值中取出最小值
  4. Math.max():Math类中的静态方法,表示在参数的多个值中取出最大值
  5. Math.pow():Math类中的静态方法,表示求幂,第一个参数为底数,第二个参数为指数(但是现在基本上用求幂运算符**
  6. Math.sqrt():Math类中的静态方法,表示对参数中的数值求平方根
  7. Math.floor():Math类中的静态方法,表示对参数中的数值进行向下取整
  8. Math.ceil():Math类中的静态方法,表示对参数中的数值进行向上取整
  9. Math.round():Math类中的静态方法,表示对参数中的数值进行四舍五入
  10. Math.trunc():Math类中的静态方法,表示对参数中的数值的小数部分进行截断
  11. Math.random():Math类中的静态方法,表示生成0到1之间的随机数

Note

对于Math.random()来说,官方文档对其的解释是生成包含0,但不包含1的随机数,实际上生成的大部分随机数都不会为0

基本使用如下:

JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 1. Math.PI
console.log(Math.PI); // 3.141592653589793
// 2. Math.abs()
console.log(Math.abs(-10)); // 10
// 3. Math.min()
console.log(Math.min(1, 2, 3)); // 1
// 4. Math.max()
console.log(Math.max(1, 2, 3)); // 3
// 5. Math.pow()
console.log(Math.pow(2, 3)); // 8
// 6. Math.sqrt()
console.log(Math.sqrt(16)); // 4
// 7. Math.floor()
console.log(Math.floor(3.9)); // 3
console.log(Math.floor(-3.9)); // -4
// 8. Math.ceil()
console.log(Math.ceil(3.1)); // 4
console.log(Math.ceil(-3.1)); // -3
// 9. Math.round()
console.log(Math.round(3.1)); // 3
console.log(Math.round(3.5)); // 4
// 10. Math.trunc()
console.log(Math.trunc(3.1)); // 3
console.log(Math.trunc(3.6)); // 3
// 11. Math.random()
console.log(Math.random()); // 0.123456789

对于取随机数来说,常见的公式有以下几种:

  1. 得到一个两数之间的随机数

    JavaScript
    1
    2
    3
    function getRandomArbitrary(min, max) {
        return Math.random() * (max - min) + min;
    }
    
  2. 得到一个两数之间的随机整数

    JavaScript
    1
    2
    3
    4
    5
    6
    7
    8
    function getRandomInt(min, max) {
        const minCeiled = Math.ceil(min);
        const maxFloored = Math.floor(max);
        return Math.floor(
            Math.random() * 
            (maxFloored - minCeiled) + 
            minCeiled); // 不包含最大值,包含最小值
    }
    
  3. 得到一个两数之间的随机整数,包括两个数在内

    JavaScript
    1
    2
    3
    4
    5
    6
    7
    8
    function getRandomIntInclusive(min, max) {
        const minCeiled = Math.ceil(min);
        const maxFloored = Math.floor(max);
        return Math.floor(
            Math.random() * 
            (maxFloored - minCeiled + 1) + 
            minCeiled); // 包含最小值和最大值
    }
    

例如下面代码:

JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 1. 得到一个两数之间的随机数
function getRandomArbitrary(min, max) {
    return Math.random() * (max - min) + min;
}

console.log(getRandomArbitrary(1, 10)); //8.832451676651598

// 2. 得到一个两数之间的随机整数
function getRandomInt(min, max) {
    const minCeiled = Math.ceil(min);
    const maxFloored = Math.floor(max);
    return Math.floor(
        Math.random() *
        (maxFloored - minCeiled) +
        minCeiled); // 不包含最大值,包含最小值
}

console.log(getRandomInt(1, 10)); // 3

// 3. 得到一个两数之间的随机整数,包括两个数在内
function getRandomIntInclusive(min, max) {
    const minCeiled = Math.ceil(min);
    const maxFloored = Math.floor(max);
    return Math.floor(
        Math.random() *
        (maxFloored - minCeiled + 1) +
        minCeiled); // 包含最小值和最大值
}

console.log(getRandomIntInclusive(1, 10)); // 9

日期类与格式化

Date类

在JavaScript中,同样与Java有着表示日期和时间的类:Date类。在JavaScript中,所有的和时间相关的数据都由Date对象来表示

创建Date对象时,同样使用new关键字,一般有三种方式:

  1. new Date():直接创建一个Date对象,该对象表示当前的日期和时间
  2. new Date(表示时间的字符串):通过一个表示时间的字符串创建对象时,该对象表示的时间和日期即为指定的时间和日期,字符串的格式:月/日/年 时:分:秒或者年-月-日T时:分:秒
  3. new Date(年, 月, 日, 时, 分, 秒):通过指定的数值表示相应的字段创建对象,该对象表示的时间和日期即为指定的时间和日期

例如下面的代码:

JavaScript
1
2
3
4
let date = new Date();
let date1 = new Date("2021/01/01 12:00:00");
let date2 = new Date("2021-01-01T12:00:00");
let date3 = new Date(2021, 0, 1, 12, 0, 0);

在Date类中,也提供了一系列方法,常用的方法如下:

  1. getFullYear():Date对象方法,获取完整的年份数值(四位数)
  2. getMonth():Date对象方法,获取月份,但是需要注意,其月份计数从0开始,所以0表示1月、1表示2月,以此类推
  3. getDate():Date对象方法,获取月份中的第几天
  4. getDay():Date对象方法,获取星期,同样,其星期计数从0开始,并且0表示周日
  5. getTime():Date对象方法,返回当前对象时间对应的时间戳
  6. now():Date静态方法,返回当前系统时间的时间戳

日期格式化

在JavaScript中,提供了两个Date类对象方法进行格式化:

  1. toLocaleDateString(): 将日期转换为本地的字符串
  2. toLocaleTimeString():将时间转换为本地的字符串

但是更加常用的是:toLocaleString(),该方法根据指定格式将日期和时间转换成字符串,其参数有两个:

  1. 第一个参数locales:表示缩写语言代码的字符串,或由此类字符串组成的数组,常见的缩写语言代码的字符串有:
  2. zh-CN:中文中国
  3. zh-HK:中文香港
  4. en-US:英文美国
  5. 第二个参数options:一个调整输出格式的对象,在对象中可以通过对象的属性来对日期的格式进行配置,常见的配置有:
  6. dateStyletimeStyle:表示日期和时间的风格,其取值有:fulllongmediumshort
  7. hour12:表示是否使用12小时制,其取值有truefalse
  8. weekday:表示星期的显示方式,其取值有:longshortnarrow
  9. year:表示年份的显示方式,其取值有:numeric(完整数值型)和2-digit(两位数)

例如下面的代码:

JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let date = new Date();
let s = date.toLocaleString("zh-CN", {
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
    hour: "2-digit",
    minute: "2-digit",
    second: "2-digit",
    weekday: "long",
});

console.log(s); // 2024年11月12日星期二 19:53:29

包装类

在Java中,每一个基本数据类型对应着各自的包装类,例如int对应Integer,在JavaScript中也不例外,原始值也对应着自己的包装类:

  1. String包装类:将字符串包装成String对象
  2. Number包装类:将数值包装成Number对象
  3. Boolean包装类:将布尔值包装成Boolean对象
  4. BigInt包装类:将大整数包装成BigInt对象
  5. Symbol包装类:将符号包装成Symbol对象

Note

尽管有包装类的存在,但是在JavaScript中,不推荐使用包装类的构造函数创建对应的对象

与Java一样,包装类存在的意义就是为了使原始值可以调用方法和属性。如果使用一个原始值调用方法或属性时,JavaScript解释器会临时将原始值包装为对应的对象,然后调用这个对象的属性或方法

因为原始值会被临时转换为对应的对象,这就意味着对象中的方法都可以直接通过原始值来调用,例如下面的代码:

JavaScript
1
2
3
let str = "hello";
console.log(str.length); // 5
console.log("hello".length); // 5

字符串的方法

在JavaScript中,字符串本质就是一个字符数组,所以它的方法与数组比较类似,详细的方法见官方文档,下面列举常用的方法:

  1. length:String类对象的方法,获取当前字符串的长度
  2. at():String类对象的方法,表示根据索引获取字符,与[]不同的时,at()可以传递负数(目前为实验性方法)
  3. charAt():String类对象的方法,表示根据索引获取字符,但是不能传递负数
  4. concat():String类对象的方法,参数传递一个或多个字符串,表示将参数的字符串和调用对象的字符串进行拼接
  5. includes():String类对象的方法,参数传递一个字符串,表示对象字符串是否包含参数字符串,存在返回true,否则返回false
  6. indexOf():String类对象的方法,参数传递一个字符串,表示对象字符串是否包含参数字符串,存在返回第一次出现位置的索引,否则返回-1
  7. lastIndexOf():String类对象的方法,参数传递一个字符串,表示对象字符串是否包含参数字符串,存在返回第一次出现位置的索引,否则返回-1,不同于indexOf()lastIndexOf()是反向查找
  8. startsWith():String类对象的方法,参数传递一个字符串,表示对象字符串是否以参数字符串开头,是返回true,否则返回false
  9. endsWith():String类对象的方法,参数传递一个字符串,表示对象字符串是否以参数字符串结尾,是返回true,否则返回false
  10. padStart():String类对象的方法,一共两个参数,第一个参数代表字符串的目标总长度(如果该值小于等于对象字符串的长度,则直接返回对象字符串),第二个参数代表指定的填充字符,表示在对象字符串的开头以指定字符填充,直到对象字符串的长度达到目标总长度,返回填充后的字符串
  11. padEnd():String类对象的方法,一共两个参数,第一个参数代表字符串的目标总长度(如果该值小于等于对象字符串的长度,则直接返回对象字符串),第二个参数代表指定的填充字符,表示在对象字符串的结尾以指定字符填充,直到对象字符串的长度达到目标总长度,返回填充后的字符串
  12. replace():String类对象的方法,一共两个参数,第一个参数代表匹配模式,可以传递正则表达式,也可以传递一个字符串,如果这个参数为空字符串,则默认将替换内容插入到对象字符串的开头,第二个参数代表用于替换的字符串,返回替换后的字符串。注意,如果对象字符串中有多个匹配的字符串,则当前方法只会替换第一个
  13. replaceAll():String类对象的方法,一共两个参数,第一个参数代表匹配模式,可以传递正则表达式,也可以传递一个字符串,如果这个参数为空字符串,则默认将替换内容插入到对象字符串的开头,第二个参数代表用于替换的字符串,返回替换后的字符串。本方法会替换所有匹配的字符串
  14. slice():String类对象的方法,表示对字符串进行切片,一共两个参数,使用方式与数组的slice()方法类似
  15. substring():String类对象的方法,表示截取字符串,与slice()方法使用和功能类似
  16. split():String类对象的方法,一共两个参数,第一个参数代表分隔符,第二个参数代表一个非负整数,指定数组中包含的子字符串的数量限制,表示从一个对象字符串中根据分隔符取出指定个数的子字符串存入数组中,返回该数组
  17. toLowerCase():String类对象的方法,表示将对象字符串中的字符全部转换为小写
  18. toUpperCase():String类对象的方法,表示将对象字符串中的字符全部转换为大写
  19. trim():String类对象的方法,表示去除字符串中开头和结尾的空格,注意不会去除字符串中间的空格
  20. trimStart():String类对象的方法,表示去除字符串中开头的空格,注意不会去除字符串中间的空格
  21. trimEnd():String类对象的方法,表示去除字符串中结尾的空格,注意不会去除字符串中间的空格

上面的方法使用方式与数组基本一致,不再演示,详情见官方文档

正则表达式

创建正则表达式

在Java部分也提过与正则表达式相关的内容,JavaScript中也存在正则表达式,在JavaScript中,有两种创建正则表达式字符串的方式

  1. new RegExp():使用正则表达式构造函数,其可以接收两个参数(字符串),第一个参数表示正则表达式,第二个参数表示匹配模式
  2. /正则/匹配模式:使用正则表达式字面量

例如下面的代码:

JavaScript
1
2
3
4
5
6
7
// 构造函数
let regExp = new RegExp("abc", "i");
console.log(regExp); // /abc/i

// 字面量
let regExp2 = /abc/i;
console.log(regExp2); // /abc/i

正则表达式语法

基本语法如下:

  1. 在正则表达式中大部分字符都可以直接写
  2. |在正则表达式中表示或
  3. []表示或(字符集),例如:
    1. [a-z]:任意的小写字母
    2. [A-Z]:任意的大写字母
    3. [a-zA-Z]:任意的字母
    4. [0-9]:任意数字
  4. [^]表示除了,例如:[^x] 除了x
  5. . 表示除了换行外的任意字符
  6. 在正则表达式中使用\作为转义字符
  7. 其他的字符集:
    1. \w 任意的单词字符[A-Za-z0-9_]
    2. \W 除了单词字符[^A-Za-z0-9_]
    3. \d 任意数字[0-9]
    4. \D 除了数字[^0-9]
    5. \s 空格
    6. \S 除了空格
    7. \b 单词边界:确保\b包裹的内容在测试的字符串中前后不能有大写或者小写字母
    8. \B 除了单词边界
  8. 开头和结尾:
    1. ^ 表示字符串的开头
    2. $ 表示字符串的结尾

Note

可以同时使用^$达到完全匹配被包裹的字符串的效果

例如下面的代码:

JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
let re = /abc|bcd/; // 匹配abc或bcd

re = /[a-z]/; // 匹配任意小写字母

re = /[A-Z]/; // 匹配任意大写字母

re = /[A-Za-z]/; // 匹配任意字母

re = /[a-z]/i; // 匹配模式i表示忽略大小写

re = /[^a-z]/; // 匹配包含小写字母以外内容的字符串

re = /./; // 匹配任意字符,除去换行符

re = /\./; // 匹配点

re = /\w/; // 匹配字母、数字、下划线

re = /^a/; // 匹配开始位置的a

re = /a$/; // 匹配结束位置的a

re = /^a$/; // 只匹配字母a,完全匹配,要求字符串必须和正则完全一致

re = /^abc$/; // 只匹配abc,完全匹配,要求字符串必须和正则完全一致

const str = "foo bar baz";
console.log(/\bbar\b/.test(str)); // true,因为 "bar" 是一个独立的单词
console.log(/\bba\b/.test(str)); // false,因为 "ba" 不是一个独立的单词

量词:

  1. {m} 正好m个
  2. {m,} 至少m个
  3. {m,n} m-n个
  4. + 一个以上,相当于{1,}
  5. * 任意数量的a
  6. ? 0-1次 {0,1}

例如下面的代码:

JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
let re = /a{3}/; // 匹配3个a

re = /^a{3}$/; // 只匹配3个a,完全匹配,要求字符串必须和正则完全一致

re = /^(ab){3}$/; // 只匹配3个ab,完全匹配,要求字符串必须和正则完全一致

re = /^[a-z]{3}$/; // 匹配3个小写字母,完全匹配,要求字符串必须和正则完全一致

re = /^[a-z]{1,}$/; // 匹配1个或多个小写字母,完全匹配,要求字符串必须和正则完全一致

re = /^[a-z]{1,3}$/; // 匹配1个到3个小写字母,完全匹配,要求字符串必须和正则完全一致

re = /^ba+$/; // 匹配1个或多个a,完全匹配,要求字符串必须和正则完全一致

re = /^ba*$/; // 匹配0个或多个a,完全匹配,要求字符串必须和正则完全一致

re = /^ba?$/; // 匹配0个或1个a,完全匹配,要求字符串必须和正则完全一致

正则表达式相关方法

正则表达式对象的截取方法:exec(),参数传递一个待检查的字符串,返回值前面的字符串表示匹配到的子字符串,index表示第一次匹配到的字符位于原始字符串的基于 0 的索引值,input表示匹配的原始字符串

例如下面的代码:

JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
let str = "abcaecafcacc";

// i表示忽略大小写,g表示全局匹配,m是多行匹配,并且不区分彼此位置
let re = /a(([a-z])c)/ig; // 匹配a开头,c结尾,中间是一个小写字母的字符串

let result = re.exec(str);

console.log(result);

输出结果
[
  'abc',
  'bc',
  'b',
  index: 0,
  input: 'abcaecafcacc',
  groups: undefined
]

字符串对象中与正则表达式相关的方法:

  1. split():根据正则表达式对对象字符串进行拆分
  2. search():搜索符合正则表达式的内容第一次在字符串中出现的位置
  3. replace():根据正则表达式替换字符串中的指定内容
  4. match():根据正则表达式去匹配字符串中符合要求的内容,本方法一次只会匹配第一个符合正则表达式的字符串,如果需要匹配全部,需要在正则表达式的匹配模式中写上g
  5. matchAll():根据正则表达式去匹配字符串中符合要求的内容(必须设置g全局匹配),它返回的是一个迭代器

例如下面的代码:

JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
let str = "a@b@c@d";

let result = str.split("@");

str = "123abc456adc789";
result = str.split(/a[bd]c/); // ["123", "456", "789"];

str =
    "dajsdh13715678903jasdlakdkjg13457890657djashdjka13811678908sdadadasd";

result = str.search("abc"); // 3
result = str.search(/1[3-9]\d{9}/); // 6

result = str.replace(/1[3-9]\d{9}/g, "哈哈哈"); // 替换所有匹配的手机号码

result = str.match(/1[3-9]\d{9}/g); // 匹配所有手机号码

result = str.matchAll(/1[3-9](\d{9})/g); // 匹配所有手机号码

for(let item of result){
    console.log(item);
}

输出结果
[
  '13715678903',
  '715678903',
  index: 6,
  input: 'dajsdh13715678903jasdlakdkjg13457890657djashdjka13811678908sdadadasd',
  groups: undefined
]
[
  '13457890657',
  '457890657',
  index: 28,
  input: 'dajsdh13715678903jasdlakdkjg13457890657djashdjka13811678908sdadadasd',
  groups: undefined
]
[
  '13811678908',
  '811678908',
  index: 48,
  input: 'dajsdh13715678903jasdlakdkjg13457890657djashdjka13811678908sdadadasd',
  groups: undefined
]

Promise对象

Info

了解此部分内容先了解JavaScript中的定时器

在编写定时器时,因为其中需要用到回调函数,所以假设有需求:设置三个定时器,延迟2秒之后输出1, 完了之后延迟1秒输出2, 完了之后延迟1秒输出3,那么直接使用定时器写出的代码如下:

JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
setTimeout(() => {
    console.log('1');
    // 输出1之后输出2
    setTimeout(() => {
        console.log('2');
        // 输出2之后输出3
        setTimeout(() => {
            console.log('3');
        }, 1000);
    }, 1000)
}, 2000)

但是上面的上面的代码存在回调地狱问题,也就是回调函数的嵌套。为了解决这个问题,可以考虑使用Promise

Promise是Javascript中的对象,专门用于处理JavaScript异步编程的问题。创建Promise对象时需要一个回调函数,该函数有两个参数,第一个参数表示异步代码成功执行时调用的函数,第二个参数表示异步代码执行失败时调用的函数

Promise有三种状态: 1. Pending(等待) 2. Fulfilled(成功执行) 3. Rejected(执行失败)

这三种状态只有两种状态转换: 1. Pending->Fulfilled(表示从执行开始到成功执行) 2. Pending->Rejected(表示从执行开始到执行失败) 一旦状态确定(Fulfilled或者Rejected中的一种)就无法再改变状态(因为已经调用了对应状态的函数)

例如下面的代码:

JavaScript
1
2
3
4
5
6
7
8
9
let p = new Promise((resolve, reject) => {
    // 编写异步代码
    setTimeout(() => {
        // 成功执行调用resolve
        resolve("Success");
        // 执行失败调用reject
        reject("Fail");
    }, 2000);
})

Promise对象有then方法,该方法存在两个参数。这两个参数对应着创建Promise对象时其回调函数中的两个参数,then方法中的回调函数的参数用于接收上面resolvereject中的参数,所以有下面的代码:

JavaScript
1
2
3
4
5
6
7
p.then((message) => {
    // 执行resolve时会调用本函数
    console.log(message); // Success
}, (message) => {
    // 执行reject时会调用本函数
    console.log(message);
});

直接使用Promise解决前面的需求就需要利用到then的链式调用,因为then的返回值是Promise对象,所以可以实现链式调用。同样对于上面的需求:

JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 包装异步代码
function delay(duration, num) {
    return new Promise((resolve, reject)=>{
        setTimeout(() => {
            resolve(num);
        }, duration);
    })
}

// 调用delay函数
// 因为要实现链式调用打印不同的消息,所以需要返回一个新的Promise对象
delay(2000, 1)
    .then((message)=>{
        // 2秒打印1
        console.log(message);
        // 返回delay函数的返回值,即返回Promise对象
        return delay(1000, 2);
    })
    .then((message)=>{
        console.log(message);
        return delay(1000, 3);
    })
    .then((message)=>{
        console.log(message);
    })

如果调用不返回一个新的Promise对象,则此时需要执行的内容不确定,那么在上面的回调函数的参数的message自然也就为null

JavaScript
1
2
3
4
5
delay(2000, 1).then((message)=>{
    console.log(message); // 1
}).then((message)=>{
    console.log(message); // null
})

但是很明显仅仅使用Promise还是缺少阅读性,尽管其解决了回调嵌套的问题。所以还需要结合awaitasync关键字,使用awaitasync关键字需要注意三点:

  1. await关键字修饰包装的异步代码会直接取到resolve或者reject的参数值
  2. 如果一个函数内部使用了await关键字,那么这个函数就需要被async修饰
  3. 一个函数内部有被await关键字修饰的多个执行语句,那么多个语句的执行顺序是有序的,即依次执行。这一个特点就让本为异步执行的定时器变成了同步
JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
async function printLog(){
    // 2秒后打印1
    const num1 = await delay(2000, 1);
    console.log(num1);
    // 1秒后打印2
    const num2 = await delay(1000, 2);
    console.log(num2);
    // 1秒后打印3
    const num3 = await delay(1000, 3);
    console.log(num3);
}

printLog();

Web存储

Web存储包含两种机制:

  1. sessionStorage该存储区域在页面会话期间可用(即只要浏览器处于打开状态,包括页面重新加载和恢复),仅为会话存储数据,这意味着数据将一直存储到浏览器(或选项卡)关闭。数据永远不会被传输到服务器,一般存储限额大于Cookie(最大5MB)
  2. localStorage即使浏览器关闭并重新打开也仍然存在。存储的数据没有过期日期,只能通过JavaScript、清除浏览器缓存或本地存储的数据来清除

下面以localStorage为例演示Web存储的添加、删除和获取:

JavaScript
1
2
3
4
5
6
7
8
// 存入内容
// setItem方法存储键值对
localStorage.setItem("存入内容", "内容");
// 取出内容——根据key取出
console.log(localStorage.getItem("存入内容")); // 内容
// 删除内容——根据key删除
localStorage.removeItem("存入内容");
console.log(localStorage.getItem("存入内容")); // null

如果存取对象,就需要进行序列化和反序列化:

JavaScript
1
2
3
4
5
6
7
// 对对象进行序列化再存入
let jsonstr = JSON.stringify(obj);
localStorage.setItem("对象", jsonstr);
// 取出对象时反序列化
jsonstr = localStorage.getItem("对象");
const str = JSON.parse(jsonstr);
console.log(str);

在浏览器调试工具界面找到Application即可看到Local Storage,此处就是通过localStorage保存的内容

JavaScript垃圾回收机制(了解)

垃圾回收(Garbage collection),和生活一样,生活时间长了以后会产生生活垃圾,程序运行一段时间后也会产生垃圾。如果一个对象没有任何的变量对其进行引用,那么这个对象就是一个垃圾,垃圾对象的存在,会严重的影响程序的性能

在JavaScript中有自动的垃圾回收机制,这些垃圾对象会被解释器自动回收,无需程序员手动处理,对于垃圾回收来说,程序员唯一能做的事情就是将不再使用的对象引用设置为null

例如下面的代码:

JavaScript
1
2
3
4
5
6
let obj = {name:"mark"};
let obj2 = obj;

// 解除引用,触发垃圾回收
obj = null;
obj2 = null;