JS基础(三)

目录

Map集合

Map: js中内置对象,属于类,使用new Map()创建一个Map对象,有序

MapObject对比

1.他们都可以使用key-value形式定义成员

2.对象中的属性名只能使用字符串类型或符号类型命名,如果使用对象类型命名,js会自动将对象类型转换为字符串[object Object]

由于js会自动将对象解析为字符串,因此可以使用对象[{}]对象['[object Object]']来访问对象中以对象作为属性名的属性值

let obj2 = { name: '李四' };
let obj = {
    name: '张三',
    age: 18,
    sex: '男',
    'address': '北京市',
    [Symbol()]: '符号类型',
    [obj2]: '对象类型'
}
console.log(obj); // 输出: { name: '张三', age: 18, sex: '男', address: '北京市', Symbol(): '符号类型', '[object Object]': '对象类型' }
console.log(obj[{}]); // 输出: 对象类型
console.log(obj['[object Object]']); // 输出: 对象类型
console.log(obj[Symbol()]); // 输出: undefined

3.Map中任何数据类型都可以成为属性名,不会进行自动转化为字符串

let obj3 = { name: '李四' };
let map = new Map();
map.set(obj3, '对象类型');
map.set(Symbol(), '符号类型');
map.set(18, '数字类型');
map.set(true, '布尔类型');
map.set(null, 'null类型');
map.set(undefined, 'undefined类型');
map.set(NaN, 'NaN类型');
console.log(map);
console.log(map.get(obj3)); // 输出: 对象类型

输出:
img

4.Map和Object属性名的个数获取方式不同

// 对象属性名的个数,通过Object对象将属性名转为数组,再通过length属性
let obj4 = { name: '张三', age: 18, sex: '男' };
console.log(Object.keys(obj4).length); // 输出: 3
// Map属性名的个数,通过size属性
let map2 = new Map();
map2.set('name', '张三');
map2.set('age', 18);
map2.set('sex', '男');
console.log(map2.size); // 输出: 3

5.Map中属性名的顺序和添加顺序保持一致,而Object的属性名顺序和添加顺序不一致

let map3 = new Map();
map3.set('name', '张三');
map3.set('age', 18);
map3.set('sex', '男');
console.log(map3);

img


Map对象的方法

Map对象的方法有:
set(key, value): 设置键值对
get(key): 获取键对应的值
has(key): 判断Map中是否存在该键
delete(key): 删除Map中指定的键值对,删除成功返回true,key不存在时删除返回false
clear(): 清空Map中的所有键值对,是map对象的size为0,该方法不返回任何值

let result = map.delete('NaN1');
console.log(result); // 输出: false
let newMap = map.clear();
console.log(map); // 输出: Map(0) {}
console.log(newMap); // 输出: undefined

Map与数组相互转化

使用Array.from()方法或扩展运算符将Map对象转为数组
将map中每一个key-value键值对作为数组的一个元素,数组中的元素是[key, value]形式的数组

let map3 = new Map();
map3.set('name', '张三');
map3.set('age', 18);
map3.set('sex', '男');
// 1.使用Array.from()方法
let arr1 = Array.from(map3);
console.log(arr1);
// 2.使用扩展运算符
let arr2 = [...map3];
console.log(arr2);

img

上面代码我们可以看出,map对象转化为一个二维数组,那么也可以将二维数组转化为map

let arr3 = [['name', '张三'], ['age', 18], ['sex', '男']];
let map4 = new Map(arr3);
console.log(map4); // 输出: Map(3) { 'name' => '张三', 'age' => 18, 'sex' => '男' }

for-of遍历map对象和遍历数组一样,不能使用for-in遍历map

通过for将map转为二维数组,然后通过of获取每个元素值(是一个一维数组)
遍历map对象,每一个item就是一个数组,数组内容为[key, value]

let map5 = new Map();
map5.set('name', '张三');
map5.set('age', 18);
map5.set('sex', '男');
for (let [key, value] of map5) {
    console.log(key, value);
}

forEach遍历map对象

let map6 = new Map();
map6.set('name', '张三');
map6.set('age', 18);
map6.set('sex', '男');
map6.forEach((value, key) => {
    console.log(key, value);
});

keys()和values()方法

keys()方法返回一个包含Map对象中所有键的迭代器
values()方法返回一个包含Map对象中所有值的迭代器

let map7 = new Map();
map7.set('name', '张三');
map7.set('age', 18);
map7.set('sex', '男');
let keys = map7.keys();
keys.forEach((key) => {
    console.log(key);
});
let values = map7.values();
values.forEach((value) => {
    console.log(value);
});

Set集合

Set: js中内置对象,属于类,使用new Set()创建一个Set对象。功能和数组类似。集合(set)中的元素只会出现一次,即集合中的元素是唯一的,set中元素顺序和添加顺序保持一致和map一样
和数组区别: Set对象中不能存放重新的元素,数组中可以存放重复的元素

Set对象的方法

add(value): 向Set中添加一个元素,添加成功返回Set对象本身

let set = new Set();
let newSet = set.add('name');
console.log(set === newSet); // 输出: true

delete(value): 删除Set中指定的元素,删除成功返回true,删除失败返回false
has(value): 判断Set中是否存在指定的元素,存在返回true,不存在返回false
size: sieze属性,返回Set中元素的个数
console.log(set.size); // 输出: 1:
clear(): 清空Set中的所有元素,是set对象的size为0,该方法不返回任何值

Set与数组相互转化

使用Array.from()方法或扩展运算符将Set对象转为数组

let set2 = new Set();
set2.add('name');
set2.add('age');
set2.add('sex');
// 1.使用Array.from()方法
let arr1 = Array.from(set2);
console.log(arr1);
// 2.使用扩展运算符
let arr2 = [...set2];
console.log(arr2);

将数组转化为Set对象

let arr3 = ['name', 'age', 'sex'];
let set3 = new Set(arr3);
console.log(set3); // 输出: Set(3) { 'name', 'age', 'sex' }

for-of遍历Set对象和遍历数组一样

遍历Set对象,每一个item就是一个元素

let set4 = new Set();
set4.add('name');
set4.add('age');
set4.add('sex');
for (let item of set4) {
    console.log(item);
}

数组去重

let arr4 = ['name', 'age', 'sex', 'name', 'age'];
let set5 = new Set(arr4);
let arr5 = [...set5];
console.log(arr5); // 输出: ['name', 'age', 'sex']
// 简写
let arr6 = [...new Set(arr4)];
console.log(arr6); // 输出: ['name', 'age', 'sex']

Math

Math: 是一个内置对象,它拥有一些数学常数属性和数学函数方法。它不是一个函数对象,不是一个构造器,它的所有属性与方法都是静态的

Math.PI: 圆周率
Math.abs(): 返回一个数的绝对值


Math.ceil(): 向上取整,四舍五入
ceil: 单词含义: 装天花板

let float = 3.005;
console.log(Math.ceil(float)); // 向上取整 4

Math.floor(): 向下取整

let pai = 3.14;
console.log(Math.ceil(pai)); // 向上取整 4
console.log(Math.floor(pai)); // 向下取整 3

Math.round(): 四舍五入
五入: 遇到中间值时,向正无穷方向舍入取整

规则:
1.是中间值时,向正无穷方向舍入取整
2.非中间值时舍入到最近的整数
相对于正数,可以理解为四舍五入取整

console.log(Math.round(0.51)); // 1
console.log(Math.round(0.5));  // 1
console.log(Math.round(-0.5)); // -0
console.log(Math.round(-0.51)); // -1

0.5: 向正无穷方向舍入取整,就是1
-0.5: 向正无穷方向舍入取整就是 -0 即0, -0 === 0 true
-0.51: 非中值舍入到最近的整数,即为-1


Math.trunc(): 返回一个数字的整数部分,小于部分直接截取

Math.max(): 返回多个数中的最大值
Math.min(): 返回多个数中的最小值

console.log(Math.max(1, 2, 3, 4, 5)); // 最大值 5
let arr = [1, 2, 3, 4, 5];
console.log(Math.max(...arr)); // 最大值 5
console.log(Math.min(1, 2, 3, 4, 5)); // 最小值 1
console.log(Math.min(...arr)); // 最小值 1

Math.pow(数, 次方): 返回一个数的次方值 等价于 4 ** 2 4的2次方
Math.random(): 返回一个[0,1)之间的随机浮点数,包括0但不包括1
Math.sqrt(): 返回一个数的平方根

随机生成正整数

随机生成[0, 5]之间的整数问题

let num = Math.round(Math.random() * 5);

上面代码中,假如生成每个整数的概率都是1,即由于四舍五入的存在,结果为0的场景是0~0.4 结果为1的场景是0.5~1.4,因此他们生成概率不相同

解决方案应该是向下取整,使得生成每个区间的概率相同
0~0.9 结果为0,1~1.9 结果为1,2~2.9 结果为2,3~3.9 结果为3,4~4.9 结果为4,5~5.9 结果为5,这样生成每个整数概率相同

let num = Math.floor(Math.random() * 6);
console.log(num);

随机生成[0, n]之间的整数公式

let num = Math.floor(Math.random() * (n + 1));

Date对象

新Date对象的唯一方法是通过new操作符

当前日期对象: new Date()

let date = new Date();
console.log(date); // 输出: Wed May 10 2023 09:43:00 GMT+0800 (中国标准时间)

字符串转date对象

// 格式: 月/日/年 时:分:秒 不推荐把年写成两位
console.log(new Date('05/01/25 9:43:00')); // 输出: Thu May 01 2025 09:43:00 GMT+0800 (中国标准时间)
console.log(new Date('15/01/5 9:43:00')); // 输出: Invalid Date
// 格式: 年/月/日 时:分:秒
console.log(new Date('2025/05/01 9:43:00')); // 输出: Thu May 01 2025 09:43:00 GMT+0800 (中国标准时间)
// 格式: 年-月-日 时:分:秒
console.log(new Date('2025-05-01 9:43:00')); // 输出: Thu May 01 2025 09:43:00 GMT+0800 (中国标准时间)
// 格式: 年,月,日,时,分,秒,毫秒
console.log(new Date(2025, 4, 1, 9, 43, 0, 33)); // 输出: Thu May 01 2025 09:43:00 GMT+0800 (中国标准时间)
console.log(new Date(2025, 4, 1, 9, 43, 0, 33).getMilliseconds()); // 输出: 33

注意的是,时间格式是年/月/日,如果把年份写成2位,js会按照月/日/年的格式解析,所以会报错,所以时间格式中,年份一定要写成4位

常用方法

getFullYear(): 获取4位年份
getMonth(): 获取月份,0~11,0代表1月,11代表12月
getDate(): 获取日期,1~31
getDay(): 获取星期几,0~6,0代表星期天,6代表星期六
getHours(): 获取小时,0~23
getMinutes(): 获取分钟,0~59

let date2 = new Date('2025-5-10 09:43:00');
console.log(date2.getFullYear()); // 输出: 2025
console.log(date2.getMonth()); // 输出: 4 (0-11)
console.log(date2.getDate()); // 输出: 10
date2 = new Date('2025-5-10 00:43:00');
console.log(date2.getHours()); // 输出: 0
console.log(date2.getMinutes()); // 输出: 43
console.log(date2.getDay());

时间戳毫秒精度

时间戳: 自1970年1月1日00:00:00以来经过的毫秒数

日期实例.getTime(): 获取当前时间戳
Date.now(): 获取当前时间戳

date2 = new Date();
console.log(date2.getTime()); // 输出: 1746844752718
console.log(Date.now()); // 输出: 1746844752718

日期格式化

toLocaleDateString(): 获取日期字符串,格式为年/月/日
toLocaleTimeString(): 获取时间字符串,格式为时:分:秒
toLocaleString(): 获取日期时间字符串,格式为年/月/日 时:分:秒

let date = new Date('2025-05-10 12:59:59');
let result = date.toLocaleDateString();
console.log(result); // 输出: 2025/5/10
result = date.toLocaleTimeString();
console.log(result);// 输出: 12:59:59
result = date.toLocaleString();
console.log(result); // 输出: 2025/5/10 12:59:59

toLocaleString()可以传入国家地区编码,用于展示符合当地习惯的日期时间格式

let date = new Date('2025-05-10 12:59:59');
// 美国 返回格式: 月/日/年, 时:分:秒 AM/PM
let result2 = date.toLocaleString('en-US');
console.log(result2); // 输出: 5/10/2025, 12:59:59 PM
// 中国 返回格式: 年/月/日, 时:分:秒
result2 = date.toLocaleString('zh-CN');
console.log(result2); // 输出: 2025/5/10 12:59:59
// 香港 返回格式: 日/月/年 上午/下午 时:分:秒
result2 = date.toLocaleString('zh-HK');
console.log(result2); // 输出: 10/5/2025 下午12:59:59

还可以接受对象作为第二个参数,该对象用于配置指定的格式化选项
格式化样式文档

let date = new Date('2025-05-10 12:59:59');
let style = { weekday: "short" };
console.log(date.toLocaleString('zh-CN', style)); // 周六
style = { year: "numeric", month: "long", day: "numeric" };
console.log(date.toLocaleString('zh-CN', style)); // 2025年5月10日
style = { year: "2-digit" };
console.log(date.toLocaleString('zh-CN', style)); // 输出: 25年

date转yyyy-mm-dd HH:mm:ss

function formatDate(date) {
    const pad = (n) => n.toString().padStart(2, '0');
    const year = date.getFullYear();
    const month = pad(date.getMonth() + 1);
    const day = pad(date.getDate());
    const hours = pad(date.getHours());
    const minutes = pad(date.getMinutes());
    const seconds = pad(date.getSeconds());
    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
// 示例
const now = new Date();
console.log(formatDate(now)); //

包装类

js中的包装类有: StringNumberBooleanSymbolBigIntObject

字符串数据类型不是对象为什么能够调用toString()方法

let num = 3;
num = num.toString();
console.log(num); // 3 字符串

原因: 当原始类型数据调用方法时,js会先包装成包装类对象,而后调用其中的方法
num -> new Number(num) -> 调用number对象中的toString()方法 -> 返回字符串重新赋值给num


注意

1.包装类对象和原始类型的值相同,但是他们的引用地址不同

var str1 = "hello";
// 1.1.2 构造函数创建
var str2 = new String("hello");
console.log(str1 == str2); // true
console.log(str1 === str2); // false

2.原始类型数据可以直接调用对应包装类的方法
由于js会自动将原始数据包装成类对象,因此可以直接使用包装类方法

let obj = { name: '张三' };
console.log(obj.hasOwnProperty('name')); // 输出: true

字符串方法

字符串其本质就是字符数组,操作上和数组类似

str.length: 获取字符串长度
str[index]: 获取指定索引的字符
str.charCodeAt(index): 获取指定索引的字符的Unicode编码

str.indexOf(searchValue, fromIndex]): 查找指定字符在字符串中第一次出现的脚标,若不存在则返回-1

let str = 'hello';
console.log(str.indexOf('e', 3)); // 输出: -1
console.log(str.indexOf('e')); // 输出: 1

str.lastIndexOf(searchValue, fromIndex): 查找指定字符在字符串中最后一次出现的脚标,若不存在则返回-1

let str = 'hello';
console.log(str.lastIndexOf('l')); // 输出: 3

str.at(index): 正数时获取指定索引的字符,负数时获取倒数第几个字符

let str = 'hello';
console.log(str.at(1)); // 输出: e
console.log(str.at(-1)); // 输出: o

str.charAt(index): 获取指定索引的字符,若索引不存在则返回空字符串

let str = 'hello';
console.log(str.charAt(1)); // 输出: e
console.log(str.charAt(8)); // 输出: ''
console.log(str.charAt(-1)); // 输出: ''

str.concat(...str): 先将concat参数中字符串拼接一个字符串得到x字符串,然后再把str后拼接x字符串

let str2 = 'hello world'; 
let str3 = str2.concat('!', '!', '!');
console.log(str3); // 输出: hello world!!!

str.startWith(searchString, index): 判断字符串中从index开始,是否以指定字符串开头,返回布尔值
index参数可以省略,默认以0index开始

let str = 'hello';
console.log(str.startsWith('e', 1)); // 输出: true

str.endsWith(searchString, index + 1): 用于判断一个字符串是否以指定字符串结尾,返回布尔值
index + 1: 从第一个字符到index + 1个字符进行截取
可以不传第二个参数,默认值为字符串str.length
然后判断截取的新字符串是否以searchString结尾

hello以第四个字符结尾为hell,而后判断是否以ell结尾

let str = 'hello';
console.log(str.endsWith('ell', 4)); // 输出: true

str.padStart(个数, 填充字符): 在字符串开头填充指定字符,直到字符串长度达到指定个数

let str = "1";
console.log(str.padStart(3, '0')); // 输出: 001

str.padEnd(个数, 填充字符): 在字符串结尾填充指定字符,直到字符串长度达到指定个数

let str = "1";
console.log(str.padEnd(3, '0')); // 输出: 100

str.repeat(重复次数): 将字符串重复指定次数,返回重复后的字符串

let str = "hello";
console.log(str.repeat(3)); // 输出: hellohellohello

