+ 在前端 JS 内的代码执行是单线程(同一个时间内只能做一件事情)
+ JS 提供给我们一个 异步代码执行机制(定时器)
+ 异步代码执行机制(EventLoop)
=> 当代码执行过程中, 遇到异步代码了
=> 会把异步代码放在 事件队列池 中等待
=> 继续向后执行同步代码
=> 等到所有同步代码执行完毕, 调用栈空的时候, 再去异步事件队列池内拿到异步代码执行(先进先出)
定时器的开启(设置)
1. 延时定时器(炸弹定时器)
=> 语法: setTimeout(函数, 数字)
-> 函数: 表示时间到达的时候要执行的代码
-> 数字: 倒计时的时间(单位是 ms)
2. 间隔定时器
=> 语法: setInterval(函数, 数字)
-> 函数: 表示每间隔一段时间以后执行的代码
-> 数字: 间隔的时间(单位是 ms)
定时器的返回值
+ 两种定时器的返回值是一样的
+ 不区分定时器种类, 只是表示你是页面上的第几个定时器
关闭定时器(销毁)
+ 销毁定时器的时候, 不区分定时器种类, 只要给出的定时器数字是对的, 就可以销毁
+ 语法:
=> clearTimeout()
=> cleatInterval()
*/
// 1. 延时定时器
// 2000ms 以后会执行 a 这个函数
// console.log(new Date())
// setTimeout(function a() {
// console.log('boom')
// console.log(new Date())
// }, 2000)
// 2. 间隔定时器
// setInterval(function () {
// console.log(new Date())
// }, 1000)
// 定时器的返回值
// var t1 = setTimeout(function () {})
// var t2 = setInterval(function () {})
// console.log('t1 : ', t1)
// console.log('t2 : ', t2)
// 关闭定时器
var t1 = setTimeout(function () { console.log('timeout') }, 3000)
var t2 = setInterval(function () { console.log('interval') }, 1000)
// 点击按钮关闭定时器
// btn 表示页面中 id 为 btn 的元素
// onclick 表示点击在这个元素身上的时候, 执行后面的函数
btn.onclick = function () {
// 执行关闭定时器的代码
// clearTimeout(t1)
// clearTimeout(t2)
clearInterval(t1)
clearInterval(t2)
}
案例:倒计时
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
body {
background-color: #ccc;
}
div {
width: 300px;
height: 80px;
background-color: #ccc;
line-height: 40px;
text-align: center;
color: red;
text-shadow: -1px -1px 0 #ccc, 1px 1px 0 #fff;
}
</style>
</head>
<body>
<div>
<p>距离出去浪还有: </p>
<span id="timeBox"> 00 天 00 小时 00 分 00 秒</span>
</div>
<script src="./utils.js"></script>
<script>
/*
倒计时
+ 每间隔 1s 拿到一次当前时间和目标时间之间的时间差
=> 间隔定时器
-> 当前时间
+ 直接获取到目标时间
*/
// 1. 拿到目标时间
var target = new Date('2022-3-1 17:18:30')
// 2. 开启定时器
var timer = setInterval(function () {
// 3. 在定时器内拿到当前时间
var current = new Date()
// 4. 拿到每一次的时间差
var diff = diffTime(current, target)
// 5. 写入页面
// 元素.innerText = 值
// 向一个标签内写入文本, 当你写入第二个文本的时候, 会覆盖第一个
timeBox.innerText = ` ${ diff.day } 天 ${ diff.hours } 小时 ${ diff.minutes } 分 ${ diff.seconds } 秒`
// 6. 关闭定时器
// 当四个值都是 0 的时候, 关闭定时器
if (diff.day === 0 && diff.hours === 0 && diff.minutes === 0 && diff.seconds === 0) clearInterval(timer)
}, 1000)
</script>
</body>
</html>
效果:
案例分析:
+ 问题1: 什么时候会出现效果 ? 点击的时候
=> 所有的代码书写在点击事件内
+ 问题2: 在点击事件内根据时间开启定时器, 开启哪一个定时器 ?
=> 间隔定时器, 因为需要每间隔 1s 修改一次
=> 点击的时候, 只是开启一个定时器吗 ?
=> 60s 以后要关闭
=> 点击的瞬间, 会在 1s 以后, 才会出现第一次效果
=> 点击的瞬间, 应该先修改一次文本
// 3. 全局准备一个开关
var flag = true
// 1. 给 页面中 id="btn" 的按钮添加一个点击事件
btn.onclick = function () {
// 3-2. 判断开关是否是开启的
if (!flag) return
// 3-3. 关闭开关
flag = false
// 2. 开启定时器
// 2-1. 需要在开启定时器以前, 提前设置一次文本
btn.innerText = '再次获取(5s)'
// 2-2. 提前准备一个变量, 表示倒计时的总时间(注意: 书写在定时器外面)
var s = 4
var timer = setInterval(function () {
// 2-3. 修改 button 按钮内的文本
btn.innerText = `再次获取(${ s-- }s)`
// 2-4. 当 s 倒计时到达 0 的时候, 关闭定时器
if (s < 0) {
clearInterval(timer)
// 文本回归
btn.innerText = '点击获取验证码'
// 3-4. 再次开启开关
flag = true
}
}, 1000)
}
/*
分析问题出现的原因
+ 因为所有内容都书写在点击事件内
+ 我们每一次的点击都会执行一遍点击事件内的所有代码
+ 点击事件内的代码执行一次, 就会开启一个定时器
+ 当你多次点击的时候, 会不停的开启定时器, 多个定时器并行
解决方向
+ 当你点击的时候, 开启倒计时
+ 在本次倒计时结束以前, 所有的点击没有效果
+ 只要事件函数内的代码执行, 就会开启一个倒计时
+ 核心: 不让点击事件内的代码执行
+ 如何能让事件内的代码不执行 ?
=> return
=> 因为是一个函数, 只要执行了 return, 后续代码都不执行了
+ 解决频繁 DOM 操作的技能 ?
=> 开关
=> 在全局定义一个开关, 设置为 true
=> 在事件内判断 如果 开关是 true, 那么代码执行
=> 如果开始是 false, 代码直接 return
=> 当你点击的时候
-> 不光要开启定时器, 还在在开启定时器的时候把开关关闭
-> 等到关闭定时器的时候, 表示本次倒计时结束了, 此时再次打开开关, 表示可以进行下一次点击了