这是我参与「第五届青训营 」伴学笔记创作活动的第 6 天
3.2 事件基础
事件
定义:编程时系统内发生的动作或者发生的事情,比如用户在网页上单击一个按钮(鼠标点击/经过/离开/拖拽,键盘回车)
事件监听(绑定事件/注册事件):让程序检测是否有事件发生,一旦有事件触发,就立即调用一个函数做出响应
DOM L0 事件源.on事件类型 = function(){}
元素.addEventListener('事件', 要执行的函数)
-
事件监听三要素
- 事件源:dom元素:文本框,button,
<div>标签 - 事件:用什么方式触发:鼠标单击click, 鼠标经过mouseover
- 事件调用的函数
- 事件源:dom元素:文本框,button,
//1. 获取元素
let btn = document.querySelector('button')
btn.addEventListnener('click', function() {
alert('被点击了')
})
例子:点击后关闭
<body>
<div class="qrCode">
<img src="" alt>
<i class="close_btn"x</i>
</div>
<script>
//1. 获取元素 事件源i 关闭qrCode
let closeBtn = document.querySelector('.close_btn')
let qrCode = closeBtn = document.querySelector('.qrCode')
//2. 事件监听
closeBtn.addEventListner('click', function() {
qrCode.sytle.display = 'none'
})
</script>
</body>
事件源:btn
关闭:css修改display = 'none'
- 事件类型
小米搜索框:光标
.result-list {
display: none;//一开始隐藏,得到光标后显示
}
.mi .search {
border: 1px solid orange;
}
<body>
<div class="mi">
<input type="search" placeholder="笔记本">
<ul class="result-list">
<li><a href="#">全部商品</a></li>
<li><a href="#">小米11</a></li>
<li><a href="#">小米10</a></li>
<li><a href="#">空调</a></li>
</ul>
</div>
<script>
let search = document.querySelector('input[type=search]')
let list = document.querySelector('.result-list')
search.addEventLisner('focus', function() {
// 'focus' -> 'mouseenter'鼠标经过
// 显示下拉菜单
list.style.display = 'block'
//文本框边框变色
search.classList.add('search')
})
// 失去焦点
search.addEventLisner('blur', function() {
// 'blur' -> mouseleave 鼠标离开
// 隐藏下拉菜单
list.style.display = 'none'
//文本框边框去色
search.classList.remove('search')
})
</script>
</body>
全选框例子
<table>
<tr>
<th class="checkAll">
<input type="checkbox" name="" 全选>
</th>
<th>表头...</th>
</tr>
<tr>
<td>
<input type="checkbox" name="check" class="ck">
</td>
</tr>
</table>
<script>
// 1. 获取元素 全选和小复选框
let all = document.querySelector('#checkAll')
let cks = document.querySelectorAll('.ck')
// 2. 事件监听 全选按钮 获得状态
all.addEventListener('click', function() {
for (let i = 0; i < cks.length; i++) {
cks[i].checked = all.checked;//this.checked this指向all
}
})
</script>
绑定多个事件--循环
只有所有单选框都全选,全选框勾选上
<script>
for (let i = 0; i < cks.length; i++) {
cks[i].addEventListener('click', function() {
//每次点击,都有遍历所有的小按钮
for (let j = 0; j < cks.length; j++) {
if (cks[j].checked === false) {
all.checked = false
return;
}
}
all.checked = true;
})
}
</script>
购物车加减
<body>
<div>
// 都是字符串型,加减要类型转换
<input type="text" id="total" value="1" readonly>
<input type="button" value="+" id="add">
<input type="button" value="-" id="reduce" disabled>//默认禁用状态
<script>
let total = document.querySelector('#total')
let add = document.querySelector('#add')
let reduce = document.querySelector('#reduce')
add.addEventListener('click', function() {
total.value++;//有隐式转换
reduce.disabled = false
})
reduce.addEventListener('click', function() {
total.value--
if (total.value <= 1) { //比较运算符也有隐式转换
reduce.disabled = true
}
})
</script>
</div>
</body>
高阶函数
函数表达式:把函数(非调用,而是定义)赋值给变量
let fn = function() {...}
btn.onclick = function(){}
回调函数:将函数A作为参数传递给函数B,函数A成为回调函数
function fn() {}
setInterval(fn, 1000)
// fn为回调函数,在1000ms后才调用
环境对象
什么是环境对象:函数内部特殊的变量this,代表着当前函数运行时所处的环境
function fn() {
console.log(this) // window
}
fn() // 相当于window.fn()
let btn = document.querySelector('button')
btn.addEventListener('click', function() {
console.log(typeof this) // this指向btn
})
谁调用,this就是谁
编程思想
tab栏切换:点到哪个按钮亮显哪个
<body>
<button class="pink">第1个</button> * 5
<script>
let btns = document.querySelectorAll('button')
for (let i = 0; i < btns.length; i++) {
btns[i].addEventListener('click', function() {
// solution 1
for (let j = 0; j < btns.length; j++) {
btns[j].classList.remove('pink')
}
// solution 2, better
document.querySelector('.pink').classList.remove('pink')//要有初始的pink style
this.classList.add('pink')
})
}
</script>
</body>
todo
综合案例
<script>
let tabs = document.querySelectorAll('.tab-item');
let products = document.querySelectorAll('.products .main');
console.log(products.length)
for (let i = 0; i < tabs.length; i++) {
tabs[i].addEventListener('click', function() {
document.querySelector('.tab .active').classList.remove('active')
document.querySelector('.products .active').classList.remove('active')
tabs[i].classList.add('active')
products[i].classList.add('active')
})
}
</script>
3.3 节点操作
节点操作:增删改查
DOM节点是什么:DOM树里每一个内容
节点分类:元素节点 div、属性节点 class属性、文本节点
查找父节点:子元素.parentNode 返回最近一级的父节点,找不到返回null
例子:关闭二维码,获得子节点"close_btn",关闭父节点erweima:
<div class="erweima">
<img src-"" alt>
<i class="close_btn">x</i>
</div>
<script>
let son = document.querySelector('.close_btn')
son.parentNode.style.display = 'none'
</script>
查找子节点
childNodes获得所有子节点children仅获得所有元素节点,返回一个伪数组父元素.children
<body>
<butto>点击</butto>
<ul>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
</ul>
</body>
<script>
let btn = document.querySelector('button')
let ul = document.querySelector('ul')
btn.addEventListener('click' function() {
for (let i = 0; i < ul.children.length; i++) {
ul.children[i].style.color="red"
}
})
</script>
查找兄弟节点 元素.nextElementSibling 下一个兄弟元素
元素.previousElementSibling 上一个兄弟元素
增加节点
-
Step1:创建一个新的节点
document.createElement('标签名')- 写内容
元素.innerHTML = "some content/div标签"
- 写内容
-
Step2:把新节点放入到指定的元素内部
- 插入到父元素的最后一个子元素
父元素.appendChild(要插入的元素)document.body.appendChild(newElement) - 插入到父元素中某个子元素的前面
父元素.insertBefore(要插入的元素,在哪个元素前面)新元素永远放在第一个ul.insertBefore(newLi, ul.children[0])
- 插入到父元素的最后一个子元素
克隆节点:克隆出一个跟原标签一样的元素
(区别于克隆对象 let cloneObj = Object.assign(new Date(), oldDate);
元素.cloneNode(布尔值)
- true: 包含后代节点一起克隆
- false:克隆时不包含后代节点,默认
<body>
<ul>
<li>11111</li>
</ul>
</body>
<script>
let ul = document.querySelector('ul')
let newUl = ul.cloneNode(true) //
document.body.appendChild(newUl)
</script>
删除节点
JS的原生DOM操作中,要删除元素必须通过父元素删除
父元素.removeChild(要删除的元素)
与隐藏节点display:none有区别,隐藏节点还是存在的
数组里删除元素,区别
<body>
<ul>
<li>11111</li>
</ul>
</body>
<script>
let btn = document.querySelector('button')
let ul = document.querySelector('ul')
btn.addEventListener('click', function() {
ul.removeChild(ul.children[0])
})
</script>
时间对象
-
实例化
当前时间:let date = new Date()指定时间 let date = new Date('1949-10-01') -
时间对象方法:得到number的年月日时分秒与星期
getFullYear()getMonth()getDate()getDay()getHoursgetMinutes()getSeconds() -
例子:实现一个页面时间数秒更新的功能
let div = ; getTime() // 避免第0-1s会有一个空白 setInterval(getTime, 1000) function getTimie() { // 获取年月日,时分秒;展示 div.innerHTML = `` } -
时间戳:用于倒计时,从1970年...起至现在的毫秒数
获取时间戳的方法
let date = new Date(); date.getTime()console.log(+new Date(''2021-8-30 12:00:00))指定了时间console.log(Date.now())
例子:有一个下班倒计时的例子
综合案例:微博评论发布:点击后增加标签
获取[0, length - 1]
Math.floor(Math.random()*(max+1));
重绘和回流--面试题
类似面试题:节流、防抖
浏览器如何进行渲染
HTML+样式的解析
重点:回流(也叫重排):给盒子layout(布局)
重绘和回流:重绘不一定引起回流,但回流一定会引起重绘
任何一个盒子的尺寸变化了,要回流并且重绘
尺寸不变,只是color, background-color这些属性变化,要重绘
回流的操作:
- 页面的首次刷新
- 尺寸变化(浏览器窗口变化、字体小、元素大小位置、内容变化、DOM增删、激活CSS伪类)
3.4 事件高级
事件对象
这个对象有事件触发时的相关信息,比如鼠标点击对象,存储了鼠标点在哪个位置等信息,
如何获取:事件对象在回调函数里
元素.addEventListener('click', function (e) {
console.log(e)
})
常用属性
补充:pageX和pageY 跟文档坐标有关系
<body>
<button>点击</button>
<script>
let btn = document.querySelector('button')
btn.addEventListener('click', function(e) {
console.log(e);//e是一个鼠标事件对象
})
</script>
</body>
例子:一张图片一直跟着鼠标移动
mousemove事件
不断把鼠标在页面中的坐标位置给图片left和top值
例子:按下回车,自动发布
事件流
事件流与两个阶段
事件流定义:事件完整执行过程中的流动路径
两个阶段:捕获、冒泡;往下查找,找到后往上返回
事件捕获和事件冒泡
- 事件冒泡
例子:向上调用同名事件
- 事件捕获
阻止事件流动(传播)
组织冒泡/捕获,需求:不想自动触发父元素的事件
事件对象.stopPropagation()
son.addEventListener('click', function(e) {
alert('我是儿子')
e.stopPropagation() //加这一句,只会弹“我是儿子”
})
鼠标经过事件,对比mouseover和mouseenter:mouseover鼠标经过则触发,经过 是有冒泡效果的;mouseenter不会往上冒泡,从子到父,就不会触发(推荐使用)
- 阻止默认行为,比如链接点击不跳转,表单域不提交
e.preventDefault()
<a href="www.baidu.com">跳转到百度</a>
<script>
let a = document.querySelector('a')
a.addEventListener('click', function(e) {
e.preventDefault() //加了这一句后,就不跳转了
})
</script>
-
两种注册事件的区别
- L0:传统on注册
- L2:事件监听注册
<body>
<button>点击</button>
<script>
let btn = document.querySelector('button')
// 1.l0 on
// 多次相同的事件,只执行最后一次
btn.onclick = function () {
alert('第一次')
}
btn.onclick = function () {
alert('第二次')
} // 覆盖,只弹第一次
// 解绑事件
btn.onclick = null //不会有弹出
<body>
<button>点击</button>
<script>
let btn = document.querySelector('button')
// 2. addEventListener 会弹两次
btn.addEventListener('click', function() {
alert('第一次')
}
btn.addEventListener('click', function () {
alert('第二次')
})
</script>
</body>
<body>
<button>点击</button>
<script>
let btn = document.querySelector('button')
// 2. addEventListener 会弹两次
btn.addEventListener('click', add)
function add() {
alert('第一次')
}
btn.removeEventListener('click', add) //移除了,不弹了,无法对匿名函数解绑,因此不可以写出btn.addEventListener('click', funciton() {...})
</script>
</body>
事件委托(不是很懂 )
为什么要事件委托
- 减少事件注册
- 新增后代元素的事件绑定,不用每次新增再绑定事件
综合案例——渲染学生信息
与节点操作(微博)不同,数据驱动视图(Vue/React)
传统的jquery开发是通过操作dom来实现页面的渲染和交互,而React采用的是数据驱动视图的方式:view是基于数据来渲染的,数据一旦变化,view就会自动更新。因此我们在开发时,只需要关注数据即可,不用直接操作dom。
在原生DOM里性能低
-
渲染已有数据
-
点击录入:往数组arr里追加
push()方法,重新渲染 -
删除:事件委托,委托给父亲
tbody,那就只用给一个元素注册事件。删除也是数组里面的数据,然后重新渲染只能点击元素a,才会执行删除操作
e.target返回点击的标签,判断标签名是否为ae.target.tagName === a删除操作:删除数组里的数据,
arr.splice(从哪里开始删,删多少个)<a>添加属性,把索引号记下来<a href="" id="",获取ide.target.idsplice(index, number); index:表示从第几个元素开始; number: 表示从此元素开始,向后删除几个元素打印对象
console.dir(e.target)