str.slice(start, end): 截取字符串,返回截取后的字符串,不破坏原字符串
start: 开始截取的索引,若为负数则从字符串末尾开始计算
end: 结束截取的索引,若为负数则从字符串末尾开始计算
正数: 截取范围[start, end)
负数: 截取范围(-倒数第n位, -倒数第n+length位] lenght为截取结果字符串的长度

let str = 'hello world';
console.log(str.slice(1, 5)); // 输出: ello
console.log(str.slice(-6, -1)); // 输出: world

str.substring(start, end): 截取字符串,返回截取后的字符串
start: 开始截取的索引
end: 结束截取的索引
正数: 截取范围[start, end)

let str = 'hello world';
console.log(str.substring(1, 5)); // 输出: ello

如果start大于end,则交换start和end,而slice则不会交换

let str = 'hello';
console.log(str.substring(3,1)); // el
console.log(str.substring(1,3)); // el

str.split(separator, limit): 将字符串按照指定分隔符分割成数组,返回分割后的数组
separator: 分隔符,可以是一个字符串也可以是一个正则表达式
limit: 分割次数,可以不传,默认为Infinity

let str5 = 'hello world ';
let str6 = 'hello,world,';
console.log(str5.split(' ')); // 输出: ['hello', 'world', '']
console.log(str6.split(',')); // 输出: ['hello', 'world', '']
console.log(str6.split(',', 1)); // 输出: ['hello']

str.toLowerCase(): 将字符串中所有大写字母转成小写字母,返回转换后的字符串,不改变原字符串
str.toUpperCase(): 将字符串中所有小写字母转成大写字母,返回转换后的字符串,不改变原字符串


str.trim(): 去除字符串两端的空格,返回去除空格后的字符串,不改变原字符串

let str8 = ' hello world ';
console.log(str8.trim()); // 输出: 'hello world'

正则表达式对象

js中正则表达式: 用来定义一个规则对象,通过该规则对象,js检查某个字符串是否符合规则或者把字符串中符合规则的字符取出

创建正则表达式对象

RegExp: Regular expression规则表达式

1.字面量方式

let reg = /正则表达式/匹配模式

2.构造函数方式

let reg = new RegExp('正则表达式', '匹配模式')

区别:
1.字面量方式不能写变量,构造函数方式可以写变量
2.字面量方式不解析转义字符,构造函数中正则表达式会解析转义字符

let reg = new RegExp('\w');
let reg2 = /\w/;
console.log(reg); // 输出: /w/
console.log(reg2); // 输出: /\w/

正则表达式匹配模式

1.g: 全局匹配
2.i: 忽略大小写
3.m: 多行匹配

默认情况下只匹配第一个符合规则的字符,加上g后则匹配所有符合规则的字符

正则表达式方法

reg.test(str): 检查字符串中是否存在符合正则表达式规则的字符,返回布尔值

let reg = /a/;
let str = "abc";
console.log(reg.test(str));  // 输出: true

正则表达式简单符号含义

1.[]: 表示字符集,匹配字符集中任意一个字符
举例:
[abc]: 匹配a、b、c中的任意一个字符
[^abc]: 匹配除了a、b、c以外的任意一个字符
[a-z]: 匹配a-z中的任意一个字符
[a-zA-Z]: 匹配a-z或A-Z中的任意一个字符

2.[^]: 表示非字符集,匹配除了字符集中的字符以外的任意一个字符
3.(): 表示分组,将多个字符作为一个整体进行处理
4.|: 表示或,匹配符号两侧的任意一个表达式
5..: 表示任意一个字符,除了\n
6.\: 表示转义字符,将特殊符号转义为普通字符
举例:
\d: 匹配一个数字字符,相当于[0-9]
\D: 匹配一个非数字字符,相当于[^0-9]
\w: 匹配一个单词字符,相当于[a-zA-Z0-9_]
\W: 匹配一个非单词字符,相当于[^a-zA-Z0-9_]
\s: 匹配一个空白字符,相当于[\t\n\v\f\r\p{Z}]
\.: 匹配是否包含.字符传

let reg3 = /\./;
let str = 'a.c';
console.log(reg3.test(str)); // 输出: true

7.^: 表示字符串的开头,匹配字符串的开头
8.$: 表示字符串的结尾,匹配字符串的结尾
9.*: 表示匹配前面的字符0次或多次
10.+: 表示匹配前面的字符1次或多次
11.?: 表示匹配前面的字符0次或1次
12.{n}: 表示匹配前面的字符n次
举例:

let reg4 = /a{3}/; // 匹配字符串中是否包含 aaa
let str = 'aaaa';
console.log(reg4.test(str)); // 输出: true

13.{n,}: 表示匹配前面的字符n次或n次以上
举例:

let reg5 = /a{3,}/; // 匹配字符串中是否包含aaa或更多个a
let str = 'aaaa';
console.log(reg5.test(str)); // 输出: true

14.{n,m}: 表示匹配前面的字符n次到m次

完全匹配字符串

^a$: 表示字符串完全匹配a

正则表达式的exec方法执行多次返回所有匹配结果

reg.exec(str): 检查字符串中是否存在符合正则表达式规则的字符,返回一个数组,该数组中索引对应的值为匹配结果

// 匹配包含以a开头,以c结尾,中间是任意字母的字符串,不区分大小写,全局匹配(返回多个匹配结果)
let reg = /a[a-z]c/ig;
let str = "abcazcaxc";
let result = reg.exec(str);
while (result) {
    console.log(result);
    result = reg.exec(str);
}

返回一个数组,该数组中索引对应的值为匹配结果,循环获取匹配结果
img


通过()分组,获取匹配结果中的子项

let reg2 = /a([a-z])c/ig;
let str2 = "abcazcaxc";
let result2 = reg2.exec(str2);
while (result2) {
    console.log(result2);
    result2 = reg2.exec(str2);
}

每次循环获取的数组中,返回的索引对应值分别是匹配结果和子项
img

注意

1.使用exec遍历正则匹配结果时,要确保最后的匹配结果为null,否则会陷入死循环

练习_在一串字符串中获取匹配所有的手机号

在一个字符串中取出手机号

let str3 = "15676565432gdsgsdgs8787717654565789899813509098987"
let reg3 = /1[3-9]\d{9}/g;
let result3 = reg3.exec(str3);
while (result3 = reg3.exec(str3)) {
    console.log(result3[0]); // 输出: 15676565432 13509098987
}

实现手机号脱敏效果

let str3 = "15676565432gdsgsdgs8787717654565789899813509098987"
let reg3 = /(1[3-9]\d)\d{4}(\d{4})/g;
let result3 = reg3.exec(str3);
while (result3 = reg3.exec(str3)) {
    console.log(result3[1] + '****' + result3[2]); // 输出: 176****5789 135****8987
}

字符串的split方法使用正则表达式拆分字符串

let str = '刘邦abc萧何adc张良ac韩信';
// 匹配a开头c结尾,a和c中间是任意长度的字符串 进行分隔  
let reg = /a[a-z]{0,}c/ig;
str.split(reg).forEach((item, index) => {
    console.log(item); // 输出: 刘邦 萧何 张良 韩信
});

字符串的search方法根据正则搜索

str.search(reg): 根据正则表达式搜索字符串,返回首次匹配到的字符的索引,如果没有匹配到则返回-1

let str = 'hello world';
let reg = /o/;
console.log(str.search(reg)); // 输出: 4

字符串的replace方法根据正则替换字符串

str.replace(reg, newStr): 根据正则表达式替换字符串,返回替换后的字符串,不改变原字符串

let str = 'hello world';
let reg = /o/;
let result = str.replace(reg, 'a');
console.log(result); // 输出: hella world

当正则表达式为全局匹配时,replace返回替换后的字符串,不改变原字符串

let str = 'hello world';
let reg = /o/g;
let result = str.replace(reg, 'a');
console.log(result); // 输出: hella warld

字符串的match方法根据正则匹配字符串

str.match(reg): 根据正则表达式匹配字符串,返回一个数组,该数组中索引对应的值为匹配结果

let str = 'hello world';
let reg = /o/;
let result = str.match(reg);
console.log(result);

img


当正则表达式为全局匹配时,match返回一个数组,该数组只展示匹配到结果值

let str = 'hello world';
let reg = /o/g;
let result = str.match(reg);
console.log(result); // 输出: ['o', 'o']

img


字符串matchAll方法根据正则匹配所有字符串

使用match方法时正则表达式必须是全局模式g,返回一个迭代器

let str = 'hello world';
let reg = /o/g;
let result = str.matchAll(reg);
for (const item of result) {
    console.log(item);
}

img


垃圾回收gc(garbage collection)

垃圾回收是删除任何其他不被其他对象使用的对象的过程。垃圾收集通常缩写为“GC”,是 JavaScript 中使用的内存管理系统的基本组成部分
将变量设置为null,那么对象的引用就不会被引用,让垃圾回收器回收对象内存,提高内存使用效率和性能


DOM

API: (Application Programming Interface)是应用程序开发接口的简称
DOM: Document Object Model,文档对象模型,是 HTML 和 XML 文档的编程接口,它提供了对文档的结构化表示,并定义了一种方式,使程序和脚本能够动态地访问和更新文档的内容、结构和样式
DOM提供者: DOM由浏览器厂商(如 Chrome、Firefox)根据 DOM 标准实现其功能,因此DOM的具体功能由浏览器实现
DOM和浏览器之间的关系: DOM 独立于 JavaScript,但 JavaScript 是最常用于操作 DOM 的语言,是js和html的桥梁

DOM作用: 它能把HTML中的任何东西都看作是对象,通过DOM可以获取到HTML中的任何元素,修改元素的属性,样式,内容等

对象模型:
通过对象模型可以找到各个元素之间的关系
img


元素对象的内容是会随着html页面的修改而动态更新

<div>你好</div>
<script>
    let div = document.getElementsByTagName('div')[0];
    console.log(div.innerText);
</script>

当页面内容修改该后,元素对象的内容也会随之修改
img


innerText和innerHTML的区别

innerText: 获取元素内的文字,不包含子元素标签,在设置标签内容时不会解析标签,而是把标签看成文本
innerHTML: 获取元素内的内容,包含子元素标签,在设置标签内容时可以解析标签

获取区别:

<div id="text"><a>你好</a></div>
<div id="html"><a>你好</a></div>

<script>
    console.log(document.getElementById('text').innerText); // 输出: 你好
    console.log(document.getElementById('html').innerHTML); // 输出: <a>你好</a>
</script>

设置区别:

<div id="text"></div>
<div id="html"></div>
<script>
    document.getElementById('text').innerText = '<a>hello</a>';
    document.getElementById('html').innerHTML = '<a>hello</a>';
</script>

img


document原型链

console.log(document); // document对象
console.log(document.__proto__); // HTMLDocument对象
console.log(document.__proto__.__proto__); // Document对象
console.log(document.__proto__.__proto__.__proto__); // Node对象
console.log(document.__proto__.__proto__.__proto__.__proto__); // EventTarget对象
console.log(document.__proto__.__proto__.__proto__.__proto__.__proto__); // Object对象
console.log(document.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__); // null

document对象的原型链: HTMLDocument -> Document -> Node -> EventTarget -> Object -> null


元素节点

getElementsByClassName(): 根据类名获取元素节点,返回数组

<span class="sp"></span>
<span class="sp"></span>
<span class="sp"></span>
<script>
let spArr = document.getElementsByClassName('sp');
console.log(spArr);
</script>

img


属性:
document.htmlElement: 获取html元素节点,返回一个对象
document.head: 获取head元素节点,返回一个对象
document.body: 获取body元素节点,返回一个对象
document.title: 获取title元素节点,返回一个字符串
document.URL: 获取当前页面的URL,返回一个字符串
document.domain: 获取当前页面的域名,返回一个字符串


创建一个元素节点并添加到某个节点末位:
document.createElement(tagName): 创建一个元素节点,返回一个对象

<span id="box"></span>
<script>
    let box = document.getElementById('sp');
    let span = document.createElement('span');
    span.innerText = 'hello';
    box.appendChild(span);
</script>

img


获取当前元素子节点

<div class="div">
    <span class="sp">你好</span>
    <span class="sp">你好</span>
</div>
<script>
    let div = document.getElementsByClassName('div')[0];
    console.log(div.childNodes);
</script>

打印出5个节点
img
原因:
1.连续的空白、文本、换行会解析为一个文本节点
2.元素标签后存在换行或空白也会解析为一个本文节点
3.元素节点解析一个元素节点

即使换行符也会被当作一个文本节点,因此childNodes仍然是5个

<div class="div">

    1


    <span class="sp">你好</span>
    <span class="sp">你好</span>
</div>
<script>
    let div = document.getElementsByClassName('div')[0];
    console.log(div.childNodes);
</script>

节点分布:
img

把代码压缩后,元素的childNodes才是子元素个数

<div class="div"><span class="sp">你好</span><span class="sp">你好</span></div>
<script>
    let div = document.getElementsByClassName('div')[0];
    console.log(div.childNodes); // 两个节点
</script>

获取当前元素所有子元素

children: 获取当前元素的所有子元素,返回一个类数组

<div class="div">
    <span class="sp">你好</span>
    <span class="sp">你好</span>
</div>
<script>
    let div = document.getElementsByClassName('div')[0];
    console.log(div.children); // 两个节点
    console.log(div.children);
    console.log(div.children instanceof Array);
    console.log([...div.children] instanceof Array);
</script>

img

注意:
childern返回一个类数组,但不能使用数组的方法,可以使用扩展运算符将其转换为数组


获取元素的第一个子节点和第一个子元素

firstChild: 获取当前元素的第一个子节点,返回一个对象
firstElementChild: 获取当前元素的第一个子元素,返回一个对象

<div class="div">
    <span class="sp">你好</span>
    <span class="sp">你好</span>
</div>
<script>
    let div = document.getElementsByClassName('div')[0];
    console.log(div.firstChild); // 文本节点
    console.log(div.firstElementChild); // span节点
</script>

获取当前元素下一个兄弟元素

nextElementSibling: 获取当前元素的下一个兄弟元素,返回一个对象


获取当前元素上一个兄弟元素

previousElementSibling: 获取当前元素的上一个兄弟元素,返回一个对象


获取当前元素标签名

tagName: 获取当前元素的标签名,返回一个字符串


获取其父元素

parentNodeparentElement: 获取当前元素的父元素,返回一个对象


querySelector: 根据选择器获取元素节点,返回一个对象

querySelectorAll: 根据选择器获取元素节点,返回一个数组


当标签中有id属性时,可以不通过选择器获取元素对象,浏览器已经通过id获取到了元素对象,可以直接使用

但不推荐这样做

<div id="div"></div>
<script>
    console.log(document.getElementById('div')); // 获取到div元素对象
    console.log(div); // 获取到div元素对象
</script>

本文节点

在DOM中所有的文本都是文本节点,包括标签中的空白、换行、文本内容,一个文本节点被标签所分隔

获取或修改某个元素中文本节点内容

innerText: 对于可见元素,获取某个元素文本内容,会把元素的后代标签去掉只保留内容,不保留换行和空格,考虑样式重绘
对于不可见元素,浏览器会直接返回原始文本(包括空格)
textContent: 获取某个元素文本内容,会把元素的后代标签去掉只保留内容,保留换行和空格,不考虑样式重绘
innerHTML: 获取某个元素文本内容,保留标签,保留换行和空格

<div class="div">
    你好
    <div>你好1</div>
</div>
<script>
    let div = document.getElementsByClassName('div')[0];
    console.log(div.innerText);
    console.log(div.textContent);
    console.log(div.innerHTML);
</script>

innerText: 把后代标签、换行、空格去掉只保留内容
textContent: 把后代标签去掉只保留内容,保留换行和空格
innerHTML: 保留标签,保留换行和空格
img


对于不可见元素,其innerText也返回了原始文本和textContent一样

<div class="div" style="display: none">
    你好
</div>
<script>
    let div = document.getElementsByClassName('div')[0];
    console.log(div.textContent);
    console.log(div.innerText);
</script>

img


innerText: 受子元素样式效果影响
textContent: 不受子元素样式效果影响

<div class="div">
    <span style="display: none">你好</span>
</div>
<script>
    let div = document.getElementsByClassName('div')[0];
    console.log(div.textContent); // 原文输出子元素文本,不受样式影响
    console.log(div.innerText); // 空字符串,受样式影响
</script>

img


修改文本内容时区别:
innerText和textContent: 空格也会当做文本,会把标签当做文本,也就是说会把标签转移为文本
innerHTML: 会把标签解析为html标签,有被xss(Cross-site Scripting跨站脚本攻击)注入的风险<script>病毒</scritp>

<div class="div">
</div>
<div class="div2">
</div>
<div class="div3">
</div>
<script>
    document.getElementsByClassName('div')[0].innerText = 'hello  ';
    document.getElementsByClassName('div2')[0].textContent = 'hello  ';
    document.getElementsByClassName('div3')[0].innerHTML = '<span>hello</span>';
</script>

img


属性节点

读取元素属性值:
方式一: 元素.属性名
注意:
class属性需要使用className来获取

