JavaScript 高级 - 第4天
文章目录
- [JavaScript 高级 - 第4天](#JavaScript 高级 - 第4天)
深浅拷贝
在开发中,我经常需要复制一个对象,如果直接用赋值会有下面的问题:
html
<script>
const obj = {
uname: 'pink',
age: 18
}
const o = obj // 把 obj 赋值给 o
console.log(o) // {uname: 'pink', age: 18}
o.age = 20 // 修改 o 中的 age
console.log(o) // {uname: 'pink', age: 20}
console.log(obj) // {uname: 'pink', age: 20} obj 也被修改了
</script>
这样和我们的意愿相悖,那么应该如何实现拷贝后双方互不干扰呢?
浅拷贝
首先浅拷贝和深拷贝只针对引用类型。
浅拷贝:拷贝的是地址。
常见方法:
-
拷贝对象:
Object.assgin()或 展开运算符{...obj}拷贝对象html<script> const obj = { uname: 'pink', age: 18 } // 展开运算符 const o = { ...obj } console.log(o) // {uname: 'pink', age: 18} o.age = 20 console.log(o) // {uname: 'pink', age: 20} console.log(obj) // {uname: 'pink', age: 18} 不变 </script>或:
html<script> const obj = { uname: 'pink', age: 18 } // assign const o = {} Object.assign(o, obj) console.log(o) // {uname: 'pink', age: 18} o.age = 20 console.log(o) // {uname: 'pink', age: 20} console.log(obj) // {uname: 'pink', age: 18} </script> -
拷贝数组:
Array.prototype.concat()或者[...arr],可以参考上面的例子自行补充。
但这两个方法也有一定的问题,如果我们在对象中再套一个对象,你会发现这个问题的根本还是没有被解决:
html
<script>
const obj = {
uname: 'pink',
age: 18,
family: {
baby: '小pink'
}
}
// 浅拷贝
const o = {}
Object.assign(o, obj)
o.age = 20
o.family.baby = '老pink'
console.log(o) // { uname: 'pink', age: 20, family: {baby: '老pink'} }
console.log(obj) // { uname: 'pink', age: 20, family: {baby: '老pink'} } family 中的 baby 也被修改了!
</script>
浅拷贝拷贝的是地址,当拷贝的是对象的时候,在栈中拷贝到的实际上是指向对象内容的地址,它们指向同一个对象内容。因此对对象中的对象进行修改时,在堆中的内容会被修改,即被拷贝者所指的内容被修改了:

所以,浅拷贝仅是表层的拷贝,无法做到多层拷贝。
如果是简单数据类型拷贝值,引用数据类型拷贝的是地址 (简单理解: 如果是单层对象,没问题,如果有多层就有问题)
深拷贝
首先浅拷贝和深拷贝只针对引用类型。
深拷贝:拷贝的是对象,不是地址。
常见方法:
- 通过递归实现深拷贝
lodash/cloneDeep- 通过
JSON.stringify()实现
递归实现深拷贝
通过函数递归:
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。
- 简单理解:函数内部自己调用自己,这个函数就是递归函数。
- 递归函数的作用和循环效果类似
- 由于递归很容易发生"栈溢出 "错误(
stack overflow),所以必须要加退出条件return
我们在代码中来实现深拷贝(简易版):
html
<body>
<script>
const obj = {
uname: 'pink',
age: 18,
// 复杂数据类型
hobby: ['乒乓球', '足球'],
family: {
baby: '小pink'
}
}
const o = {}
// 拷贝函数
function deepCopy(newObj, oldObj) {
for (let k in oldObj) {
// 处理数组的问题 一定先写数组再写对象 不能颠倒 因为数组也属于对象
if (oldObj[k] instanceof Array) {
// 因为数组是引用类型 需要遍历来拷贝 因此也需要进行for循环 我们可以利用递归来实现
// 声明空数组
newObj[k] = []
// newObj[k] []
// oldObj[k] ['乒乓球', '足球']
// 再次递归实现数组拷贝
deepCopy(newObj[k], oldObj[k])
} else if (oldObj[k] instanceof Object) {
// 处理对象问题 和数组一个道理
newObj[k] = {}
deepCopy(newObj[k], oldObj[k])
}
else {
// 简单数据类型
// k 属性名 oldObj[k] 属性值
// newObj[k] === o.uname 给新对象添加属性
newObj[k] = oldObj[k]
}
}
}
deepCopy(o, obj) // 函数调用 两个参数 o 新对象 obj 旧对象
console.log(o)
o.age = 20
o.hobby[0] = '篮球'
o.family.baby = '老pink'
console.log(obj)
</script>
</body>
js库lodash里面cloneDeep内部实现了深拷贝
在开发中,我们不需要亲自写深拷贝函数,只需要引用lodash即可。详细安装和使用教程见官方网站。其中深拷贝指路:lodash.cloneDeep | Lodash中文文档 | Lodash中文网
语法规范:_.cloneDeep(obj),这是一个函数,有返回值。
使用步骤:
- 先在自己的JavaScript代码前引用lodash库
- 使用专门的语法调用函数,接收返回值
html
<body>
<!-- 先引用 -->
<script src="./lodash.min.js"></script>
<script>
const obj = {
uname: 'pink',
age: 18,
hobby: ['乒乓球', '足球'],
family: {
baby: '小pink'
}
}
// 接收返回值
const o = _.cloneDeep(obj)
console.log(o)
o.family.baby = '老pink'
console.log(obj) // 不受影响
</script>
</body>
JSON序列化
我们可以先把对象转换为字符串:JSON.stringify(obj),此时我们拷贝的不再是引用类型了,而是简单数据类型。如果这时我们又把这个字符串转换为对象:JSON.parse(JSON.stringify(obj)),正好,这个新的对象和这个被拷贝的不是一个对象!
html
<body>
<script>
const obj = {
uname: 'pink',
age: 18,
hobby: ['乒乓球', '足球'],
family: {
baby: '小pink'
}
}
// 把对象转换为 JSON 字符串
// 再把字符串转换为对象 使用新的变量接收
const o = JSON.parse(JSON.stringify(obj))
console.log(o)
o.family.baby = '123'
console.log(obj) // 不受影响
</script>
</body>
异常处理
了解 JavaScript 中程序异常处理的方法,提升代码运行的健壮性。
throw 抛异常
异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行。
html
<script>
function counter(x, y) {
if(!x || !y) {
throw new Error('参数不能为空!') // 抛出异常信息
// 或 throw '参数不能为空!'
}
return x + y
}
counter() // 没有传递参数
</script>
如果没有传参,最后的报错:

总结:
throw抛出异常信息,程序也会终止执行throw后面跟的是错误提示信息Error对象配合throw使用,能够设置更详细的错误信息
try ... catch 捕获异常
我们可以通过try/catch捕获错误信息(浏览器提供的错误信息),try表示试试,catch拦住,finally最后。
html
<script>
function foo() {
try {
// 可能发送错误的代码 要写到 try
const p = document.querySelector('.p')
p.style.color = 'red'
} catch (error) {
// 如果 try 代码段中执行有错误时,会执行 catch 代码段
// 查看错误信息
console.log(error.message)
// catch 只是拦截错误 提供信息 但不会终止程序 需要手动中断
// 终止代码继续执行
return
// 或配合 throw 使用 因为 throw 可以主动中断
}
finally {
// 不管程序对不对 一定会执行 finally 里的代码
alert('执行')
}
console.log('如果出现错误,我的语句不会执行')
}
foo()
</script>
总结:
try...catch用于捕获错误信息- 将预估可能发生错误的代码写在
try代码段中 - 如果
try代码段中出现错误后,会执行catch代码段,并截获到错误信息 finally不管是否有错误,都会执行
debugger
相当于断点调试,类似于在代码某一行设置断点 ,当代码运行后,会直接跳转到debugger处。
JavaScript
function potentiallyBuggyCode() {
debugger
// 执行可能有漏洞的操作以进行检查、逐步调试等
}
处理 this
了解函数中 this 在不同场景下的默认值,知道动态指定函数 this 值的方法。
this 是 JavaScript 最具"魅惑"的知识点,不同的应用场合 this 的取值可能会有意想不到的结果,在此我们对以往学习过的关于【 this 默认的取值】情况进行归纳和总结。
this 指向-普通函数
普通函数 的调用方式决定了 this 的值,即【谁调用 this 的值指向谁】,如下代码所示:
html
<script>
// 普通函数 谁调用 this就指向谁
console.log(this) // window
function fn() {
console.log(this) // window
}
window.fn()
// 定时器
window.setTimeout(function () {
console.log(this) // window
}, 1000)
// 事件监听
document.querySelector('button').addEventListener('click', function () {
console.log(this) // 指向 button
})
// 对象
const obj = {
sayHi: function () {
console.log(this) // 指向 obj
}
}
obj.sayHi()
</script>
注: 普通函数没有明确调用者时 this 值为 window,严格模式 下没有调用者时 this 的值为 undefined。(严格模式要求必须先声明再调用/赋值)
this 指向-箭头函数
箭头函数 中的 this 与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在 this !箭头函数中访问的 this 不过是箭头函数所在作用域的 this 变量。
- 箭头函数会默认我们绑定外层
this的值,所以箭头函数中this的值和外层的this是一样的 - 箭头函数中的
this引用的就是最近的作用域中的this - 向外层作用域中,一层一层查找
this,直到有this的定义
html
<script>
console.log(this) // 此处为 window
// 箭头函数
const sayHi = function() {
console.log(this) // 该箭头函数中的 this 为函数声明环境中 this 一致
}
// 普通对象
const user = {
name: '小明',
// 该箭头函数中的 this 为函数声明环境中 this 一致
walk: () => {
console.log(this)
},
sleep: function () {
let str = 'hello'
console.log(this)
let fn = () => {
console.log(str)
console.log(this) // 该箭头函数中的 this 与 sleep 中的 this 一致
}
// 调用箭头函数
fn();
}
}
// 动态添加方法
user.sayHi = sayHi
// 函数调用
user.sayHi()
user.sleep()
user.walk()
</script>
在开发中【使用箭头函数前需要考虑函数中 this 的值】,事件回调函数使用箭头函数时,this 为全局的 window,因此DOM事件回调函数不推荐使用箭头函数,如下代码所示:
html
<script>
// DOM 节点
const btn = document.querySelector('.btn')
// 箭头函数 此时 this 指向了 window 而不是DOM节点
btn.addEventListener('click', () => {
console.log(this)
})
// 普通函数 此时 this 指向了 DOM 对象
btn.addEventListener('click', function () {
console.log(this)
})
</script>
同样由于箭头函数 this 的原因,基于原型的面向对象也不推荐采用箭头函数,如下代码所示:
html
<script>
function Person() {
}
// 原型对象上添加了箭头函数
Person.prototype.walk = () => {
console.log('人都要走路...')
console.log(this); // window
}
const p1 = new Person()
p1.walk()
</script>
总结:
- 函数内不存在
this,沿用上一级的 - 不适用于构造函数、原型函数、dom事件函数等
- 适用于需要使用上层的
this的地方 - 使用正确的话,它会在很多地方带来方便,后面我们会大量使用,慢慢体会
改变 this 指向
以上归纳了普通函数和箭头函数中关于 this 默认值的情形,不仅如此 JavaScript 中还允许指定函数中 this 的指向,有 3 个方法可以动态指定普通函数中 this 的指向:
call()(了解)
使用 call 方法调用函数,同时指定函数中 this 的值。
语法:函数名.call(thisArg, arg1, arg2, ...)
其中,
thisArg:在函数运行时指定的this值arg1, arg2:传递到其他参数- 返回值就是函数的返回值,因为它就是调用函数
使用方法如下代码所示:
html
<script>
// 普通函数
function sayHi() {
console.log(this) // window
}
let obj = {
name: '小明',
age: 18
}
// 调用函数并指定 this 的值
sayHi.call(obj) // this 值为 obj
// 如果函数有形参 可以在call中添加实参
// 求和函数
function counter(x, y) {
return x + y;
}
// 调用 counter 函数,并传入参数
counter.call(obj, 1, 2) // 指向 obj 返回 3
</script>
总结:
call方法能够在调用函数的同时指定this的值- 使用
call方法调用函数时,第1个参数为this指定的值 call方法的其余参数会依次自动传入函数做为函数的参数
apply()
使用 apply 方法调用函数 ,同时指定函数中 this 的值.
语法:函数名.apply(thisArg, [argsArray])
thisArg:在函数运行时指定的this值argsArray:传递的值,必须包含在数组里- 返回值就是函数的返回值,因为它就是调用函数
- 因此
apply主要跟数组 有关系,比如使用Math.max()求数组最大值
使用方法如下代码所示:
html
<script>
// 普通函数
function sayHi() {
console.log(this) // { name: '小明', age: 18 }
}
let obj = {
name: '小明',
age: 18
}
// 调用函数并指定 this 的值
sayHi.apply(obj) // this 值为 obj
// 求和函数
function counter(x, y) {
return x + y
}
// 调用 apply 函数,并传入参数
// 必须以数组方式传递数据
counter.apply(obj, [5, 10]) // 返回 15
</script>
由于apply主要针对于数组参数,因此可以结合Math.max()求数组最大值:
javascript
// 使用场景: 求数组最大值/最小值
// const max = Math.max(1, 2, 3)
const arr = [100, 44, 77]
const max = Math.max.apply(Math, arr)
const min = Math.min.apply(null, arr)
// 或使用展开运算符
// const max = Math.max(...arr)
console.log(max, min) // 100 44
总结:
apply方法能够在调用函数的同时指定this的值- 使用
apply方法调用函数时,第1个参数为this指定的值 apply方法第2个参数为数组,数组的单元值依次自动传入函数做为函数的参数
bind()(重点)
bind 方法并不会调用函数 ,而是创建一个指定了 this 值的新函数。
语法:函数名.bind(thisArg, arg1, arg2, ...)
thisArg:在函数运行时指定的this值argsArray:传递的值,必须包含在数组里- 返回由指定的
this值和初始化参数改造的原函数拷贝(新函数) - 因此当我们只是想改变
this指向,并且不想调用这个函数的时候,可以使用bind,比如改变定时器内部的this指向。
使用方法如下代码所示:
html
<script>
// 普通函数
function sayHi() {
console.log(this)
}
let obj = {
name: '小明',
age: 18
}
// 调用 bind 指定 this 的值
// bind 不会调用函数 但能改变 this 的指向
// 返回值是一个函数 但是这个函数里面的 this 是更改过的
const sayHello = sayHi.bind(obj)
// 调用使用 bind 创建的新函数
sayHello() // { name: '小明', age: 18 }
</script>
在开发中,由于定时器是默认由window调用的,有时候我们需要改变定时器内部的this指向:
html
<body>
<button>发送短信</button>
<script>
// 需求,有一个按钮,点击里面就禁用,2秒钟之后开启
document.querySelector('button').addEventListener('click', function () {
// 禁用按钮
this.disabled = true
window.setTimeout(function () {
// 在这个普通函数里面,我们要this由原来的window 改为 btn
this.disabled = false
}.bind(this), 2000) // 这里的this 和 btn 一样
})
</script>
</body>
注:bind 方法创建新的函数,与原函数的唯一的变化是改变了 this 的值。
call apply bind 总结
相同点:
- 都可以改变函数内部的
this指向
区别点:
call和apply都会调用函数,并且改变函数内部this指向call和apply传递的参数不一样,call传递参数arg1, arg2, ...形式,apply必须数组形式bind不会调用函数,可以改变函数内部this指向
主要应用场景:
call调用函数并且可以传递参数apply经常跟数组有关系,比如借助于数学对象实现数组最大值和最小值bind不调用函数,但是还想改变this指向,比如改变定时器内部的this指向
防抖节流
防抖(debounce)
所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。防抖,即单位时间内,频繁触发事件,只执行最后一次。

由图所示,当我们触发一次事件后,事件执行还没结束时,我们又再次触发该事件。防抖会取消掉没执行完的事件,重新开始准备本次触发的执行。总而言之,在事件未结束前若多次触发,防抖会选择重新开始执行最近一次的触发,而把之间的所有事件都取消掉。只要被打断,就需要重新来。
使用场景:
- 搜索框输入,只需用户最后一次输入完,再发送请求
- 手机号、邮箱验证输入检测
我们在例子中体会一下:
我们设计一个函数,当鼠标在盒子里移动时,计数器 i会加1:
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>Document</title>
<style>
.box {
width: 500px;
height: 500px;
background-color: #ccc;
color: #fff;
text-align: center;
font-size: 100px;
}
</style>
</head>
<body>
<div class="box"></div>
<script>
const box = document.querySelector('.box')
let i = 1 // 让这个变量++
// 鼠标移动函数
function mouseMove() {
box.innerHTML = ++i
// 如果里面存在大量操作 dom 的情况,可能会卡顿
}
box.addEventListener('mousemove', mouseMove)
</script>
</body>
</html>
但这会有个问题,因为鼠标移动太快,像素一改动i就会加1,这会导致多次进行 DOM 操作而导致卡顿。

由此,我们想要延时检测鼠标是否移动,只有在鼠标停顿后再移动才进行加1操作。我们可以通过防抖实现这个效果。
实现防抖只要有两种方式:
- lodash提供的防抖函数来处理
- 手写一个防抖函数来处理
lodash提供的防抖函数
我们先看看方式1,在lodash官网中搜索,找到防抖函数,这里指路:lodash.debounce | Lodash中文文档 | Lodash中文网。
语法:_.debounce(func, [wait=0], [options=])
debounced(防抖动)函数,该函数会从上一次被调用后,延迟 wait 毫秒后调用 func 方法。 debounced(防抖动)函数提供一个 cancel 方法取消延迟的函数调用以及 flush 方法立即调用。
options(选项) 对象决定如何调用 func 方法,options.leading 与|或 options.trailing 决定延迟前后如何触发(注:是 先调用后等待 还是 先等待后调用)。
func 调用时会传入最后一次提供给 debounced(防抖动)函数 的参数。 后续调用的 debounced(防抖动)函数返回是最后一次 func 调用的结果。
简单来说,wait就是延迟的毫秒数,func就是需要执行的函数。有点像定时器。
在上述例子中:
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>Document</title>
<style>
.box {
width: 500px;
height: 500px;
background-color: #ccc;
color: #fff;
text-align: center;
font-size: 100px;
}
</style>
</head>
<body>
<div class="box"></div>
<!-- 先引入 这里写你们存放库代码的位置即可 -->
<script src="./lodash.min.js"></script>
<script>
const box = document.querySelector('.box')
let i = 1 // 让这个变量++
// 鼠标移动函数
function mouseMove() {
box.innerHTML = ++i
// 如果里面存在大量操作 dom 的情况,可能会卡顿
}
// 使用防抖函数 鼠标停止500ms后才调用函数
box.addEventListener('mousemove', _.debounce(mouseMove, 500))
</script>
</body>
</html>
注意,一定要先引用lodash库才能使用这个防抖函数。
现在我们处理后,可以看到效果好了很多,也减少了性能的消耗:

手写防抖函数
要求:鼠标在盒子上移动,鼠标停止500ms之后,里面的数字才会发生变化+1
核心思路 :防抖的核心就是利用定时器(setTimeout)来实现
- 声明一个定时器变量
- 当鼠标每次滑动都先判断是否有定时器 了,如果有定时器先清除以前的定时器
- 如果没有定时器则开启 定时器,记得存到变量里面(方便关闭)
- 在定时器里面调用要执行的函数
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>Document</title>
<style>
.box {
width: 500px;
height: 500px;
background-color: #ccc;
color: #fff;
text-align: center;
font-size: 100px;
}
</style>
</head>
<body>
<div class="box"></div>
<script>
const box = document.querySelector('.box')
let i = 1
// 鼠标移动函数
function mouseMove() {
box.innerHTML = ++i
}
// 防抖函数 接受实参
function debounce(fn, time) {
// 1. 声明一个定时器变量
let timer
// 如果直接写一个定时器 在事件监听中由于debounce(mouseMove, 500)直接调用了函数 在页面一打开后会直接先执行debounce函数才进行事件监听 但此时debounce函数已执行完毕 默认有返回值 这时候再进行事件监听就不能执行debounce函数了
// 因此这里我们需要设置返回值为函数 这个函数就是用来执行2 3 4 步骤的
return function () {
// 2. 判断是否有定时器
if (timer) clearTimeout(timer)
// 3. 开启定时器
timer = setTimeout(function() {
// 4. 调用要执行的函数
fn()
}, time)
}
}
// 调用函数
box.addEventListener('mousemove', debounce(mouseMove, 500))
</script>
</body>
</html>
节流(throttle)
所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。简单来说,节流就是单位时间内,频繁触发事件,只执行一次。

如图所示,当事件触发后开始执行,如果执行过程中事件又再次被触发,当前执行的事件仍然不会被取消,而是继续执行直到完毕后才能接受事件触发。期间被触发的任务会被取消。
使用场景:
- 高频事件:鼠标移动
mousemove - 页面尺寸缩放
resize - 滚动条滚动
scroll等等
我们还是以上面鼠标移动加1的例子来解释:
利用节流来处理鼠标滑过盒子显示文字,要求:鼠标在盒子上移动,不管移动多少次,每隔500ms才+1.
还是有两个方法实现节流:
- lodash提供的节流函数来处理
- 手写一个节流函数来处理
lodash提供的节流函数
指路:lodash.throttle | Lodash中文文档 | Lodash中文网。
语法:_.throttle(func, [wait=0], [options=])
throttle 节流函数,在 wait 秒内最多执行 func 一次的函数。
该函数提供一个 cancel 方法取消延迟的函数调用以及 flush 方法立即调用。
可以提供一个 options 对象决定如何调用 func 方法, options.leading 与|或 options.trailing 决定 wait 前后如何触发。
func 会传入最后一次传入的参数给这个函数。 随后调用的函数返回是最后一次 func 调用的结果。
简单来说,就是在 wait 毫秒内,函数func仅能执行一次。
我们在代码中演示一下:
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>Document</title>
<style>
.box {
width: 500px;
height: 500px;
background-color: #ccc;
color: #fff;
text-align: center;
font-size: 100px;
}
</style>
</head>
<body>
<div class="box"></div>
<!-- 先引入 -->
<script src="./lodash.min.js"></script>
<script>
const box = document.querySelector('.box')
let i = 1
// 鼠标移动函数
function mouseMove() {
box.innerHTML = ++i
}
// 使用节流函数 鼠标在500ms内仅调用一次函数
box.addEventListener('mousemove', _.throttle(mouseMove, 500))
</script>
</body>
</html>
手写节流函数
核心思想 :节流的核心就是利用定时器(setTimeout)来实现
- 声明一个定时器变量
- 当鼠标每次滑动都先判断是否有定时器 了,如果有定时器则不开启新定时器
- 如果没有定时器则开启 定时器,记得存到变量里面
- 在定时器里面调用 要执行的函数,定时器里面要把定时器清空
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>Document</title>
<style>
.box {
width: 500px;
height: 500px;
background-color: #ccc;
color: #fff;
text-align: center;
font-size: 100px;
}
</style>
</head>
<body>
<div class="box"></div>
<script>
const box = document.querySelector('.box')
let i = 1
// 鼠标移动函数
function mouseMove() {
box.innerHTML = ++i
}
// 节流函数 鼠标在500ms内仅调用一次函数
function throttle(fn, time) {
// 1. 声明一个定时器变量
let timer = null
return function () {
// 2. 判断是否有定时器
if(!timer) {
timer = setTimeout(function(){
// 3. 调用要执行的函数
fn()
// 4. 把定时器清空
timer = null // 在 setTimeout 中无法使用 clearTimeout(timer)来清除 因为定时器还在运作
}, time)
}
}
}
// 函数调用
box.addEventListener('mousemove', throttle(mouseMove, 500))
</script>
</body>
</html>
最终效果实现:

总结
防抖和节流:
