Web API 基本认知
前菜:变量声明习惯以及const的进阶说明
建议用 const,因为 const 修饰的变量不可变,符合开发中的常见语义。
如果要改的话再用 let,拒绝使用 var。
为什么使用 const 修饰的复杂数据类型可以"变"?比如 数组 和 对象 数据类型。
以数组为例进行说明:
typescript
const arr = ['red', 'pink']
arr.push('blue')
console.log(arr) //输出 'red', 'pink', 'blue'
因为复杂数据对象是存储在堆空间中的,const 修饰的实际上是 arr,arr 指向的地址没变,那就是合法的,而地址指向的 堆空间的内容变化与 const 是没用关系的:

当我们让 const 修饰的 arr 指向另外一个新数组时,就会报错了,因此此时 arr 所代表的地址发生了变化:
typescript
arr = [1,2,3] //指向新数组报错


Web API的作用和分类
Web API 的作用就是让我们使用 JavaScript 去操作 HTML 和浏览器。
而 Web API 分为两种类型:DOM(文档对象模型)和 BOM(浏览器对象模型)。

什么是DOM
DOM 文档对象模型,是用来呈现以及与任意 HTML 或者 XML 文档交互的 API。
简单说就是 DOM 是浏览器提供的一套专门用来操作网页内容的功能。

DOM 树
DOM 树就是指 将 HTML 文档以树状结构直观的表现出来,我们称之为文档树或者DOM树。
这是一个描述网页关系的名词。
文档树可以清晰直观的表现标签与标签之间的关系。

DOM 对象
DOM对象:浏览器根据 HTML 标签生成的 JS 对象。
既然是对象,那么就会有对应的属性和方法,因此:
-
所有的标签属性都可以在这个对象上面找到。
-
修改这个对象的属性会自动映射到标签身上。
换句话说,比如 div 这个东西在 HTML 的体系里就叫标签,而在 JS 的体系里面就叫 DOM 对象。
DOM 的核心思想就是将 网页内容当作对象来处理。

document 对象是 DOM 里面提供的一个对象,所以它提供的属性和方法都是用来访问和操作网页内容的。
网页所有的内容都在 document 里。
DOM 获取元素
获取元素方式主要是根据 CSS 选择器来获取DOM元素,这是最现代和推荐的方式。
回想一下,我们以前想要操作某个HTML标签是怎么做的?
答案是通过 CSS 来操作标签的属性的:
typescript
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>网页标题</title>
<style>
.box {
height: 200px;
width: 300px;
}
</style>
</head>
<body>
<div class="box"></div>
</body>
</html>
DOM 获取对象和 CSS 选择器类似,首先要选中这个 HTML 标签,选中了标签才能操作。
查找 DOM 元素就是利用 JS 来选择页面中的标签元素。
语法:单个 DOM 对象获取
typescript
document.querySelector('css选择器');
参数:包含一个或者多个有效的 CSS 选择器字符串,CSS 选择器怎么用的这里就怎么用。
返回值:CSS 选择器匹配的第一个元素,一个 HTML Element 对象。
如果没用匹配到就返回 null。
这会选择匹配的第一个元素。
比如上面的例子,我们就可以获取:
typescript
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>网页标题</title>
<style>
.box {
height: 200px;
width: 300px;
}
</style>
</head>
<body>
<div class="box">123</div>
<div class="box">abc</div>
<script>
// 获取匹配的第一个元素
const domObj = document.querySelector("div");
console.log(domObj);
</script>
</body>
执行:

可以看到会自动匹配到第一个符合元素的对象。
但是这样看起来还不够清楚,实际上我们可以打印 dir :
typescript
const domObj = document.querySelector("div");
// 方式一:console.log (默认行为)
// 输出:<div class="box">123</div>
// 特点:以 HTML 标签形式展示,直观,适合看结构
console.log(domObj);
// 方式二:console.dir (推荐用于调试属性)
// 输出:Object { accessKey: "", ariaLabel: "", ... }
// 特点:以 JSON/对象树形式展示,适合查看具体属性值、事件监听器等
console.dir(domObj);
让其以对象树的方式来展示:

如上图所示,我们可以找到其很多div对象的属性:

在 JavaScript 调试中,console.dir() 用于以可交互的对象树形式打印 JavaScript 对象的属性,常用于查看 DOM 元素的详细结构,区别于 console.log() 的 HTML 标签式显示。
除了上面这种方式,我们还可以通过:
typescript
document.querySelector('.box');
的方式来指定选择哪一个 div 对象。
由于两个 div 都使用了 .box 选择器,因此还是获取匹配到的第一个 div 对象,因此和上面的结果输出一样,还是选择 123 的div。
比如我们写一个不一样的:
typescript
<style>
.box {
height: 200px;
width: 300px;
}
</style>
</head>
<body>
<div class="box">123</div>
<div class="box">abc</div>
<p id="nav">导航栏</p>
<script>
// 获取匹配的第一个元素
const domObj = document.querySelector("div");
console.dir(domObj);
// 指定获取某一个元素
const domObj2 = document.querySelector("#nav");
console.dir(domObj2);
</script>
执行效果:

总之就是可以通过 CSS 的各种风格的选择器语法来进行 DOM 的获取。
语法:多个 DOM 对象获取
选择匹配多个元素也很好做:
typescript
document.querySelectorAll('css选择器');
参数:包含一个或者多个有效的 CSS 选择器字符串。
返回值:CSS 选择器匹配的 NodeList 对象集合。
代码如下:
typescript
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>网页标题</title>
<style>
.box {
height: 200px;
width: 300px;
}
</style>
</head>
<body>
<div class="box">123</div>
<div class="box">abc</div>
<p id="nav">导航栏</p>
<script>
// 获取匹配的所有元素
const domDivObjs = document.querySelectorAll("div");
console.dir(domDivObjs);
</script>
</body>
</html>
执行效果如下:

但是要注意,使用 document.querySelectorAll() 方法返回的是一个伪数组:
- 有长度有索引号的数组;
- 但是没用 pop()、push() 等数组方法
想要得到里面的每一个对象,则需要通过 for() 循环的方式来获取。
哪怕只有一个元素,通过 querySelectorAll() 获取过来的也是一个伪数组,里面只有一个元素而已。
其它获取DOM元素的方法(了解,不使用了已经)

操作元素内容
主要是两个 HTML 标签,也就是 DOM 对象的属性:innerHTML 属性和 innerText 属性。
在打印对应的 DOM 对象时,我们是可以看到的:

元素 innerText 属性
将文本内容添加/更新到任意标签位置,显示的是纯文本,不解析标签。
代码:
typescript
<div class="box">123</div>
<script>
// 获取DOM对象
const domDivObjs = document.querySelector(".box");
//打印文本属性
console.log(domDivObjs.innerText);
//修改文本属性
domDivObjs.innerText = "456";
</script>
运行效果:

那么显示纯文本,不解析标签(指html标签)是什么意思呢?
借助刚刚的示例代码,我们在修改文本信息的时候加入 html 标签:
typescript
<div class="box">123</div>
<script>
// 获取DOM对象
const domDivObjs = document.querySelector(".box");
//打印文本属性
console.log(domDivObjs.innerText);
//修改文本属性,用html标签加粗
domDivObjs.innerText = "<strong>456</strong>";
</script>

可以看见当我们更改 innerText 属性时,如果是要把它改成 html 标签的形式修改当前这个属性是无法做到的。
哪个可以做到?
元素 innerHtml 属性
它会将文本内容添加/更新到任意标签位置。
它会解析标签,多标签的时候建议使用模板字符串。
typescript
<div class="box">123</div>
<script>
// 获取DOM对象
const domDivObjs = document.querySelector(".box");
//打印文本属性
console.log(domDivObjs.innerText);
//修改文本属性,用html标签加粗
domDivObjs.innerHTML = "<strong>456</strong>";
</script>

如果还在纠结到底用谁,那么直接用 innerHTML 就可以。
操作元素属性
操作元素常用属性
可以通过 JS 设置/修改标签元素的属性,比如通过 src 更换图片。
最常见的属性比如 href、title、src 等。
因为都是 DOM 对象的属性,因此操作方式和前文一致,这里不再赘述:

操作元素样式属性
可以通过 JS 设置/修改标签元素的样式属性。
主要有下面几种方式。
通过 style 属性操作CSS
语法:
typescript
对象.style.样式属性 = 值;
为什么是这样的?原因很简单,因为 style 本身也是个对象:

操作类名(className)操作CSS
但是上面这种方式有个问题是,如果一个标签元素有大量的属性可以修改,如果我们修改的比较多,那么采用修改 style 的形式就会写很多很多繁复的代码,就不是很方便。
我们可以换一种形式,如果修改的样式比较多,我们可以写一个类名,然后让这个类的属性加载到对应的标签元素身上,这个元素有了这个类名就可以执行这个类名下的多个样式了,就会简洁一些。
语法:
typescript
// active 就是一个 css 类名
元素.className = 'active'
注意:
1、由于class 是关键字,所以使用className去代替;
2、className 由于是新值换旧值,因此如果需要添加一个类,则需要保留之前的类名。
代码:
typescript
<style>
.box {
width: 200px;
height: 200px;
background-color: pink;
}
</style>
</head>
<body>
<div class="box">123</div>
<script>
// 获取DOM对象
const domDivObjs = document.querySelector(".box");
//打印文本属性
console.dir(domDivObjs);
//修改文本属性,用html标签加粗
domDivObjs.innerHTML = "<strong>456</strong>";
</script>
效果如下:

现在我们如果挨个去调用 style 对象去改高宽颜色的话,很费劲,因此我们可以直接写个 css 类,然后赋值给 className:
typescript
<style>
.box {
width: 200px;
height: 200px;
background-color: pink;
}
.active {
width: 300px;
height: 300px;
background-color: black;
margin-left: 100px;
}
</style>
</head>
<body>
<div class="box">123</div>
<script>
// 获取DOM对象
const domDivObjs = document.querySelector(".box");
// 添加类名
domDivObjs.className = "active";
</script>

但是要注意,在我们使用 className 的时候会覆盖前面的类名(也就是div 中的 class 关键字),比如我们上面的例子中,就是 .active 覆盖了 .box 的样式。
如果我们还想保留历史 类名 的样式的话,可以如下操作,为了便于观察,改写一下:
typescript
<style>
.box {
/*字体颜色*/
color: red;
}
.active {
width: 300px;
height: 300px;
background-color: black;
margin-left: 100px;
}
</style>
</head>
<body>
<div class="box">123</div>
<script>
// 获取DOM对象
const domDivObjs = document.querySelector(".box");
// 添加类名,同时保留之前类名的样式
domDivObjs.className = "box active";
</script>

通过classList操作类控制CSS
但是使用 className 的方式也还是有问题,也就是很容易覆盖前面的类名,为了解决这个问题,我们使用 classList 的方式来追加和删除类名,这种方式才是我们喜欢用的方式。
语法:
typescript
// 追加一个类
元素.classList.add('类名')
// 删除一个类
元素.classList.remove('类名')
// 切换一个类
元素.classList.toggle('类名')
typescript
<style>
.box {
/*字体颜色*/
color: red;
}
.active {
width: 300px;
height: 300px;
background-color: black;
margin-left: 100px;
}
</style>
</head>
<body>
<div class="box">123</div>
<script>
// 获取DOM对象
const domDivObjs = document.querySelector(".box");
// 添加类名来修改样式
domDivObjs.classList.add("active");
</script>

另外两个不再赘述。
操作表单元素属性
表单很多时候也需要修改属性,比如点击眼睛可以看到密码,本质是把表单类型转换为文本框。

正常的有属性有取值的,跟其它的标签属性没有任何区别。
获取语法:DOM对象.属性名;
设置语法:DOM对象.属性名 = 值;
代码如下:
typescript
<input type="text" value="电脑">
<script>
// 获取元素
const username = document.querySelector('input');
// 打印表单属性的值
console.dir(username.value)
</script>

注意要获取表单的内容必须使用 表单.value 来获取,而无法通过 表单.innerHTML 来获取。
我们可以再看如何设置值:
typescript
<input type="text" value="电脑" />
<script>
// 获取元素
const username = document.querySelector("input");
// 打印表单属性的值
console.dir(username.value);
// 将文本框设置成密码框
username.type = "password";
</script>

表单属性中除了上面常见的两种,还有一类属性,它们只要添加上就表示true也就是有效果,移除就为false也就是没有效果;
简单说就是如果为 true 代表添加了该属性,如果是false代表移除了该属性。
比如这些属性: disabled、checked、selected,这些值一律都是布尔类型。
比如我们设置一个复选框,也就是将输入框的类型改成 checkbox:
typescript
<input type="checkbox" name="" id="" />
<script>
// 获取元素
const ipt = document.querySelector("input");
// 打印表单中复选框的属性
console.dir(ipt.checked);
</script>

如果我们把 checked 属性加上:
typescript
<input type="checkbox" name="" id="" checked/>
<script>
// 获取元素
const ipt = document.querySelector("input");
// 打印表单中复选框的属性
console.dir(ipt.checked);
</script>

相信一定有所体会了。
因此我们可以通过将该值设置为 true 的方式表示勾选:
typescript
ipt.checked = true;
自定义属性
上面讲的都是标准属性,也就是标签元素自带的属性,比如 class 、id、title 等,可以直接使用点语法进行操作,比如 disabled、checked 等。
但是这在开发当中肯定是不够用的。
因此我们可以自定义属性:
在 HTML5 中推出来了专门的 data-自定义属性;
在标签上一律以 data- 开头;
在 DOM 对象上一律以 dataset 对象方式获取。
typescript
<div data-parm="haha" data-parm2="阿萨的"></div>
<script>
// 获取元素
const d = document.querySelector("div");
// 打印div中的自定义属性
console.dir(d.dataset);

我们也可以直接打印其中的自定义属性的值:
typescript
<div data-parm="haha" data-parm2="阿萨的"></div>
<script>
// 获取元素
const d = document.querySelector("div");
// 打印div中所有的自定义属性
console.dir(d.dataset);
// 打印其中某一个参数
console.log(d.dataset.parm);
</script>