let inputValue = input.value; // 获取input的value属性值
let className = div.className; // 获取div的class属性值  
let disabled = input.disabled; // 输出: true/false  

方式二:
getAttribute('属性名'): 获取元素属性值

<div class="div,div1">
</div>
<script>
    const div = document.getElementsByTagName('div')[0];
    console.log(div.getAttribute("class")); // 输出: div,div1
</script>

修改元素属性值:
方式一: 元素.属性名 = 属性值
方式二: 元素.setAttribute('属性名', '属性值')
注意: 对于只有属性名没有属性值的属性,也要设置值,可以设置true/false

input.setAttribute('disabled', true); // 设置input的disabled属性为true

删除属性:
removeAttribute('属性名'): 删除元素属性

input.removeAttribute('disabled'); // 删除input的disabled属性

自定义属性

自定义属性:
data-属性名: 自定义属性,在html中用data-属性名表示,在js中用dataset.属性名获取自定义属性值

<div data-id="123"></div>
<script>
    const div = document.getElementsByTagName('div')[0];
    console.log(div.dataset.id); // 输出: 123
</script>

文档的加载

1.根据html自上而下加载渲染代码,因此body中的代码应该写在script代码前,进行渲染完成后便于js操作
2.根据规范<html>标签中只有<head><body>标签,因此<script>写在body中最底部

确保页面加载完成后,执行逻辑方法

1.window.onload事件: 当页面加载完成时触发该事件,可以保证页面加载完成后再执行代码

window.onload = function () {
    console.log('页面加载完成');
}

2.addEventListener('load', function () {} )

window.addEventListener('load', function () {
    console.log('页面加载完成');
})

2.DOMContentLoaded事件: 当页面加载完成时触发该事件,可以保证页面加载完成后再执行代码

document.addEventListener('DOMContentLoaded', function () {
    console.log('页面加载完成');
})

3.将代码编写到外部的js文件中,使用<script>标签引入,并添加<script>属性defer,表示延迟执行,等待页面加载完成后再执行
<script defer src="../js.js" />

注意

1.load: 当页面中所有资源都加载完成时,才会触发该事件,例如页面中嵌套了其他iframe页面,也要等待iframe加载完成后才会触发该事件
2.DOMContentLoaded: 当页面中DOM结构加载完成时,就会触发该事件,不需要其他等待资源加载完成,回调函数执行时机比load更早
3.defer: 延迟执行,等待页面加载完成后再执行,执行时机要比DOMContentLoaded早

执行时机早晚: doby中script标签中的代码 > defer > DOMContentLoaded > load

练习_全选效果

在事件的影响函数中,this指向事件源,即触发事件的元素

checkAll.addEventListener('click', function () {
    console.log(this); // this指向事件源,即checkAll,就是全选元素对象
    // 将全部的checkBoxs设置选中状态和全选元素选中状态保持一致  
    for (let i = 0; i < checkBoxs.length; i++) {
        items[i].checked = this.checked;
    }
})

当每一个checkbox都选中时,全选按钮也应该选中,当有一个checkbox不选中时,全选按钮也应该不选中

let checkBoxs = document.querySelectorAll('[name=checkItem]:checked'); // 获取所有checkbox元素
checkAll.checked = checkBoxs.length === items.length;

操作元素

添加元素和修改元素

appendChild: 将新元素添加到指定元素的子元素列表的末尾

新元素 = document.createElement('标签名'); // 创建一个新元素
element.appendChild(新元素); // 将新元素添加到指定元素的子元素列表的末尾 

insertAdjacentElement: 将新元素添加到指定元素的位置

新元素 = document.createElement('标签名'); // 创建一个新元素
element.insertAdjacentElement('位置', 新元素); // 将新元素添加到指定元素的位置 

位置:
beforebegin: 元素自身的前面
afterbegin: 元素内部的第一个子元素之前
beforeend: 元素内部的最后一个子元素之后
afterend: 元素自身的后面

insertAdjacentHTML: 将html字符串添加到指定元素的位置

element.insertAdjacentHTML('位置', 'html字符串'); // 将html字符串添加到指定元素的位置 

img

添加重复元素问题

追加元素方法都会去判断这个父元素里面是否拥有这个元素对象,如果有的话就不会再添加了,因此需要创建新对象后再追加

for (let i = 0; i < length; i++) {
    snake.appendChild(snakeNode.cloneNode());
}

替换元素

replaceChild: 替换指定元素的子元素

新元素 = document.createElement('标签名'); // 创建一个新元素
element.replaceChild(新元素, 子元素); // 替换指定元素的子元素
<div>
    <span>你好</span>
</div>
<script>
    let div = document.querySelector('div');
    let span = document.querySelector('span');
    let p = document.createElement('p');
    p.textContent = 'hello';
    div.replaceChild(p, span);
</script>

img


替换当前元素
replaceWith: 替换当前元素

新元素 = document.createElement('标签名'); // 创建一个新元素
element.replaceWith(新元素); // 替换当前元素

删除元素

removeChild: 删除指定元素的子元素

element.removeChild(子元素); // 删除指定元素的子元素 

remove: 删除当前元素

element.remove(); // 删除当前元素 

练习_删除表格中的行

通过行对象获取单元格操作对象,为操作对象添加删除事件,当点击删除按钮时,删除该行对象

let tr = document.createElement('tr');
tr.innerHTML = `<td>${name}</td><td>${age}</td><td>${hobby}</td><td><button>删除</button></td>`;
tbody.appendChild(tr);

tr.querySelector('button').onclick = function () {
    tbody.removeChild(tr); // 将td中的删除按钮绑定了点击事件,并删除其父元素tr  
}

添加元素文本防止xss脚本注入风险

textContentinnerText都可以防止xss脚本注入风险
innerHTMLinsertAdjacentHTML都不能防止xss脚本注入风险


复制节点对象cloneNode()方法

cloneNode(): 复制节点对象,返回新的节点对象,保留原始节点的属性和标签事件,不保留子节点以及动态事件
cloneNode(true): 复制节点对象,返回新的节点对象,保留原始节点的属性和标签事件,保留子节点和子节点标签中事件,但不会复制动态事件

<div class="div" onclick="alert('你好')">你好<span onlick="alert('你好')">你好</span></div>
<script>
    let div = document.querySelector('.div');
    div.onlick = function () {
        alert('div被点击了');
    }
    let newDiv = div.cloneNode(true); // clone原始节点返回新的div节点对象,保留原始div的属性和标签事件,不保留子节点以及动态事件
    console.log(newDiv); // 输出: <div class="div" onclick="alert('你好')"></div>
    newDiv.innerText = 'hello';
    document.body.appendChild(newDiv);
    let newDiv2 = div.cloneNode(true); 
    console.log(newDiv2);
</script>

img


操作样式

修改css样式属性值

修改样式语法: element.style.样式属性名 = 样式属性值

注意

1.如果样式名中含有-,需要将-去掉,并将后面的字母大写,例如background-color需要写成backgroundColor

<style>
    .div {
        width: 100px;
        height: 100px;
        background-color: red;
    }
</style>
<div class="div">你好</div>
<button>点我</button>
<script>
    var div = document.querySelector('div')
    var btn = document.querySelector('button')
    btn.onclick = function () {
        // 随机颜色
        var r = Math.floor(Math.random() * 256)
        var g = Math.floor(Math.random() * 256)
        var b = Math.floor(Math.random() * 256)
        div.style.backgroundColor = 'rgb(' + r + ',' + g + ',' + b + ')'
    }
</script>

img

2.使用!important的样式优先级最高,js也不能直接修改,因为js修改样式是为元素添加内联样式,而!important的样式优先级最高,因此不会生效


getComputedStyle()读取css样式

getComputedStyle(): window静态方法,可以直接使用,获取指定对象正在生效的样式
由于样式优先级存在,因此不能通过元素.style.样式属性名来获取样式,需要使用getComputedStyle()方法来获取正在生效的样式

<style>
    .div {
        width: 100px;
        height: 100px;
        background-color: red !important;
    }
</style>
<div class="div">你好</div>
<button>点我</button>
<script>
    var div = document.querySelector('div')
    var btn = document.querySelector('button')
    btn.onclick = function () {
        // 随机颜色
        var r = Math.floor(Math.random() * 256)
        var g = Math.floor(Math.random() * 256)
        var b = Math.floor(Math.random() * 256)
        div.style.backgroundColor = 'rgb(' + r + ',' + g + ',' + b + ')'
        console.log(getComputedStyle(div).backgroundColor); // 输出: rgb(255, 0, 0)
    }
</script>

img

getComputedStyle()方法参数

参数一: 要获取的元素对象
参数二: 可选参数,伪元素

