Web Api
# Web APIs - 第3天
> 目标:学习事件流,事件委托,其他事件等知识,优化多个事件绑定和实现常见网页交互
- 事件流
- 移除事件监听
- 其他事件
- 元素尺寸与位置
- 综合案例
![image.png](https://cos.easydoc.net/84678576/files/luakki5o.png)
## 事件流
**为什么要学习事件流?**
- 可以帮我们解决一些疑惑,比如点击子盒子会会弹出2次的问题
事件流指的是事件完整执行过程中的`流动路径`
当触发事件时,会经历两个阶段,分别是`捕获阶段`、`冒泡阶段`
![image.png](https://cos.easydoc.net/84678576/files/luakl8mh.png)
事件捕获概念:
当一个元素的事件被触发时,会从DOM的根元素开始依次调用同名事件 (从外到里)
### 捕获
事件捕获需要写对应代码才能看到效果
**语法:**
```html
元素.addEventListener('click', 回调函数, 是否使用捕获)
```
```html
<body>
<div class="father">
父盒子
<div class="son">子盒子</div>
</div>
<script>
// 事件流
const father = document.querySelector('.father')
const son = document.querySelector('.son')
// 1. 事件捕获
// // 点击父盒子
father.addEventListener('click', function () {
alert('我是爸爸')
}, true) // 事件捕获
// 点击子盒子
son.addEventListener('click', function () {
alert('我是儿子')
}, true) // 事件捕获
</script>
</body>
```
>说明:
>
>- addEventListener第三个参数传入 true 代表是捕获阶段触发(很少使用)
>- 若传入false代表冒泡阶段触发,默认就是 false
###事件冒泡
事件冒泡概念:
当一个元素的事件被触发时,同样的事件将会在该元素的所有祖先元素中依次被触发。这一过程被称为事件冒泡
- 简单理解:当一个元素触发事件后,会依次向上调用所有父级元素的 `同名事件`
- 事件冒泡是`默认`存在的,或者第三个参数传入 `false` 都是冒泡
- `实际工作都是使用事件冒泡为主`
~~~html
<body>
<div class="father">
父盒子
<div class="son">子盒子</div>
</div>
<script>
// 事件流
const father = document.querySelector('.father')
const son = document.querySelector('.son')
// 2. 事件冒泡
// 点击父盒子
father.addEventListener('click', function () {
alert('我是爸爸')
})
// 点击子盒子
son.addEventListener('click', function () {
alert('我是儿子')
}, false)
</script>
</body>
~~~
### 阻止冒泡
**问题:**因为默认就有冒泡阶段的存在,所以容易导致事件影响到父级元素(祖先元素)
**需求:**若想把事件就限制在当前元素内,就需要阻止事件冒泡
**前提:**
阻止事件冒泡需要拿到`事件对象`
```html
<body>
<div class="father">
父盒子
<div class="son">子盒子</div>
</div>
<script>
// 事件流
const father = document.querySelector('.father')
const son = document.querySelector('.son')
// 1. 事件冒泡
// 点击父盒子
father.addEventListener('click', function () {
alert('我是爸爸')
})
// 点击子盒子
son.addEventListener('click', function (e) {
alert('我是儿子')
// 1.1 先获取事件对象
// 1.2 事件对象.stopPropagation() 注意是个方法
e.stopPropagation()
})
</script>
</body>
```
结论:事件对象中的 `ev.stopPropagation` 方法,专门用来阻止事件冒泡(事件传播)
>鼠标经过事件:
>
>mouseover 和 mouseout 会有冒泡效果
>
>mouseenter 和 mouseleave 没有冒泡效果 (推荐)
### 事件委托
**事件委托(EventDelegation)**:是JavaScript中注册事件的常用技巧,也称为事件委派、事件代理
简单理解:原本需要注册在子元素的事件委托给父元素,让父元素担当事件监听的职务
**为什么要用事件委托呢?**
- 如果同时给多个元素注册事件,还需要利用循环多次注册事件
- 大量的事件监听是比较耗费性能的,如下代码所示
~~~html
<script>
// 假设页面中有 10000 个 button 元素
const buttons = document.querySelectorAll('table button');
for(let i = 0; i <= buttons.length; i++) {
// 为 10000 个 button 元素添加了事件
buttons.addEventListener('click', function () {
// 省略具体执行逻辑...
})
}
</script>
~~~
事件委托是利用事件流的特征解决一些开发需求的知识技巧
- 优点:减少注册次数,可以提高程序性能
- 原理:事件委托其实是利用事件冒泡的特点
- 给父元素注册事件,当我们触发子元素的时候,会冒泡到父元素身上,从而触发父元素的事件
利用事件委托方式如何得到当前点击的元素呢?
- 实现:事件对象.target. tagName 可以获得真正触发事件的元素
```html
<body>
<ul>
<li>第1个孩子</li>
<li>第2个孩子</li>
<li>第3个孩子</li>
<li>第4个孩子</li>
<li>第5个孩子</li>
</ul>
<script>
// 需求: 点击每个小li都会有弹窗效果
// 1. 获取父元素ul
const ul = document.querySelector('ul')
// 2. 给ul注册点击事件
ul.addEventListener('click', function (e) {
// alert('我会弹窗')
// 3. 利用事件对象.target 得到目标元素
// console.log(e.target)
// e.target.style.color = 'pink'
// 需求2:点击哪个小li,对应的li变色
// console.dir(e.target.tagName) 可以得到目标元素的标签名
if (e.target.tagName === 'LI') {
e.target.style.color = 'pink'
}
})
</script>
```
### 阻止默认行为
阻止元素发生默认的行为
例如:
- 当点击提交按钮时阻止对表单的提交
- 阻止链接的跳转等等
**语法:**
~~~javascript
事件对象.preventDefault()
~~~
~~~html
<body>
<form action="">
姓名: <input type="text" name="username">
<button>提交</button>
</form>
<a href="http://www.baidu.com">点击跳转</a>
<script>
// 阻止默认行为
const form = document.querySelector('form')
const input = document.querySelector('[name=username]')
form.addEventListener('submit', function (e) {
// 如果input表单的值为空则不允许提交
if (input.value === '') {
// return 无法阻止提交事件
e.preventDefault() // 阻止提交事件
}
})
document.querySelector('a').addEventListener('click', function (e) {
e.preventDefault()
})
</script>
</body>
~~~
## 事件解绑(了解)
移除事件处理函数,也称为解绑事件
~~~html
<body>
<button class="l2">移除L2事件监听</button>
<button class="l0">移除L0事件监听</button>
<script>
// 需求:按钮就点击一次,然后移除点击事件
// 1. l2事件监听
const l2 = document.querySelector('.l2')
l2.addEventListener('click', fn)
function fn() {
alert('我点击了')
// 移除事件监听
l2.removeEventListener('click', fn)
}
// 2. l0事件监听
const l0 = document.querySelector('.l0')
l0.onclick = function () {
alert('我点击了')
// 移除事件监听
l0.onclick = null
}
</script>
</body>
~~~
## 其他事件
### 页面加载事件
加载外部资源(如图片、外联CSS和JavaScript等)加载完毕时触发的事件
为什么要学?
- 有些时候需要等页面资源全部处理完了做一些事情
- 老代码喜欢把 script 写在 head 中,这时候直接找 dom 元素找不到
**事件名:load**
监听页面所有资源加载完毕:
~~~javascript
window.addEventListener('load', function() {
// xxxxx
})
~~~
当初始的 HTML 文档被完全加载和解析完成之后就触发,而无需等待样式表、图像等完全加载
**事件名:DOMContentLoaded**
~~~javascript
document.addEventListener('DOMContentLoaded', function() {
// xxxxx
})
~~~
### 元素滚动事件
滚动条在滚动的时候持续触发的事件
为什么要学?
- 很多网页需要检测用户把页面滚动到某个区域后做一些处理,比如固定导航栏,比如返回顶部
**事件名:scroll**
监听整个页面滚动:
~~~javascript
// 获取DOM,并且获取窗口滚动条位置
let dom = document.documentElement
window.addEventListener('scroll', function() {
console.log(dom.scrollTop)
})
~~~
>scrollTop / scrollLeft, 被卷去的头部或者左侧,可以读取,也可以修改(赋值)
### 页面尺寸事件
会在窗口尺寸改变的时候触发事件:
~~~javascript
let nowLi = document.querySelector("div")
window.addEventListener('resize', function() {
console.log(nowLi.offsetWidth)
})
~~~
>clientWidth和clientHeight,获取元素的可见部分宽高(不包含border,margin,滚动条等)
## 元素尺寸与位置
获取元素的自身宽高、包含元素自身设置的宽高、padding、border
![image.png](https://cos.easydoc.net/84678576/files/luaklon7.png)
![image.png](https://cos.easydoc.net/84678576/files/luaklttr.png)
# Web APIs - 第4天笔记
> 目标: 了解DOM节点的增删改查,掌握利用数据操作页面,完成移动端通讯录案例
- 日期对象
- 节点操作
- M端事件
- JS插件
- 综合案例
## 日期对象
日期对象:用来表示日期和时间的对象
作用:可以得到当前系统日期和时间
Date是JavaScript内置对象
日期对象使用必须先`实例化`:创建一个日期对象并获取时间
在代码中发现了 `new` 关键字时,一般将这个操作称为实例化
### 实例化
~~~javascript
<body>
<script>
// 1. 实例化日期对象
// 1.1 得到当前的日期和时间
const date = new Date()
console.log(date)
// 1.2 得到指定的日期和时间
const date1 = new Date('2099-12-12 08:08:08')
console.log(date1)
</script>
</body>
~~~
### 格式化日期对象
| 方法 | 作用 | 说明 |
| ------------- | ------------------ | -------------------- |
| getFullYear() | 获得年份 | 获取四位年份 |
| getMonth() | 获得月份 | 取值为 0 ~ 11 |
| getDate() | 获取月份中的每一天 | 不同月份取值也不相同 |
| getDay() | 获取星期 | 取值为 0 ~ 6 |
| getHours() | 获取小时 | 取值为 0 ~ 23 |
| getMinutes() | 获取分钟 | 取值为 0 ~ 59 |
| getSeconds() | 获取秒 | 取值为 0 ~ 59 |
~~~javascript
<body>
<script>
// 2. 格式化日期对象-转换成开发中常见的日期和时间格式
const myDate = new Date()
console.log(myDate.getFullYear()) // 得到年份 数字型的 2023
console.log(myDate.getMonth() + 1) // 得到月份 数字型的 0 需要加1
console.log(myDate.getDate()) // 得到几号 数字型的 27
console.log(myDate.getDay()) // 得到星期 数字型 5
console.log(myDate.getHours()) // 得到小时
console.log(myDate.getMinutes()) // 得到分钟
console.log(myDate.getSeconds()) // 得到秒数
console.log(`现在的日期是:${myDate.getFullYear()}年${myDate.getMonth() + 1}月${myDate.getDate()}日`)
</script>
</body>
~~~
### 格式化日期对象另外方法
| 方法 | 作用 | 说明 |
| -------------------- | ---------------------------------------- | ------------------ |
| toLocaleString() | 返回该日期对象的字符串(包含日期和时间) | 2099/9/20 18:30:43 |
| toLocaleDateString() | 返回日期对象日期部分的字符串 | 2099/9/20 |
| toLocaleTimeString() | 返回日期对象时间部分的字符串 | 18:30:43 |
~~~html
<body>
<div class="box"></div>
<script>
const date = new Date()
console.log(date.toLocaleString()) // 2023/1/27 23:19:20
console.log(date.toLocaleDateString()) // 2023/1/27
console.log(date.toLocaleTimeString()) // 23:19:20
// 1. 封装一个 getDateTime函数,里面格式化时间对象
function getDateTime() {
const date = new Date()
return date.toLocaleString()
}
// 注意: 先写上这句话,防止盒子有1秒的空白期
document.querySelector('.box').innerText = getDateTime()
// 3. 显示到box盒子里面,并且添加定时器
setInterval(function () {
document.querySelector('.box').innerText = getDateTime()
}, 1000)
</script>
</body>
~~~
### 时间戳
什么是时间戳:
- 是指1970年01月01日00时00分00秒起至现在的总毫秒数(数字型),它是一种特殊的计量时间的方式
**使用场景:** 计算倒计时效果,需要借助于时间戳完成
算法:
- 将来的时间戳 - 现在的时间戳 = 剩余时间毫秒数
- 剩余时间毫秒数转换为年月日时分秒就是倒计时时间
~~~javascript
// 1. 实例化
const date = new Date()
// 2. 获取时间戳
console.log(date.getTime())
// 还有一种获取时间戳的方法
console.log(+new Date())
// 还有一种获取时间戳的方法
console.log(Date.now())
~~~
获取时间戳的方法,分别为 getTime 和 Date.now 和 +new Date()
## DOM 节点
**DOM树:**DOM 将 HTML文档以树状结构直观的表现出来,我们称之为 DOM 树 或者 节点树
**节点(Node)**是DOM树(节点树)中的单个点。包括文档本身、元素、文本以及注释都属于是节点。
- `元素节点`(重点)
- 所有的标签 比如 body、 div
- html 是根节点
- 属性节点
- 所有的属性 比如 href
- 文本节点
- 所有的文本
![image.png](https://cos.easydoc.net/84678576/files/luakvzpq.png)
### 查找节点
利用节点关系查找节点,返回的都是对象
- 父节点
- 子节点
- 兄弟节点
有了查找节点可以使我们选择元素更加方便
#### 父节点
语法:
~~~JavaScript
元素.parentNode
~~~
~~~html
<body>
<div class="pop">
<a href="javascript:;" class="close"></a>
</div>
<script>
// 点击关闭按钮可以关闭父盒子
const closeBtn = document.querySelector('.close')
// 利用孩子选取父节点,返回的也是一个元素对象
console.log(closeBtn.parentNode)
closeBtn.addEventListener('click', function () {
this.parentNode.style.display = 'none'
})
</script>
</body>
~~~
#### 子节点
**语法:**
~~~javascript
父元素.children
~~~
~~~html
<body>
<ul>
<li>我是第1个孩子</li>
<li>我是第2个孩子</li>
<li>我是第3个孩子</li>
<li>我是第4个孩子</li>
</ul>
<script>
// 1. 查询子节点
const ul = document.querySelector('ul')
console.log(ul.children)
// 2. 查询兄弟节点
const li2 = document.querySelector('ul li:nth-child(2)')
// console.log(li2)
console.log(li2.previousElementSibling) // 上一个兄弟
console.log(li2.nextElementSibling) // 下一个兄弟
console.log(ul.children[0]) // 第一个孩子
console.log(ul.children[2]) // 第三个孩子
</script>
</body>
~~~
#### 兄弟节点
~~~html
<body>
<ul>
<li>我是第1个孩子</li>
<li>我是第2个孩子</li>
<li>我是第3个孩子</li>
<li>我是第4个孩子</li>
</ul>
<script>
// 2. 查询兄弟节点
const li2 = document.querySelector('ul li:nth-child(2)')
// console.log(li2)
console.log(li2.previousElementSibling) // 上一个兄弟
console.log(li2.nextElementSibling) // 下一个兄弟
console.log(ul.children[0]) // 第一个孩子
console.log(ul.children[2]) // 第三个孩子
</script>
</body>
~~~
### 增加节点
很多情况下,我们需要在页面中增加元素
- 比如,点击发布按钮,可以新增一条信息
一般情况下,我们新增节点,按照如下操作:
- 创建一个新的节点
- 把创建的新的节点放入到指定的元素内部
1. 父元素最后一个子节点之后,插入节点元素
~~~javascript
element.append()
~~~
2. 父元素第一个子元素的之前,插入节点元素
~~~javascript
element.prepend()
~~~
如下代码演示:
```html
<body>
<ul>
<li>我是小li</li>
</ul>
<script>
// 1. 创建节点
const li = document.createElement('li')
li.innerHTML = '我是放到后面的'
console.log(li)
// 2. 追加给父元素
const ul = document.querySelector('ul')
// 2.1 append 放到ul 的最后面 类似css的 after伪元素
ul.append(li)
// 2.2 prepend放到 ul 的最前面 类似css的 before伪元素
const firstli = document.createElement('li')
firstli.innerHTML = '我是放到前面的'
ul.prepend(firstli)
</script>
</body>
```
### 删除节点
若一个节点在页面中已不需要时,可以删除它
**语法:**
~~~JavaScript
element.remove()
~~~
>1. 把对象从它所属的 DOM 树中删除
>2. 删除节点和隐藏节点(display:none) 有区别的: 隐藏节点还是存在的,但是删除,则从DOM树中删除
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>删除节点</title>
</head>
<body>
<div class="remove">我要删除</div>
<div class="none">我要隐藏</div>
<script>
// 1. 删除节点, remove 会从dom树中删除这个元素
const remove = document.querySelector('.remove')
remove.remove()
// 2. display:none 隐藏元素,页面看不见,但是dom树中还存在这个标签
const none = document.querySelector('.none')
none.style.display = 'none'
</script>
</body>
</html>
```
## M端事件
M端(移动端)有自己独特的地方。比如`触屏事件 touch`(也称触摸事件),Android 和 IOS都有。
touch 对象代表一个触摸点。触摸点可能是一根手指,也可能是一根触摸笔。触屏事件可响应用户手指(或触控笔)对屏幕或者触控板操作。
常见的触屏事件如下:
| **触屏touch事件** | 说明 |
| -------- | ------------ |
| touchstart | 手指触摸到一个 DOM 元素时触发 |
| touchmove | 手指在一个 DOM 元素上滑动时触发 |
| touchend | 手指从一个 DOM 元素上移开时触发 |
~~~javascript
<body>
<div class="box"></div>
<script>
// 触摸事件
const box = document.querySelector('.box')
// 1. 手指触屏开始事件 touchstart
box.addEventListener('touchstart', function () {
console.log('我开始摸了')
})
// 2. 手指触屏滑动事件 touchmove
box.addEventListener('touchmove', function () {
console.log('我一直摸')
})
// 3. 手指触屏结束事件 touchend
box.addEventListener('touchend', function () {
console.log('我摸完了')
})
</script>
</body>
~~~
## JS插件
插件: 就是别人写好的一些代码,我们只需要复制对应的代码,就可以直接实现对应的效果
### Swiper
学习插件的思路:
1.看官网。了解这个插件可以完成什么需求 https://www.swiper.com.cn/
2.查看基本使用流程 。 https://www.swiper.com.cn/usage/index.html
3.写个小demo。看在线演示,找到符合自己需求的demo https://www.swiper.com.cn/demo/index.html
4.应用的开发中。
### AlloyFinger
AlloyFinger 是腾讯 AlloyTeam 团队开源的超轻量级 Web 手势插件,为元素注册各种手势事件
github地址:<https://github.com/AlloyTeam/AlloyFinger>
使用步骤:
1. 下载js库:<http://alloyteam.github.io/AlloyFinger/alloy_finger.js>
2. 将AlloyFinger库引入当前文件:<scriptsrc="alloy_finger.js"></script>
或者使用在线地址:<script src="<https://unpkg.com/alloyfinger@0.1.16/alloy_finger.js>"></script>
3. 配置
~~~javascript
new AlloyFinger(element, { // element 是给哪个元素做滑动事件
swipe: function (e) {
// 滑动的时候要做的事情 e.direction 可以判断上下左右滑动 Left Right 等
}
})
~~~
## 综合案例
### 遍历数组forEach
~~~JavaScript
arr.forEach(function (element, index) { /* … */ })
~~~
- element是数组元素
- index是数组元素的索引号
### 字符串截取
~~~JavaScript
字符串.substring(起始索引号, [结束索引号])
~~~
### 思想转变(相当重要)
本次案例,我们尽量减少dom操作,采取`操作数据`的形式,为了后期Vue做铺垫
增加和删除都是针对于`数组的操作`,然后根据`数组数据渲染页面`(数据驱动视图)
**事件委托的两个重要作用:**
1. 减少了注册次数
2. 给新增元素注册事件