定时器------间歇函数
网页中经常会需要一种功能:每隔一段时间需要自动执行一段代码,不需要我们去手动触发。
例如:网页中的倒计时。
要实现这种需求,我们需要定时器函数。
定时器函数有两种,这里先介绍间歇函数。
定时器函数基本使用
定时器函数可以开启和关闭定时器。
开启定时器语法:
typescript
setInterval(函数,间隔时间);
作用是每隔一段时间调用这个函数;
间隔单位是毫秒。
比如一秒钟往页面上输出一句话:
typescript
<script>
setInterval(function () {
console.log("哈哈");
}, 1000);
</script>

上图 哈哈 的左侧的数字表示执行了多少次。
注意:间歇函数一旦开始不关闭的话,将会永不停歇的运行。
而且定时器实际上是有个返回值的,返回的是一个 id 数字,这表示它是当前页面中的哪一个定时器,因为一个页面中可能存在多个定时器,id 标识了该定时器的唯一性身份标识。
关闭定时器语法:
typescript
let 变量名 = setInterval(函数, 间隔时间)
clearInterval(变量名)
比较简单,不再赘述。
DOM 事件基础
什么是事件?事件是在编程时系统内发生的动作或者发生的事情。
比如用户在网页上点击了一个按钮。
事件监听(绑定)
事件监听就是让程序检测是否有事件发生,一旦有事件触发,就立即调用一个函数做出响应,也称为绑定事件或者注册事件。
比如鼠标经过显示下拉菜单,比如点击就可以播放轮播图等等。
语法:
typescript
元素对象.addEventListener('事件类型', 要执行的函数);
事件监听三要素:
1、事件源:哪个DOM元素被事件触发了,要获取 DOM 元素;
2、事件类型:用什么方式触发的?比如鼠标单击 click,鼠标经过 mousehover 等等;
3、事件调用的函数:要做什么事情。
注意:事件类型需要加引号。
示例程序:
typescript
<button>点击</button>
<script>
// 需求:点击按钮出现一个对话框
// 1、先拿到事件源,也就是DOM对象
const btn = document.querySelector("button");
// 2、给DOM对象添加事件监听,是点击事件类型;
// 然后执行的操作就是出现一个对话框
btn.addEventListener("click", function () {
alert("你点击了我!");
});
</script>

拓展阅读,实际上还有一种写法,但是由于容易被覆盖,因此不用了:

发展史:

事件类型
常用的如下:

事件对象
事件对象是什么?
其实也是个对象,只不过这个对象里面有事件触发时的相关信息。
例如:鼠标点击事件中,事件对象就存了鼠标点在哪个位置等信息。
使用场景:
1、可以判断用户按下的是哪个键,比如按下回车键就可以发布新闻。
2、可以判断鼠标点击了哪个元素,从而做相应的操作。
如何获取事件对象呢?
语法:
- 在事件绑定的回调函数的第一个参数就是事件对象;
- 一般命名为 event、ev、e。
typescript
元素.addEventListener('click',function(event){});
上面事件对象就是 function 函数中的第一个参数 event,不管用什么变量来接收,它都是事件对象。
我们来看一下:
typescript
<button>点击</button>
<script>
const btn = document.querySelector("button");
btn.addEventListener("click", function (eventObj) {
// 打印点击事件的事件对象
console.dir(eventObj);
});
</script>

可以看见该对象里面有大量的属性可供我们使用。
事件对象常用属性

注意一个点:

以前用 keyCode,现在不推荐使用了,改用 key。
环境对象
环境对象,指的是函数内部特殊的变量 this,它代表着当前函数运行时所处的环境。
弄清楚 this 的指向,可以让我们的代码更简洁。
每个函数里面都有 this 对象:
typescript
<script>
function func() {
console.log(this);
}
func();
</script>

上面的 window 就是 this 所指向的对象,这个在后续讲 BOM 的时候会说。
因此实际上我们在 script 里调用函数的时候,是省略了一部分的:
typescript
<script>
function func() {
console.log(this);
}
window.func();
</script>
调用者是 window,是整个浏览器,因此在普通函数里面 this 指向的是 window 对象。
那么在事件对象当中,我们的代码如下:
typescript
<button>点击</button>
<script>
const btn = document.querySelector("button");
// 这里添加事件的回调函数的时候,传入的是个函数,那么一样会有个this
// 而this指向的是环境对象,因此这里搞清楚this指向的是谁就很重要
btn.addEventListener("click", function () {
// this 指向的是函数的调用者
console.dir(this);
});
</script>

可以看见此时 this 指向的是 button 对象,也就是说在这个事件的回调函数当中的环境对象就是 button。
函数的调用方式不同,this 指代的对象就不同。
因此谁调用,this 就是指向谁,是判断 this 指向的粗略规则。
因此在上面的例子中,如果我们想要改变颜色,除了之前的写法还可以这么写:
typescript
<button>点击</button>
<script>
const btn = document.querySelector("button");
// 这里添加事件的回调函数的时候,传入的是个函数,那么一样会有个this
// 而this指向的是环境对象,因此这里搞清楚this指向的是谁就很重要
btn.addEventListener("click", function () {
// btn.style.color = 'red';
this.style.color = "red";
});
</script>
因为 this 指向的就是 button 对象。
补充:回调函数
如果将函数 A 作为参数传递给函数 B 时,我们就称函数 A 为回调函数。
比如常见的使用场景:
typescript
function fn(){
console.log('我是回调函数')
}
// fn 传递给了 setInterval ,那么 fn 就是回调函数
setInterval(fn, 1000)
它的涵义就是过一秒钟,它就回头调用一下这个 fn 函数,这就是回调函数。
同理,事件监听中传递的函数参数也是回调函数。
DOM 事件进阶
事件流
事件流指的是事件完整执行过程中的流动路径。
事件流与两个阶段说明
事件流主要有两个方向:
1、从大范围到小范围,称为捕获阶段;
2、从小范围再返回去到大范围,称为冒泡阶段。

假设页面里有个 div,当触发事件时,会经历两个阶段,分别是捕获和冒泡阶段。
简单来说,捕获阶段是从父到子,冒泡阶段是从子到父。
实际工作当中都是使用事件冒泡为主。
事件捕获
这个知道是啥就行,实际工作中根本不用。
事件冒泡
当一个元素的事件被触发时,同样的事件将会在该元素的所有祖先元素中依次被触发,这一过程被称为事件冒泡。
我们可以看看:
typescript
<style>
.father {
width: 400px;
height: 400px;
background-color: red;
}
.son {
width: 200px;
height: 200px;
background-color: black;
}
</style>
<div class="father">
<div class="son"></div>
</div>
<script>
const father = document.querySelector(".father");
const son = document.querySelector(".son");
//依次添加事件
document.addEventListener("click", function () {
alert("爷爷");
});
father.addEventListener("click", function () {
alert("爸爸");
});
son.addEventListener("click", function () {
alert("儿子");
});
</script>

当我们点击最小 son 的那个 div 时,最先出现的是 儿子:

点击确定之后则会依次打印爸爸、爷爷:


这就是事件冒泡。
当一个元素的事件被触发时,同样的事件会在该元素的所有祖先元素中被依次触发,这一过程被称为冒泡。
简单的说就是当一个元素触发事件后,会依次向上调用所有父级元素的同名事件(同一种事件类型)。
因此事件冒泡是一定会发生的。
而我们一般情况下是不会希望事件冒泡发生的,如何阻止呢?
阻止冒泡
因为默认就有冒泡模式的存在,所以容易导致事件影响到父级元素。
若想把事件就限制在当前元素内,就需要阻止事件冒泡。
前提是:阻止事件冒泡需要拿到事件对象。
语法:
typescript
事件对象.stopPropagation();
注意 stopPropagation() 这个方法可以阻断事件流动传播,不光在冒泡阶段有效,捕获阶段也同样有效。
解绑事件
前面我们都是给元素添加上事件,同样我们也可以给元素解绑事件。
对于我们常用的 addEventListener() 绑定事件方法,解绑的方式同样是要用到一个函数:
typescript
removeEventListener(事件类型,事件处理函数,[获取捕获或者冒泡阶段])
比如:
typescript
function fn(){
alert('被点击了')
}
//绑定事件
btn.addEventListener('click',fn)
//解绑事件
btn.removeEventListener('click',fn)
注意:匿名函数无法被解绑!
补充:鼠标经过事件的区别

事件委托
事件冒泡也不是完全就是坏的,它也有有用一面,比如我们可以通过它来实现事件委托的机制。
啥时候需要事件委托呢?
最常用的场景就是:如果同时给多个元素注册事件,我们怎么做?
可以用 for 循环来注册事件。
那么有没有一种技巧就是注册一次事件就能完成以上效果呢?
什么是事件委托:就是利用事件流的一些特征解决一些开发需求的知识技巧。
用事件委托的优点就是:可以减少注册次数,提高程序性能。
原理就是利用了事件冒泡的特点:给父元素注册事件,当我们触发子元素的时候,会冒泡到父元素身上,从而触发父元素的事件。
typescript
<style>
.father {
width: 400px;
height: 400px;
background-color: red;
}
.son {
width: 200px;
height: 200px;
background-color: black;
}
</style>
<div class="father">
<div class="son"></div>
</div>
<script>
const father = document.querySelector(".father");
const son = document.querySelector(".son");
//依次添加事件
document.addEventListener("click", function () {
alert("爷爷");
});
father.addEventListener("click", function () {
alert("爸爸");
});
// 注释掉子元素的事件绑定
// son.addEventListener("click", function () {
// alert("儿子");
// });
</script>
在上面的代码中我们注释掉了子元素的事件绑定,但是当我们点击子元素时,一样会有 click 事件的发生,只不过子元素没有监听该事件,因此子元素不做任何反应;而根据事件冒泡的机制,其各级父元素由于注册了 click 的同类型事件,因此会被逐层触发,优点就是少进行了一次事件注册:


此时我们如果想对点击的对象做操作的话,可以借助事件对象来获取所点击的对象。
typescript
<style>
.father {
width: 400px;
height: 400px;
background-color: red;
}
.son {
width: 200px;
height: 200px;
background-color: black;
}
</style>
<div class="father">
<div class="son"></div>
</div>
<script>
const father = document.querySelector(".father");
const son = document.querySelector(".son");
//添加事件
father.addEventListener("click", function (event) {
// alert("爸爸");
console.dir(event.target); //事件对象中 target 就是我们所点击的对象,比如子元素
});
// 注释掉子元素的事件绑定
// son.addEventListener("click", function () {
// alert("儿子");
// });
</script>


可以看到在事件对象里面有非常多的API可以供我们使用,上图中的tagName 就是所点击的标签名,通过这个结合一些条件判断我们实际上就可以指定针对哪些标签元素做操作,非常丰富。
补充:阻止元素的默认行为
我们某些情况下需要阻止默认行为的发生,比如阻止链接的跳转,表单域跳转。
语法:
typescript
事件对象.preventDefault();
比如:

其它事件
页面加载事件
加载外部资源(如图片、外联CSS和JavaScript等)加载完毕时触发的事件。
以前我们都是把JavaScript脚本写在HTML标签下面,因为代码是从上往下执行的,我们需要让HTML先被加载然后JavaScript脚本才能去操作HTML标签。
但后面我们就会发现,有人的JavaScript脚本是写在HTML标签上面的。
比如:

但这显然靠目前的技术是会报错的:

没法用,怎么办?
此时就会用到我们这里的页面加载事件,学它就是因为有些时候我们会需要等待某些页面资源全部处理完了才做一些事情。
有几种基本的使用方法:
- 1、监听页面窗口所有资源加载完毕:给 window 添加 load 事件。
事件名:load。
typescript
// 等待页面所有资源加载完毕,就回去执行回调函数
window.addEventListener('load',function(){
//执行的操作
})
注意:不光可以监听整个页面资源加载完毕,也可以针对某个资源绑定load事件。
比如:
typescript
img.addEventListener('load', function(){
// 等待图片加载完毕,再去执行里面的代码
})
- 2、当初始的HTML文档被完全加载和解析完成之后,DOMContentLoaded事件被触发,而无需等待样式表、图像等完全加载。
事件名:DOMContentLoaded。
监听页面DOM加载完毕:给 document 添加DOMContentLoaded事件。
比如:
typescript
document.addEventListener('DOMContentLoaded', function(){
// 执行的操作
})
页面滚动事件
滚动条在滚动的时候持续触发的事件。
为什么要学?因为很多网页需要检测用户把页面滚动到某个区域后做一些处理,比如固定导航栏,比如返回顶部。
事件名:scroll。
监听整个页面滚动:
typescript
// 页面滚动事件
window.addEventListener('scroll', function(){
// 执行的操作
})
注意:这个给 window 加或者 document 都是可以添加 scroll 事件的。
监听某个元素的内部滚动直接给某个元素加即可。
那我们怎么知道页面滚动了多少像素呢?
可以通过HTML标签的两个属性:scrollLeft 和 scrollTop。
获取被卷去的大小,也就是获取元素内容往左、往上滚出去看不到的距离。
这两个值是可读写的。

比如:

这里还有一点要注意,就是对于整个页面来说,最大的标签元素不是 body,而是 html。
而 html 是不能够直接:document.html 的,这个会获取一个 undefined 值。
其正确引用的写法是:document.documentElement。

因此我们可以尝试给整个页面窗口做页面滚动事件的监听,看被卷去了多少像素:

所以我们可以做一些效果,比如当被卷去 100 像素时展示一个 div,不足时就隐藏:

关于滚动事件再注意两个小点:

页面尺寸事件
会在窗口尺寸改变的时候触发事件。
事件名:resize。
比如:
typescript
window.addEventListener('resize', function(){
// 执行的代码
})
检测屏幕宽度可以用到一个HTML的属性:clientWidth。
typescript
window.addEventListener('resize', function(){
let w = document.documentElement.clientWidth
console.log(w)
})
它可以获取宽高:
- 获取元素的可见部分宽高(不包含边框,margin,滚动条等);
- clientWidth 和 clientHeight。

从上图可以看到,padding 是包含在 clientWidth 和 clientHeight 的可见部分宽高里面的嗷。
元素尺寸与位置
前面案例滚动多少距离,都是我们自己算的,最好是页面滚动到某个元素,就可以做某些事情。
简单说就是通过 JavaScript 的方式,得到元素在页面中的位置。
这样我们就可以实现当页面滚动到某个位置时就可以做某些操作而省却计算了。