<style>
    .div {
        width: 100px;
        height: 100px;
        background-color: red !important;
    }

    .div::before {
        content: 'before';
        display: block;
        width: 50px;
        height: 100px;
        background-color: blue;
    }
</style>
<div class="div">你好</div>
<script>
    var div = document.querySelector('div')
    console.log(getComputedStyle(div, '::before').width);
</script>

img

注意

使用getComputedStyle()获取到的样式值并不一定带有数字,如auto,因此需要判断返回值能否进行计算,然后再计算

<style>
    .div {
        height: 100px;
        width: auto;
        left: auto;
        background-color: red !important;
    }
</style>
<div class="div">你好</div>
<script>
    var div = document.querySelector('div')
    let width = getComputedStyle(div).width;
    let auto = getComputedStyle(div).left;
    console.log(width); // 输出: 浏览器计算的宽度值
    console.log(auto); // 输出: 由于left不生效,浏览器未计算,因此返回auto
    if (!Number.isInteger(parseInt(auto))) {
        console.log('不是整数');
    }
    if (Number.isInteger(parseInt(width))) {
        console.log('是整数');
    }
</script>

img


通过元素对象获取样式

element.clientHeight: 获取元素对象的高度(内容区高 + 内边距高)
element.clientWidth: 获取元素对象的宽度(内容区宽 + 内边距宽)
element.offsetWidth: 获取元素对象的宽度(内容区宽 + 内边距宽 + 边框宽)
element.offsetHeight: 获取元素对象的高度(内容区高 + 内边距高 + 边框高)
element.scrollWidth: 获取元素对象的宽度(内容区宽 + 内边距宽 + 溢出内容宽)
element.scrollHeight: 获取元素对象的高度(内容区高 + 内边距高 + 溢出内容高)
溢出内容高: 父元素内边距以外的部分为溢出高度,因此溢出高度包括了父元素的边框高度,同时包括了子元素的内边距和边框

<style>
    .div {
        height: 100px;
        width: 200px;
        padding: 50px;
        border: 5px solid black;
    }
    .inner {
        height: 300px;
        background-color: aqua;
        border: 10px solid red;
    }
</style>
<div class="div"><div class="inner">你好</div></div>
<script>
    var div = document.querySelector('.div')
    console.log(div.clientWidth); // 输出: 300
    console.log(div.clientHeight); // 输出: 200
    console.log(div.offsetWidth); // 输出: 310
    console.log(div.offsetHeight); // 输出: 210
    console.log(div.scrollHeight);
</script>

scrollHeight = clientHeight + 子元素溢出部分
img


element.offsetParent: 获取当前元素的最近的一个祖先元素,并且该祖先元素开启了定位,如果祖先元素都没有开启定位,那么就返回body元素
element.scrollTop: 获取当前元素滚动条距离top的高度值
img


element.offsetLeft: 获取当前元素相对于offsetParent的left值
子元素的边框外缘距离定位元素的边框内缘的距离,该距离包括了定位元素的内边距,但不包含定位元素的边框,包括了子元素的外边距
element.offsetTop: 获取当前元素相对于offsetParent的top值
前元素相对于其 offsetParent 元素的顶部内边距的距离

<style>
    .div1 {
        width: 150px;
        height: 150px;
        background-color: gray;
        position: relative;
        border: 10px solid red;
        padding: 10px;
    }

    .div2 {
        width: 100px;
        height: 100px;
        background-color: aqua;
        position: absolute;
        border: 1px solid red;
        margin: 1px;
    }
</style>
<div class="div1">
    <div class="div2">
    </div>
</div>
<script>
    let div2 = document.querySelector('.div2');
    console.log(div2.offsetLeft); // 输出: 11
    console.log(div2.offsetTop); // 输出: 11
</script>

img

注意

offsetLeft和定位元素left属性的区别
offsetLeft包含子元素的外边距,定位元素的left属性不包含子元素的外边距

同理,offsetTop包含子元素的外边距,定位元素的top属性不包含子元素的外边距


操作class属性值

element.className: 修改元素的class属性值,可以同时修改多个class属性值,多个class属性值之间用空格隔开

<style>
    .div {
        height: 100px;
        width: 200px;
        background-color: red;
    }

    .div2 {
        height: 100px;
        width: 200px;
        background-color: blue;
    }
</style>
<div class="div"></div>
<script>
    var div = document.querySelector('.div');
    div.onclick = function () {
        console.log('修改前: ',this.className);
        div.className = 'div2';
        console.log('修改后: ',this.className);
    }
</script>

img

element.className += ' classname': 在原来的class属性值后面添加一个class属性值,注意:要加空格


element.classList: 获取元素的所有class值,返回一个类数组

<div class="div div2"></div>
<script>
    var div = document.querySelector('.div');
    div.onclick = function () {
        console.log('修改前: ',this.className);
        console.log(div.classList);
        console.log('修改后: ',this.className);
    }
</script>

img


element.classList.add(class属性值): 添加一个class属性值,如果已经存在不会再次添加


移除一个或多个样式
element.classList.remove(class属性值,class属性值, ...): 移除一个或多个class属性值

div.onclick = function () {
    console.log('移除前: ',this.className);
    this.classList.remove('div2');
    console.log('移除后: ',this.className);
}

img


element.classList.toggle(): 切换一个class属性值,如果元素有该class属性值,则移除,如果没有该class属性值,则添加

<style>
    .div {
        height: 100px;
        width: 200px;
        background-color: red;
    }

    .div2 {
        height: 100px;
        width: 200px;
        background-color: blue;
    }
</style>
<div class="div"></div>
<script>
    var div = document.querySelector('.div');
    div.onclick = function () {
        console.log('切换前: ', this.className);
        this.classList.toggle('div2');
        console.log('切换后: ', this.className);
    }
</script>

img


判断是否包含一个样式
element.classList.contains(): 判断元素是否包含某个class属性值,返回布尔值

<div class="div"></div>
<script>
    var div = document.querySelector('.div');
    console.log(div.classList.contains('div')); // true
</script>

替换
element.classList.replace(oldClass, newClass): 替换一个class属性值

<div class="div"></div>
<script>
    var div = document.querySelector('.div');
    div.classList.replace('div', 'div2');
</script>

事件

事件: 事件是用户与浏览器交互的行为,例如点击、输入、滚动等

绑定响应函数的方式:
1.标签中直接添加事件属性
2.通过为元素对象设置属性,其值为回调函数

input.onclick = function(){}; // 绑定点击事件

只能绑定一个回调函数,触发条件后只能执行一个回调函数

3.通过addEventListener方法绑定事件

<div class="click">点我</div>
<script>
    document.getElementsByClassName('click')[0].addEventListener('click', function () {
        console.log('第一个回调函数');
    });

    document.getElementsByClassName('click')[0].addEventListener('click', function () {
        console.log('第二个回调函数');
    });
</script>

可以绑定多个回调函数,触发条件后可以执行多个回调函数
img


事件对象

事件对象: 当动作触发事件时,浏览器会创建一个事件对象,并传递给事件处理函数,包含了当前事件的所有信息,例如事件源、鼠标位置、键盘按键等

element.onclick = event => {
    console.log(event); // 输出: 事件对象
}

MDN事件对象: https://developer.mozilla.org/zh-CN/docs/Web/API/Event

event.target: 事件源对象(触发事件的元素)

如果绑定事件的元素就是触发事件元素,那么event.target === this true
如果在事件冒泡场景中,event.target === this false

事件冒泡

事件冒泡: 当一个元素接收到事件对象的时候(触发事件),会把他接收到的事件对象传给自己的父级,如果父级监听了该事件就会执行父级的回调函数,一直向上传播,直到window对象,这种传播过程称为事件冒泡,事件冒泡是默认存在的,和当前元素有无监听事件无关,即使没有监听事件,事件对象也会向上传播

<style>
    .div {
        height: 200px;
        width: 200px;
        background-color: red;
    }
    .div2 {
        height: 100px;
        width: 100px;
        background-color: blue;
    }
</style>
<div class="div">
    <div class="div2"></div>
</div>
<script>
    var div = document.querySelector('.div');
    var div2 = document.querySelector('.div2');
    div.onclick = function (event) {
        console.log('div被点击了', 'this:', this, `event.target === this ${event.target === this}`);
    }
    div2.onclick = function (event) {
        console.log('div2被点击了', 'this:', this, `event.target === this ${event.target === this}`);
    }
</script>

div2的事件对象传递给了其父元素div的相同事件处理函数,所以div事件函数中的event.target === this为false
img

事件对象的产生和向上传导: 这两个动作和元素有无绑定事件无关

<style>
    .div {
        height: 200px;
        width: 200px;
        background-color: red;
    }
    .div2 {
        height: 100px;
        width: 100px;
        background-color: aqua;
    }
