JavaScript学习第十章-第三部分(dom)
- 事件对象
- 冒泡行为
- 事件委派
- 事件捕获
- bom对象
- 事件循环
1、event事件
事件对象
事件对象是有浏览器在事件触发时所创建的对象,这个对象中封装了事件相关的各种信息
通过事件对象可以获取到事件的详细信息
- 比如:鼠标的坐标、键盘的按键…
浏览器在创建事件对象后,会将事件对象作为响应函数的参数传递,
所以我们可以在事件的回调函数中定义一个形参来接收事件对象
使用示例
当鼠标滑过的时候,将鼠标当前的坐标实时的显示出来
代码
<style>
#box1 {
width: 300px;
height: 300px;
border: 10px green solid;
text-align: center;
line-height: 300px;
}
</style>
</head>
<body>
<div id="box1"></div>
<script>
/*
event 事件
- 事件对象
- 事件对象是有浏览器在事件触发时所创建的对象,
- 通过事件对象可以获取到事件的详细信息
比如:鼠标的坐标、键盘的按键..
- 浏览器在创建事件对象后,会将事件对象作为响应函数的参数传递,
所以我们可以在事件的回调函数中定义一个形参来接收事件对象
*/
const box1 = document.getElementById("box1");
// box1.onmousemove = event => {
// console.log(event)
// }
box1.addEventListener("mousemove",event => {
console.log(event.clientX,event.clientY);
//将当前鼠标的位置信息展示到box1中
box1.textContent = '横坐标:'+event.clientX + ",纵坐标:"+event.clientY;
})
</script>
</body>
2、事件对象行为
在DOM中存在着多种不同类型的事件对象
- 多种事件对象有一个共同的祖先 Event
- event.target 触发事件的对象
- event.currentTarget 绑定事件的对象(同this)
- event.stopPropagation() 停止事件的传导
- event.preventDefault() 取消默认行为
- 事件的冒泡(bubble)
- 事件的冒泡就是指事件的向上传到
- 当元素上的某个事件被触发后,其祖先元素上的相同事件也会同时被触发
- 冒泡的存在大大的简化了代码的编写,但是在一些场景下我们并不希望冒泡存在
- 不希望事件冒泡时,可以通过事件对象来取消冒泡
取消超链接的默认行为
当页面上面有一个a标签,当我们点击的时候会触发它的默认事件跳转,这个时候我们想取消有两种方式;
"chao" href="https://lilichao.com">超链接</a>
方式1
方式1,缺点是不够灵活,如果用户是通过addEventListener方式,那么我们就监听不到了
const chao = document.getElementById("chao");
chao.onclick = function() {
return false;
}
方式2
preventDefault() 取消默认事件
chao.addEventListener("click",function(event) {
// 取消默认行为
event.preventDefault();
})
event 对象
多种事件对象有一个共同的祖先 Event
使用示例
<div id="box1"></div>
const box1 = document.getElementById("box1");
box1.addEventListener("click",function(event) {
/*
在事件的响应函数中:
event.target 表示的是触发事件的对象
this 绑定事件的对象
*/
console.log("target",event.target);//<div id="box3"></div >
console.log("this",this);// box1
// currentTarget
console.log("currentTarget:",event.currentTarget);// box1
alert("我是box1")
})
冒泡事件
我们在div里面放了三个元素,box1 里面有 box2,box2里面有box3,那么他们的关系就是
box3 -> box2 -> box1 box1是他们的 共同祖先
演示冒泡顺序
<div id="box1">box1
<div id="box2">box2
<div id="box3">
box3
</div>
</div>
</div>
// 冒泡演示,当我们点击box3的时候,我们看到 box3 -> box2 -> box1 都弹出来了
box2.addEventListener("click", function (event) {
alert("我是box2")
})
阻止冒泡
// 阻止冒泡,我们在 box3上面加了阻止冒泡事件后,当我们点击box3的时候,只有box3的提示弹出来了
// box2 继续回冒泡
box3.addEventListener("click", function (event) {
//阻止向上冒泡
event.stopPropagation();
alert("我是box3")
})
3、冒泡应用
当我们在讨论,冒泡是好还是坏的时候,实际上冒泡是对我们有利的;
如果没有冒泡,那么我们写的一个事件,需要考虑他在页面任何元素都需要处理,
类似于我们函数绑定事件一样,新加一个元素,我们就需要绑定一次的动作
小案例
写一个圆球,跟随鼠标移动
方式1
我们不采用冒泡的方式,直接绑定到指定的元素上面,看小球启动的效果
<style>
#box1 {
width: 100px;
height: 100px;
border-radius: 50%;
background-color: aqua;
text-align: center;
line-height: 100px;
position: absolute;
}
#box2 {
width: 500px;
height: 500px;
background-color: orange;
}
#box3{
width: 200px;
height: 200px;
background-color: tomato;
}
#box4{
width: 100px;
height: 100px;
background-color: skyblue;
position: absolute;
bottom: 0;
}
</style>
</head>
<body>
<div id="box1">
圆球
</div>
<div id="box2"></div>
<div id="box3" onclick="alert('box3')">
<div id="box4" onclick="alert('box4')"></div>
</div>
</body>
代码实现
当我们绑定box的时候,小球的移动会有迟钝,并且只能向一个方向,这个没有冒泡
//当我们绑定box的时候,小球的移动会有迟钝,并且只能向一个方向
box1.addEventListener("mousemove",(event) => {
//console.log(event.x)
box1.style.left = event.x + "px"
box1.style.top = event.y + "px"
})
方式2
//如果我们直接绑定到document上面,因为冒泡的原因,最终会作用到document上
document.addEventListener("mousemove", (event) => {
//console.log(event.x)
box1.style.left = event.x + "px"
box1.style.top = event.y + "px"
})
冒泡的好处演示
如果没有冒泡的存在,当小球移动到box2上面,那么就进不去了,我们来设置box2阻止冒泡
document.getElementById(“box2”).addEventListener(“mousemove”,event => {
//event.stopPropagation();
})
冒泡是否跟位置有关
事件的冒泡和元素的样式无关,只和结构相关
box4是 box3的子元素,我们让box4发生相对定位,
位置看起来是跟box3无关,但是我们点击box4的时候,一样会冒泡到box3上
案例演示
4、事件的委派
- 只绑定一次事件,既可以让所有的超链接,包括当前的和未来新建的超链接都具有这些事件
- 可以将事件统一绑定给document,这样点击超链接时由于事件的冒泡,
- 会导致document上的点击事件被触发,这样只绑定一次,所有的超链接都会具有这些事件
使用示例
<body>
<button id="btn">增加超链接</button>
<hr />
<ul id="list">
<li><a href="javascript:;">链接一</a></li>
<li><a href="javascript:;">链接二</a></li>
<li><a href="javascript:;">链接三</a></li>
<li><a href="javascript:;">链接四</a></li>
</ul>
<script>
/*
我一个希望:
只绑定一次事件,既可以让所有的超链接,包括当前的和未来新建的超链接都具有这些事件
思路:
可以将事件统一绑定给document,这样点击超链接时由于事件的冒泡,
会导致document上的点击事件被触发,这样只绑定一次,所有的超链接都会具有这些事件
*/
const links = document.querySelector("ul a");
const btn = document.getElementById("btn");
const list = document.getElementById("list");
//我们直接给绑定点击事件的时候直接绑定到最上级元素,document上
// 我们发现这种绑定会作用到任意的元素上面,这个时候需要精细的过滤了
document.addEventListener("click",event => {
alert(event.target.textContent)
})
btn.addEventListener("click",event => {
list.insertAdjacentHTML("beforeend",
'<li><a href="javascript:;">我是一个新的超链接</a></li>'
)
})
</script>
</body>
事件的委派-过滤
委派就是将本该绑定给多个元素的事件,统一绑定给document,这样可以降低代码复杂度方便维护,但是上面我们发现,无论我们点击哪个地方,都会触发该事件,那么我们需要根据条件过滤一次;
代码
<body>
<button id="btn">增加超链接</button>
<hr />
<ul id="list">
<li><a href="javascript:;">链接一</a></li>
<li><a href="javascript:;">链接二</a></li>
<li><a href="javascript:;">链接三</a></li>
<li><a href="javascript:;">链接四</a></li>
</ul>
<script>
/*
我一个希望:
只绑定一次事件,既可以让所有的超链接,包括当前的和未来新建的超链接都具有这些事件
思路:
可以将事件统一绑定给document,这样点击超链接时由于事件的冒泡,
会导致document上的点击事件被触发,这样只绑定一次,所有的超链接都会具有这些事件
委派就是将本该绑定给多个元素的事件,统一绑定给document,这样可以降低代码复杂度方便维护
*/
const btn = document.getElementById("btn");
const list = document.getElementById("list");
// 获取list中所有的超链接
const links = list.getElementsByTagName("a");
console.log("links", Array.from(links))
//我们直接给绑定点击事件的时候直接绑定到最上级元素,document上
// 我们发现这种绑定会作用到任意的元素上面,这个时候需要精细的过滤了
document.addEventListener("click",event => {
//在代码执行之前判断一下事件是由谁复发
// 检查 event.target 是否在links 中存在
//console.log(event.target)
if([...links].includes(event.target)) {
alert(event.target.textContent)
}
})
btn.addEventListener("click",event => {
list.insertAdjacentHTML("beforeend",
'<li><a href="javascript:;">我是一个新的超链接</a></li>'
)
})
</script>
</body>
</html>
5、事件的捕获
事件的传播机制:
- 在DOM中,事件的传播可以分为三个阶段
- 捕获阶段 (由祖先元素向目标元素进行事件的捕获)(默认情况下,事件不会在捕获阶段触发)
- 目标阶段 (触发事件的对象)
- 冒泡阶段 (由目标元素向祖先元素进行事件的冒泡)
- 事件的捕获,指事件从外向内的传导,当前元素触发事件以后,会先从当前元素最大的祖先元素开始向当前元素进行事件的捕获
- 如果希望在捕获阶段触发事件,可以将addEventListener的第三个参数设置为true
- 一般情况下我们不希望事件在捕获阶段触发,所有通常都不需要设置第三个参数
公共代码
<style>
#box1 {
width: 300px;
height: 300px;
background-color: greenyellow;
}
#box2 {
width: 200px;
height: 200px;
background-color: orange;
}
#box3 {
width: 100px;
height: 100px;
background-color: tomato;
}
</style>
</head>
<body>
<div id="box1">
<div id="box2">
<div id="box3"></div>
</div>
</div>
const box1 = document.getElementById("box1");
const box2 = document.getElementById("box2");
const box3 = document.getElementById("box3");
冒泡阶段 从内向外
box1.addEventListener("click",event => {
alert('box1阶段===》' + event.eventPhase);// eventPhase 表示事件触发的阶段
// //1 捕获阶段 2 目标阶段 3 冒泡阶段
})
box2.addEventListener("click", event => {
alert('box2阶段===》' + event.eventPhase);// eventPhase 表示事件触发的阶段
// //1 捕获阶段 2 目标阶段 3 冒泡阶段
})
box3.addEventListener("click", event => {
alert('box3阶段===》' + event.eventPhase);// eventPhase 表示事件触发的阶段
// //1 捕获阶段 2 目标阶段 3 冒泡阶段
})
捕获阶段 从外向内
// 当点击box3的时候,先触发 box1(1) box2(1) box3(2)
// 当点击box2的时候,先触发 box1(1) box2(2)
// 当点击box1的时候,先触发 box1(2)
box1.addEventListener("click", event => {
alert('box1捕获阶段===》' + event.eventPhase);// eventPhase 表示事件触发的阶段
// //1 捕获阶段 2 目标阶段 3 冒泡阶段
},true)
box2.addEventListener("click", event => {
alert('box2捕获阶段===》' + event.eventPhase);// eventPhase 表示事件触发的阶段
// //1 捕获阶段 2 目标阶段 3 冒泡阶段
}, true)
box3.addEventListener("click", event => {
alert('box3捕获阶段===》' + event.eventPhase);// eventPhase 表示事件触发的阶段
// //1 捕获阶段 2 目标阶段 3 冒泡阶段
}, true)
6、bom 对象
BOM (浏览器对象模型)是浏览器提供的一组API,通过JavaScript与浏览器窗口文档、历史记录等组件交互。核心对象是window,其他常用对象包括location(地址栏)、history(历史记录)、navigator(浏览器信息)等
BOM对象:
- Window —— 代表浏览器窗口(全局对象)
- Navigator —— 浏览器的对象(可以用来识别浏览器)
- Location —— 浏览器的地址栏信息
- History —— 浏览器的历史记录(控制浏览器前进后退)
- Screen —— 屏幕的信息
BOM对象都是作为window对象的属性保存的,所以可以直接在JS中访问这些对象
console.log(“window”,window);
console.log(“Navigator”, Navigator);
console.log(“Location”, Location);
6.1 Navigator对象
Navigator —— 浏览器的对象(可以用来识别浏览器)
userAgent 返回一个用来描述浏览器信息的字符串
文档地址:https://developer.mozilla.org/zh-CN/docs/Web/API/Navigator#%E5%AE%9E%E4%BE%8B%E6%96%B9%E6%B3%95
使用示例
判断浏览器
<body>
<script>
/*
- Navigator —— 浏览器的对象(可以用来识别浏览器)
userAgent 返回一个用来描述浏览器信息的字符串
文档地址:https://developer.mozilla.org/zh-CN/docs/Web/API/Navigator#%E5%AE%9E%E4%BE%8B%E6%96%B9%E6%B3%95
*/
// 浏览器对象字符串
console.log(navigator)
console.log(navigator.userAgent);
// 判断浏览器
let sBrowser
const sUsrAg = navigator.userAgent
// The order matters here, and this may report false positives for unlisted browsers.
if (sUsrAg.indexOf("Firefox") > -1) {
sBrowser = "Mozilla Firefox"
// "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0"
} else if (sUsrAg.indexOf("SamsungBrowser") > -1) {
sBrowser = "Samsung Internet"
// "Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-G955F Build/PPR1.180610.011) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/9.4 Chrome/67.0.3396.87 Mobile Safari/537.36
} else if (
sUsrAg.indexOf("Opera") > -1 ||
sUsrAg.indexOf("OPR") > -1
) {
sBrowser = "Opera"
// "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 OPR/57.0.3098.106"
} else if (sUsrAg.indexOf("Trident") > -1) {
sBrowser = "Microsoft Internet Explorer"
// "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; Zoom 3.6.0; wbx 1.0.0; rv:11.0) like Gecko"
} else if (sUsrAg.indexOf("Edge") > -1) {
sBrowser = "Microsoft Edge (Legacy)"
// "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299"
} else if (sUsrAg.indexOf("Edg") > -1) {
sBrowser = "Microsoft Edge (Chromium)"
// Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.64
} else if (sUsrAg.indexOf("Chrome") > -1) {
sBrowser = "Google Chrome or Chromium"
// "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/66.0.3359.181 Chrome/66.0.3359.181 Safari/537.36"
} else if (sUsrAg.indexOf("Safari") > -1) {
sBrowser = "Apple Safari"
// "Mozilla/5.0 (iPhone; CPU iPhone OS 11_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.0 Mobile/15E148 Safari/604.1 980x1306"
} else {
sBrowser = "unknown"
}
alert(`You are using: ${sBrowser}`)
</script>
</body>
6.2 location 对象
location 表示的是浏览器地址栏的信息
- 可以直接将location的值修改为一个新的地址,这样会使得网页发生跳转
- location.assign() 跳转到一个新的地址
- location.replace() 跳转到一个新的地址(无法通过回退按钮回退)
- location.reload() 刷新页面,可以传递一个true来强制清缓存刷新
- location.href 获取当前地址
使用示例
console.log(location.href)
location = “https://www.lilichao.com”
location.assign(“https://www.lilichao.com”)
location.replace(“https://www.lilichao.com”)
location.reload(true)
6.3 history 对象
- history.back() 回退按钮
- history.forward() 前进按钮
- history.go() 可以向前跳转也可以向后跳转
使用示例
console.log(history.length)
//history.back()
//history.forward()
// 正数代表是向前,负数是后退 x 步
//history.go(-1)
7、定时器
通过定时器,可以使代码在指定时间后执行
- 设置定时器的方式有两种:
- setTimeout()
- - 参数:
- 回调函数(要执行的代码)
- 间隔的时间(毫秒)
- 关闭定时器
- clearTimeout()
- - 参数:
- setInterval() (每间隔一段时间代码就会执行一次)
- 参数:
- 回调函数(要执行的代码)
- 间隔的时间(毫秒)
- 关闭定时器
- clearInterval()
- 参数:
- setTimeout()
setTimeout
// 间隔2s中后执行
const timer = setTimeout(() => {
console.log('我是定时器代码==》')
},2000)
// 关闭定时器
clearTimeout(timer);
setInterval
<body>
<h1>0</h1>
<button>停止</button>
<script>
/*
通过定时器,可以使代码在指定时间后执行
- 设置定时器的方式有两种:
setTimeout()
- 参数:
1. 回调函数(要执行的代码)
2. 间隔的时间(毫秒)
- 关闭定时器
clearTimeout()
setInterval() (每间隔一段时间代码就会执行一次)
- 参数:
1. 回调函数(要执行的代码)
2. 间隔的时间(毫秒)
- 关闭定时器
clearInterval()
*/
// 间隔2s中后执行
const timer = setTimeout(() => {
console.log('我是定时器代码==》')
},2000)
// 关闭定时器
clearTimeout(timer);
const h1html = document.querySelector("h1");
let num = 0;
//间隔固定的时间一直执行
const interTime = setInterval(() => {
num ++;
h1html.textContent = num;
}, 50);
//手动停止
const btn = document.getElementsByTagName("button")[0]
btn.onclick = function() {
clearInterval(interTime);
}
</script>
</body>
8、事件循环
事件循环(event loop)
- 函数在每次执行时,都会产生一个执行环境
时产生的一切数据- 问题:函数的执行环境要存储到哪里呢?
- 函数的执行环境存储到了一个叫做调用栈的地方
- 栈,是一种数据结构,特点 后进先出
调用栈(call stack)
- 调用栈负责存储函数的执行环境
境会作为一个栈帧
插入到调用栈的栈顶,函数执行完毕其栈帧会自动从栈中弹出
使用示例
function fn() {
let a = 10;
let b = 20;
function fn2() {
console.log("fn2")
}
fn2();
console.log("fn~~")
}
fn();
console.log("1111")
</script>
9、事件循环-消息队列
事件循环(event loop)
- 函数在每次执行时,都会产生一个执行环境
数执行时产生的一切数据- 问题:函数的执行环境要存储到哪里呢?
- 函数的执行环境存储到了一个叫做调用栈的地方
- 栈,是一种数据结构,特点 后进先出
- 队列,是一种数据结构,特点 先进先出
调用栈(call stack)
- 调用栈负责存储函数的执行环境
执行环境会作为一个栈帧
插入到调用栈的栈顶,函数执行完毕其栈帧会自动从栈中弹出
消息队列
- 消息队列负责存储将要执行的函数
响应函数并不是直接就添加到调用栈中的
因为调用栈中有可能会存在一些还没有执行完的代码- 事件触发后,JS引擎是将事件响应函数插入到消息队列中排队
代码演示
function fn() {
let a = 10
let b = 20
function fn2() {
console.log("fn2")
}
fn2()
console.log("fn~")
}
fn()
console.log(1111);
// 我们思考,当我们有一个函数没有调用的时候,那么他是应该放到哪里呢
// 当我们执行这个函数的时候,是否立即就执行呢
//
const btn1 = document.getElementById("btn1");
const btn2 = document.getElementById("btn2");
// 执行此函数,会有一个3s的时间等待操作
btn1.onclick = function() {
alert("btn1事件被触发了!!!");
const begin = Date.now();
const num = document.getElementById("num");
while(Date.now() - begin < 3000) {
// let temNum = num.textContent;
// let timer;
// if(temNum <= 0) {
// clearTimeout(timer);
// break;
// }
// num.textContent = temNum - 100
}
}
btn2.onclick = function() {
alert("我是btn2的操作")
}
</script>
10、定时器-消息队列
定时器的本质,就是在指定的时间后将函数添加到消息队列中
示例1
/**
* 定时器的本质,就是在指定的时间后将函数添加到消息队列中
*/
console.time()
setTimeout(() => {
console.timeEnd();
console.log("定时器执行了!!!")
}, 3000);
const begion = Date.now();
while(Date.now() - begion < 6000) {}
setInterval()
setInterval() 每间隔一段时间就将函数添加到消息队列中,但是如果,函数执行较慢,那么就无法确保每次执行的间隔是一样的
console.time('定时间隔执行');
setInterval(() => {
console.timeEnd("定时间隔执行");
// 如果我自己的逻辑执行需要1s 那么,实际上下次就是执行时间 + 间隔的时间
//console.log("定时器执行了~~~~")
alert("执行!!!")
console.time("定时间隔执行")
}, 3000);
创建一个确保函数每次执行都有相同的间隔
console.time("固定间隔")
setTimeout(function fn(){
console.timeEnd("固定间隔");
//console.log("我开始执行了 !")
alert("---")
// 在settimeout的回调函数的最后,再调用一次setTimeout函数
console.time("固定间隔")
setTimeout(fn,3000);
}, 3000);
函数执行顺序
// 函数执行顺序
setTimeout(() => {
console.log('0毫秒以后开始执行~~~')
}, 0);
//先执行,因为他是全局作用域,先进栈,setTimeout需要先进队列,所以慢一些
console.log(22222)