比如:

补充:各类获取宽度和高度的属性总结

日期对象
用来表示时间的对象。
作用就是可以得到当前的系统时间。
实例化
在代码中用 new 关键字时,一般将这个操作称为 实例化。
比如创建一个时间对象并获取时间。
获取当前时间:
typescript
const date = new Date()
获得指定时间:
typescript
const date = new Date('2008-8-8')
console.log(date)
获得指定具体时间(精确到时分秒):
typescript
const date = new Date('2022-5-1 08:30:00')
console.log(date)
日期对象常用方法
使用场景:因为日期对象返回的数据我们不能直接使用,所以需要转换为实际开发中常用的格式。

时间戳
什么是时间戳:是指 1970年01月01日00时00分00秒起至现在的毫秒数,它是一种特殊的计量时间的方式。
为什么要有时间戳呢?
因为日期不好直接相加减进行计算...
而用时间戳就很好计算:

获取时间戳
主要有三种方式。
1、使用 getTime()方法
typescript
const date = new Date()
console.log(date.getTime())
2、简写 +new Date()
typescript
console.log(+new Date())
3、使用 Date.now()
typescript
console.log(Date.now())
注意:第三种方式虽然不需要实例化,但是只能得到当前的时间戳,而前面两种可以返回指定时间的时间戳。
DOM 节点
什么是DOM节点?
DOM 树里的每一个内容都被称之为 节点。
节点类型:
元素节点:
- 所有的标签,比如 body、div
- HTML 是根节点
属性节点:
- 所有的属性,比如 href
文本节点:
- 所有的文本
重点是元素节点,它可以帮助我们理清标签元素之间的关系。

查找节点
目标:能够具备根据节点关系查找目标节点的能力。
有什么用呢?下面是一种典型的场景:


通过理清节点之间的关系,顺势找到对应的父节点、子节点或者兄弟节点,就可以少写很多查询元素的代码了。
因此我们主要介绍三种最常见的节点关系:父节点、子节点、兄弟节点。
如何查找呢?实际上通过元素的一些属性就可以查找到对应的节点关系。
父节点查找
属性:parentNode。
作用:返回最近一级的父节点,找不到返回为 null。
typescript
子元素.parentNode

子节点查找
属性:childrenNodes;
获取所有子节点,包括文本节点(空格、换行)、注释节点等。
children 属性(重点):
- 仅获取所有元素节点;
- 返回的还是一个伪数组。
typescript
父元素.children
兄弟关系查找
下一个兄弟节点:
typescript
nextElementSibling 属性
上一个兄弟节点:
typescript
previousElementSibling 属性
增加节点(重点)
很多情况下,我们需要在页面中增加元素。
比如点击发布按钮,可以新增一条信息:

对于这种情况我们就会使用到新增节点,主要分为两步:
- 创建一个新节点;
- 把创建的新节点追加到指定的元素内部。
创建节点
即创造出一个新的网页元素,再添加到网页内,一般先创建节点,然后插入节点。
创建元素节点方法:
typescript
// 创建一个新的元素节点
document.createElement('标签名')
追加节点
要想在页面上看到,那么还得将该元素给插入到某个父元素中。
插入到父元素的最后一个元素:
typescript
// 插入到这个父元素的最后
父元素.appendChild(要插入的元素)
插入到父元素的某个子元素之前:
typescript
// 插入到某个子元素的前面
父元素.insertBefore(要插入的元素, 在哪个元素前面)
比如:

克隆节点
在特殊情况下,我们新增节点需要复制一个原有的节点,然后把复制的节点放入到指定的元素内部。
语法:
typescript
// 克隆一个已有的元素节点
元素.cloneNode(布尔值)
cloneNode 会克隆出一个跟原标签一样的元素,括号内传入布尔值:
- 若为true,则代表克隆时会包含后代节点一起克隆。
- 若为false,则代表克隆时不包含后代节点。
- 默认为 false,但基本上我们都会设置为true,这表示这个标签里面的所有东西我们都要拿到。


删除节点

移动端事件

补充:JavaScript插件
有时候自己手搓比如轮播图比较麻烦,所以在日常开发中我们可能会直接去找现成的插件来完成比如轮播图的效果。
别人写好了我们直接CV过来用即可,下面这个就是一个比较常见的插件:Swiper:

BOM 操作浏览器
window 对象
学习 window 对象的常见属性,知道各个 BOM 对象的含义。
BOM 浏览器对象模型
BOM(Browser Object Model)是浏览器对象模型。