</style>
<div class="div">outer
    <div class="div2">inner</div>
</div>
<script>
    var div = document.querySelector('.div');
    var div2 = document.querySelector('.div2');
    div.onclick = function (event) {
        console.log('div被点击了', 'this:', this, `event.target === this ${event.target === this}`);
    }
</script>

inner没有绑定事件,在点击inner时其事件对象仍然传递给父元素outer的事件中,因此outer的事件函数接收的event就是inner的事件对象
当点击outer时,其事件对象就是outer元素的事件对象
img


通过事件对象阻止其向上传导,停止触发父元素的相同事件处理函数

event.stopPropagation(): 阻止事件冒泡

<style>
    .div {
        height: 200px;
        width: 200px;
        background-color: red;
    }
    .div2 {
        height: 100px;
        width: 100px;
        background-color: aqua;
    }
</style>
<div class="div">outer
    <div class="div2">inner</div>
</div>
<script>
    var div = document.querySelector('.div');
    var div2 = document.querySelector('.div2');
    div.onclick = function (event) {
        console.log('div被点击了', 'this:', this, `event.target === this ${event.target === this}`);
    }
    div2.onclick = function (event) {
        console.log('div2被点击了', 'this:', this, `event.target === this ${event.target === this}`);
        event.stopPropagation();
    }
</script>

img


取消事件默认行为

event.preventDefault(): 取消事件默认行为,例如a标签的跳转、表单的提交等
当a标签是属性方式绑定事件时,回调函数中return false,默认行为会被取消

<a href="https://www.baidu.com">百度</a>
<script>
    var a = document.querySelector('a');
    a.onclick = function (event) {
        console.log('a被点击了');
        return false;
    }
</script>

img

当以监听方式绑定事件时,回调函数中return false,默认行为不会被取消,应使用preventDefault()取消默认行为

a.addEventListener('click', function (event) {
    console.log('a被点击了');
    return false;
})

以监听方式绑定事件时,回调函数中return false,默认行为不会被取消,仍然可以跳转
img


使用preventDefault()取消默认行为

a.addEventListener('click', function (event) {
    console.log('a被点击了');
    // return false;
    event.preventDefault(); // 阻止默认事件行为
})

img


总结

1.事件对象的创建和向上传导,这两点和当前元素有无绑定事件无关
2.事件函数中的this: 绑定事件的元素,envent.target: 触发事件元素的事件对象,由于事件对象会向上传导,因此this不一定与event.target相等
3.event.currentTarget: 绑定事件的元素,和this一样,但不受事件冒泡的影响,始终指向绑定事件的元素
4.阻止事件冒泡: event.stopPropagation()
5.取消事件默认行为: event.preventDefault()适合标签属性绑定事件和监听方式绑定事件
6.事件冒泡和样式无关,和元素结构有关系,父子关系、层级关系有关等
7.阻止事件冒泡和阻止事件默认行为是两个不同的概念,互不干扰


练习_小球跟随鼠标移动

<style>
    .boll {
        width: 100px;
        height: 100px;
        border-radius: 50%;
        background-image: radial-gradient(white, gray);
        position: absolute;
        top: 0;
        left: 0;
    }
    .isolation {
        width: 300px;
        height: 300px;
        background-color: aqua;
    }
</style>
<div class="boll"></div>
<div class="isolation"></div>
<script>
    let boll = document.querySelector('.boll');
    let isolation = document.querySelector('.isolation');
    isolation.onmousemove = function (e) {
        e.stopPropagation();
    }
    document.onmousemove = function (e) {
        bollMove(e);
    }
    function bollMove(e) {
        let x = e.clientX;
        let y = e.clientY;
        console.log(x, y);
        boll.style.top = `${y}px`;
        boll.style.left = `${x}px`;
    }
</script>

为document绑定mousemove事件,获取鼠标坐标,然后将小球的top和left属性设置为鼠标坐标
img


事件冒泡的好处

模拟事件不冒泡,对盒子进行事件冒泡阻止

isolation.onmousemove = function (e) {
    e.stopPropagation();
}

小球不能够在盒子内部移动
img

上述代码模拟事件不会产生冒泡,而又想实现小球可以在盒子内移动,由于盒子没有任何事件,因此只能为盒子添加鼠标移动事件,如果盒子区域很多,那么要对所有的盒子都添加事件,这样会导致代码冗余,因此事件冒泡可以减少代码量,提高开发效率

事件冒泡: 事件从子元素向父元素传递,直到document对象为止,因此只需要在document上添加事件即可


为什么documen的事件效率高

为body添加事件,让小球移动

body.onmousemove = function (e) {
    bollMove(e);
}

img
为document添加事件,让小球移动

document.onmousemove = function (e) {
    bollMove(e);
}

img

从上图可以看出document的事件效率要高
原因:
核心原因在于事件传播机制和浏览器优化

浏览器的事件处理分为三个阶段:
1.捕获阶段(Capture Phase):事件从 window → document → ... → 目标元素的父级
2.目标阶段(Target Phase):事件到达目标元素
3.冒泡阶段(Bubble Phase):事件从目标元素的父级 → ... → document → window

绑定到子元素:
事件触发后,浏览器需在事件传播路径上检查每个层级元素的监听器。若子元素本身有监听器,浏览器需频繁触发回调函数,导致主线程任务堆积。
高频事件(如 mousemove、scroll)尤为明显,易引发卡顿。

绑定到 document:
事件会冒泡到顶层后统一处理。浏览器内部对顶层事件监听有优化:
合并事件触发:减少回调函数调用次数。
快速路径匹配:通过 e.target 快速判断事件来源,无需逐层检查。

img


事件委派

事件委派: 事件绑定到最外层元素,通过事件冒泡机制,实现对子元素事件的处理。

例如,像上个案例中小球移动为document绑定事件,然后通过事件冒泡机制,实现对小球移动的处理

事件委派带来的问题

<style>
    .boll {
        width: 100px;
        height: 100px;
        border-radius: 50%;
        background-image: radial-gradient(white, gray);
        position: absolute;
        top: 0;
        left: 0;
    }

    .isolation {
        width: 300px;
        height: 300px;
        background-color: aqua;
    }
</style>
<div class="boll"></div>
<div class="isolation"></div>
<script>
    let boll = document.querySelector('.boll');
    let isolation = document.querySelector('.isolation');
    document.onmousemove = function (e) {
        bollMove(e);
    }
    function bollMove(e) {
        let x = e.clientX;
        let y = e.clientY;
        console.log(x, y);
        boll.style.top = `${y}px`;
        boll.style.left = `${x}px`;
    }
</script>

通过事件委派,可以让我们写更少的代码(不需要为每个元素设置事件)实现触发事件的效果,但同时也来了一个问题
假如,只想让小球在盒子内部移动
img
因此需要通过判断事件源,来决定是否执行事件处理函数

document.onmousemove = function (e) {
    // 如果事件源是盒子那么就执行该函数
    if (e.target === isolation) {
        bollMove(e);
    }
}
function bollMove(e) {
    let x = e.clientX;
    let y = e.clientY;
    // 设置边界
    let maxH = isolation.clientHeight - boll.clientHeight;
    let maxW = isolation.clientWidth - boll.clientWidth;
    x = x > maxW ? maxW : x;
    y = y > maxH ? maxH : y;
    boll.style.top = `${y}px`;
    boll.style.left = `${x}px`;
}

可以通过事件源判断事件是从哪个元素触发的,从而决定是否执行事件处理函数
img


事件处理阶段

事件捕获阶段: 事件从window → document → ... → 目标元素的父级 → 目标元素触发
事件一直捕获到目标元素为止,事件才会停止捕获,捕获只是标记元素的监听器,并不会执行。例如点击div,事件会从window → document → body → div,不会继续捕获div的子元素的事件监听器,捕获的目的就是为了事件冒过程泡执行祖先元素的监听器
目标阶段: 事件到达目标元素,触发目标元素的事件监听器
冒泡阶段: 事件从目标元素的父级 → ... → document → window

为什么先执行了子元素的事件,该原因正是要遵循事件处理流程:
事件经过目标阶段,才会执行,因此子元素先执行事件回调函数,然后通过冒泡将事件对象传递给父元素,父元素再执行事件回调函数。

让监听事件在捕获阶段执行

addEventListener第三个参数: boolean,默认值为false,表示事件在冒泡阶段执行,如果设置为true,则表示事件在捕获阶段执行

<style>
    .div1 {
        width: 150px;
        height: 150px;
        background-color: gray;
    }
    .div2 {
        width: 100px;
        height: 100px;
        background-color: aqua;
    }
    .div3 {
        width: 50px;
        height: 50px;
        background-color: green;
    }
