闭包
闭包 是指一个函数可以记住其外部变量并可以访问这些变量。
闭包有两个常用的用途;
- 在函数外部能够访问到函数内部的变量。通过使用闭包,可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量。
- 使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收。
比如,函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包。
javascript
function A() {
let a = 1
window.B = function () {
console.log(a)
}
}
A()
B() // 1
在 JS 中,闭包存在的意义就是让我们可以间接访问函数内部的变量。 经典面试题:循环中使用闭包解决 var 定义函数的问题:
javascript
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}
首先因为 setTimeout 是个异步函数,所以会先把循环全部执行完毕,这时候 i 就是 6 了,所以会输出一堆 6。解决办法有三种:
- 第一种是使用闭包的方式
javascript
for (var i = 1; i <= 5; i++) {
;(function (j) {
setTimeout(function timer() {
console.log(j)
}, j * 1000)
})(i)
}
在上述代码中,首先使用了立即执行函数将 i 传入函数内部,这个时候值就被固定在了参数 j 上面不会改变,当下次执行 timer 这个闭包的时候,就可以使用外部函数的变量 j,从而达到目的。
- 第二种就是使用 setTimeout 的第三个参数,这个参数会被当成 timer 函数的参数传入。
javascript
for (var i = 1; i <= 5; i++) {
setTimeout(
function timer(j) {
console.log(j)
},
i * 1000,
i
)
}
- 第三种就是使用 let 定义 i 了来解决问题了,这个也是最为推荐的方式
javascript
for (let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}
防抖与节流
闭包的常见应用场景就是防抖与节流。
防抖
函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。
常见的使用场景有按钮提交、搜索联想词功能。
基础代码实现:
javascript
function debounce(fn, wait) {
let timer = null
return function (...args) {
const ctx = this
// 如果此时存在定时器的话,则取消之前的定时器重新记时
if (timer) {
clearTimeout(timer)
timer = null
}
// 设置定时器,使事件间隔指定事件后执行
timer = setTimeout(() => {
fn.apply(ctx, args)
}, wait)
}
}
节流
函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。
常见的使用场景有:监控浏览器 resize、拖拽功能
基础代码实现:
javascript
// 时间戳版
function throttle(fn, delay) {
var preTime = Date.now()
return function () {
var context = this,
args = [...arguments],
nowTime = Date.now()
// 如果两次时间间隔超过了指定时间,则执行函数。
if (nowTime - preTime >= delay) {
preTime = Date.now()
return fn.apply(context, args)
}
}
}
// 定时器版
function throttle(fun, wait) {
let timeout = null
return function () {
let context = this
let args = [...arguments]
if (!timeout) {
timeout = setTimeout(() => {
fun.apply(context, args)
timeout = null
}, wait)
}
}
}