定时器-延时函数
JavaScript 内置的一个用来让代码延迟执行的函数,叫 setTimeout()。
语法:
typescript
setTimeout(回调函数, 等待的毫秒数)
setTimeout() 仅仅只执行一次,所以可以理解为就是把一段代码延迟执行,平时省略 window。
清除延时函数:
typescript
let timer = setTimeout(回调函数, 等待的毫秒数)
clearTimeout(timer)
注意:
1、延时器需要等待,所以后面的代码先执行。
2、每一次调用延时器都会产生一个新的延时器。
JS 执行机制
JavaScript 代码是从上往下执行的。
JavaScript 代码会被 JavaScript 解释器解释。
浏览器有两个引擎:
- 渲染引擎(渲染 HTML 和 CSS)
- V8引擎(谷歌浏览器里的引擎,专门用来解析 JS)
比如来看一道经典面试题:

两个结果都是 1111、3333、2222。
为什么呢?这涉及到 JavaScript 语言的执行机制。
JavaScript 语言的一大特点就是单线程,也就是说同一时间只能做同一件事。
这是由于JavaScript这门脚本语言诞生的使命所致------JavaScript是为处理页面中用户的交互以及操作DOM而诞生的。比如我们对某个DOM元素进行添加和删除操作,不能同时进行。应该先进行添加,之后再删除。
单线程就意味着所有的任务都需要排队,前一个任务结束,才会执行后一个任务。
这样所导致的问题是:如果 JS 执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。
回顾上图的面试题,假如一个延时函数要延迟10分钟才执行,那单线程的情况下这个页面不就卡死了吗?显然这样的情况是无法接受的。
因此为了解决这个问题,利用多核 CPU 的能力,HTML5 提出 Web Worker 标准,允许 JavaScript 脚本创建多个线程,于是JavaScript中出现了 同步 和 异步。

本质区别就是这条流水线上的各个流程的执行顺序不同。
其运行机制如下:

那么执行过程是怎么样的呢?
以上图中例子为例,首先第一步会先执行执行栈中的同步任务:

然后会将异步任务给放入任务队列当中:

当执行栈中的所有同步任务执行完毕,系统就会按次序依次读取任务队列中的异步任务,于是被读取的异步任务结束等待状态,进入执行栈开始执行:

由于主线程不断的重复获得任务、执行任务、再获取任务、再执行,所以这种机制被称为事件循环(Event Loop)。
流程图如下:

注意:上图右侧的提到"会提交给对应的异步进程处理,然后看哪个异步任务执行完毕了就会被推入到任务队列中等待执行",这里的异步进程说的是谁呢?当然是浏览器,虽然JS是单线程的,但是浏览器是多线程的,所以是浏览器调用线程来处理的这个过程。
location 对象
location 的数据类型是对象,它拆分并保存了 URL 地址的各个组成部分。
- 1、常用属性和方法:href;
href 属性可以获取完整的 url 地址,对其赋值时可用于地址的跳转。
typescript
// 可以得到当前文件URL的地址
console.log(location.href)
// 可以通过 JS 的方式跳转到目标地址
location.href = 'https://www.xxx.com'
- 2、常用属性和方法:search;
search 属性获取地址中携带的参数,符号 ? 后面的部分。
typescript
console.log(location.search)
比如:

- 3、常用属性和方法:hash;
hash 属性获取地址中的哈希值,符号 # 后面部分。
typescript
console.log(location.hash)

hash 这个算是后期学框架的时候的铺垫,经常用于不刷新页面但却可以显示不同页面的操作。
- 4、常用属性和方法:reload();
reload() 方法用来刷新当前页面,传入参数 true 时表示强制刷新。
typescript
// 强制刷新,类似于 ctrl + F5
location.reload(true)
navigator 对象
navigator 的数据类型是对象,该对象记录了浏览器自身的相关信息。

history 对象
history 的数据类型是对象,主要管理历史记录,该对象与浏览器地址栏的操作相对应,如前进、后退、历史记录等。

也就是左上角的内容:

本地存储
本地存储介绍

本地存储分类
localStorage

存储数据的语法:
typescript
localStorage.setItem(key, value)
获取数据的语法:
typescript
localStorage.getItem(key)
删除数据的语法:
typescript
localStorage.removeItem(key)
修改数据的语法:
typescript
// 再 set 一遍,然后把 value 换成新值即可
localStorage.setItem(key, value)

注意:本地存储只能存储字符串的数据类型。
sessionStorage

用法就把前文讲的 localStorage 给改成 sessionStorage 就可以了,API 长得一模一样,但是这个 sessionStorage 用的比较少嗷。
存储复杂数据类型




补充:JS 数组的 map() 和 join() 方法
这两个数组的方法主要是用来实现高效的字符串拼接操作的,是开发中的常用写法。


正则表达式


语法如下:



这个有用到的时候直接 AI 一下 CV 就可以了,不需要学习怎么写,知道怎么用即可。
至此 JavaScript 的 Web APIs 部分相关的内容就全部结束了!