</style>
<div class="div1">
    div1
    <div class="div2">
        div2
        <div class="div3">div3</div>
    </div>
</div>
<script>
    document.querySelector('.div1').addEventListener('click', function () {
        console.log('div1');
    }, true); // true表示捕获阶段
    document.querySelector('.div2').addEventListener('click', function () {
        console.log('div2');
    }, true); // true表示捕获阶段
    document.querySelector('.div3').addEventListener('click', function () {
        console.log('div3');
    }, true); // true表示捕获阶段
</script>

在捕获阶段执行监听器后,在冒泡阶段就不会执行了
img


查看监听事件在哪个阶段执行

event.eventPhase: 0: 冒泡阶段 1: 捕获阶段 2: 目标阶段

document.querySelector('.div1').addEventListener('click', function (e) {
    console.log('div1');
    console.log(e.eventPhase);
}, true); // true表示捕获阶段
document.querySelector('.div2').addEventListener('click', function (e) {
    console.log('div2');
    console.log(e.eventPhase);
}, true); // true表示捕获阶段
document.querySelector('.div3').addEventListener('click', function (e) {
    console.log('div3');
    console.log(e.eventPhase);
}, true); // true表示捕获阶段

img


总结

1.事件捕获顺序: 事件从window → document → ... → 目标元素的父级 → 目标元素触发
2.事件冒泡顺序: 事件从目标元素的父级 → ... → document → window
3.设置监听器执行顺序: 设置第三个参数 true 按照捕获顺序执行,false 按照冒泡顺序执行
4.监听器执行顺序无论是捕获还是冒泡都可以使用stopPropagation阻止事件对象传播


BOM

BOM简介

BOM: Browser Object Model,浏览器对象模型,提供了独立于内容而与浏览器窗口进行交互的对象,其核心对象是window

window对象: 代表浏览器窗口,同时window也是网页的全局对象,所有在网页中声明的变量和函数都是属于window对象的属性
Navigator: 浏览器的属性对象,识别是哪个是厂商浏览器
Screen: 屏幕属性对象,获取屏幕的宽高
History: 浏览器的历史记录对象,可以操作浏览器的前进和后退
Location: 浏览器的地址栏对象,可以操作浏览器地址栏


Location对象

查看Location属性含义
img

修改location的地址实现跳转

<div>点我</div>
<script>
    document.querySelector('div').onclick = function () {
        location = 'https://www.baidu.com'; // 当前窗口跳转到百度
    }
</script>

img

location.href

location.href: 获取或设置当前页面的URL地址

console.log(location.href); // http://127.0.0.1:5500/11_BOM/01_2_%E8%AF%BB%E5%8F%96%E5%BD%93%E5%89%8D%E9%A1%B5%E9%9D%A2%E5%9C%B0%E5%9D%80.html

使用location.href设置跳转到指定地址,不会产生历史记录,因此不能回退

location.assign()

location.assign(): 跳转到指定地址,会生成历史记录,可以回退

location.replace()

location.replace(新url): 跳转到指定地址,不会生成历史记录,不能回退


location.reload()

location.reload(boolean): 重新加载当前页面,如果参数为true,则强制刷新,如果参数为false,则从缓存中加载

通过火狐浏览器演示效果

<input type="text">
<button>刷新</button>
<script>
    document.querySelector('button').onclick = function () {
        location.reload(); // 刷新当前页面
    }
</script>

img

当设置为true后
img


History对象

history.back(): 后退到上一个页面
history.forward(): 前进到下一个页面
history.go(n): n为正数,前进n个页面,n为负数,后退n个页面
history.length: 返回历史记录的个数


webStorage: 浏览器本地存储

localStorage: 本地存储,存储的数据不能自动删除,除非手动删除,否则一直存在

key-value 键值对
img

setItem(key,value): 存储/修改数据,返回值undefined

localStorage.setItem('key', 'value');

getItem(key): 获取数据,返回字符串,如果key不存在,返回null类型

let value = localStorage.getItem('key');

removeItem(key): 删除数据,无返回值,返回值undefined

localStorage.removeItem('key');

clear(): 清空所有数据,返回值undefined

localStorage.clear();

key(index): 获取指定索引的key,返回字符串,如果key不存在,返回null类型

let key = localStorage.key(index);

sessionStorage: 会话存储,存储的数据在浏览器关闭后自动删除

setItem(key,value): 存储/修改数据,返回值undefined

sessionStorage.setItem('key', 'value');

getItem(key): 获取数据,返回字符串,如果key不存在,返回null类型

let value = sessionStorage.getItem('key');

removeItem(key): 删除数据,无返回值,返回值undefined

sessionStorage.removeItem('key');

clear(): 清空所有数据,返回值undefined

key(index): 获取指定索引的key,返回字符串,如果index不存在,返回null类型

使用本地存储对象注意

localStorage或sessionStorage当读取不存在的key时,返回null类型,而不是undefined类型


定时器

setTimeout()

setTimeout(回调函数, 延迟时间): 延迟时间单位为毫秒,延迟时间到了之后,执行回调函数,并且只执行一次

参数:
1.回调函数: 延迟时间到了之后,执行的函数
2.延迟时间: 延迟时间到了之后,执行回调函数,单位为毫秒,1000毫秒 = 1秒

返回值: 返回一个定时器id,用于清除定时器

setTimeout(function () {
    console.log('延迟时间到了');
}, 1000);

清除定时器

clearTimeout(定时器id): 清除定时器,参数为定时器id,定时器id由setTimeout返回

let timer = setTimeout(function () {
    console.log('延迟时间到了');
}, 5000);
clearTimeout(timer);

setInterval()

setInterval(回调函数, 间隔时间): 间隔时间到了之后,执行回调函数,并且会重复执行,直到清除定时器

参数:
1.回调函数: 间隔时间到了之后,执行的函数
2.间隔时间: 间隔时间到了之后,执行回调函数,单位为毫秒,1000毫秒 = 1秒

返回值: 返回一个定时器id,用于清除定时器

setInterval(function () {
    console.log('延迟时间到了');
}, 1000);

清除定时器

clearInterval(定时器id): 清除定时器,参数为定时器id,定时器id由setInterval返回

let timer = setInterval(function () {
    console.log('延迟时间到了');
}, 1000);
clearInterval(timer);

事件循环

事件循环的工作原理基于几个关键概念:执行栈、任务队列、宏任务和微任务。当 JavaScript 代码执行时,同步代码首先进入执行栈按顺序执行。异步操作如定时器、网络请求等则被放入任务队列中。一旦执行栈清空,事件循环就会从任务队列中取出任务来执行。

函数调用栈

function fn2() {
    console.log('fn1');
}
function fn() {
    console.log('fn2');
    fn2();
}
fn();

函数被执行时,就会添加到函数调用栈中,执行完毕后就会从函数调用栈中移除
调用栈中有三个函数,最底层为匿名的script全局作用域,然后是fn,最后是fn2
img

1.调用栈中的函数都是有关系的,嵌套关系越深,关系越紧密
2.当事件触发时,js会先把回调函数放到消息队列中,等待调用栈中的函数执行完毕后,再从消息队列中取出回调函数执行

let date = new Date();
document.querySelector('button').onclick = function () {
    console.log('点击了按钮');
    const begin = Date.now();
    while (Date.now() - begin < 3000) { }
}

如下图,第二次点击按钮时不会立即执行,要等到第一次回调函数执行结束后,调用栈中为空时,才会把第二次触发的回调函数放到调用栈中执行
因此回调函数的执行,是先放到队列中等待调用栈为空时,再从消息队列中取出回调函数执行
消息队列数据结构实现异步执行效果
img


定时器

setTimeout(回调函数, 延迟时间): setTimeout函数本质是延迟时间到了,就会把回调函数添加到消息队列中
setInterval(回调函数, 间隔时间): setInterval函数本质是每隔一段时间,就会把回调函数添加到消息队列中

setInterval缺点

无法确定每次执行函数的间隔时间
原因是,有可能某次执行时间过程比较长,在这个期间内,队列中已经存放了好几个回调函数,那么一旦上次函数执行结束,立即就会执行队列中的回调函数

使用setTimeout解决函数执行间隔不平均的问题
保证了逻辑执行的时间间隔不少于指定的时间

let duration = 3000;
let fn = () => {
    setTimeout(fn, duration);
    console.log('定时器1');
};
setTimeout(fn, duration);

学习路线:
1.前端三剑客
2.css预处理: less,sass,stylus
3.工程化: webpack,rollup,vite
4.框架: vue,react,angular

posted @ 2025-06-10 15:17  ethanx3  阅读(36)  评论(0)    收藏  举报