function create() {
let res = []
for (var i = 0; i < 5; i++) {
res[i] = function() {
return i
}
}
return res
}
console.log(create()) // 结果: [4,4,4,4,4]
分析:
由于每个函数的作用域中都保存着同一个 函数的活动对象(create 函数), 所以他们引用的都是同一个变量 i.
如何解决这个问题?
我们可以使用匿名函数来创建一个闭包, 让闭包的行为来符合预期
function create() {
let res = []
for (var i = 0; i < 5; i++) {
res[i] = (function(num) {
return function() { return num }
})(i)
}
return res
}
console.log(create()) // 结果: [0,1,2,3,4]
上面将 create 函数进行了一个重写.
1.现在没有直接把闭包赋值给数组,而是再定义一个匿名函数, 将变量 i 当参数传进去由num来接收并立即执行.
2.在每次执行这个匿名函数时都会创建一个属于自己的 活动对象/作用域/变量对象.
3.然后再里面返回一个 只访问 num 的闭包函数, 这时取到的 num 都是独立的值
2.2 闭包的概念和特性
首先看个闭包的例子:
function makeFab () {
let last = 1, current = 1
return function inner() {
[current, last] = [current + last, current]
return last
}
}
let fab = makeFab()
console.log(fab()) // 1
console.log(fab()) // 2
console.log(fab()) // 3
console.log(fab()) // 5
分析:
makeFab的返回值就是一个闭包,makeFab像一个工厂函数,每次调用都会创建一个闭包函数,如例子中的fab。
注意:fab每次调用都不需要传参数,都会返回不同的值,因为在闭包生成的时候,它记住了变量last和current,以至于在后续的调用中能够返回不同的值。
PS:能够记住函数本身所在作用域的变量,这就是闭包和普通函数的区别所在
MDN中给出闭包的定义:函数与其状态即词法环境的引用共同构成闭包
这里的“词法环境的引用”,可以简单理解为“引用了函数外部的一些变量”,例如上述例子中每次调用makeFab都会创建并返回inner函数,引用了last和current两个变量。
例子1
以实现一个可复用的确认框为例,比如在用户进行一些删除或者重要操作时,为了防止失误操作,我们可能会通过弹窗让用户再次确认操作。
比如在触发弹窗中的确认/取消事件时异步操作,这时候我们就需要使用两个回调函数完成操作,弹窗函数confirm接受三个参数,一个时提示语,一个是确认回调函数,一个是取消回调函数:
function confirm (confirmText, confirmCallback, cancelCallback) {
// 插入提示框DOM,包含提示语句、确认按钮、取消按钮
// 添加确认按钮点击事件,事件函数中做dom清理工作并调用confirmCallback
// 添加取消按钮点击事件,事件函数中做dom清理工作并调用cancelCallback
}
之前我还一直想不通,原来是闭包在作祟!!!我服了!!!
因为使用闭包,confirm中则包含两个回调函数,那么我们就可以通过confirm传递回调函数,并且根据不同的结果完成不同的动作,比如我们根据id删除一条数据可以这样这:
function removeItem(id){
confirm(‘确认删除吗?’,()=>{
//用户点击确认,发送远程ajax请求
api.removeItem(id).then(xxx)
},
()=>{
//用户点击取消
console.log('取消删除‘)
}
}
解析:在这个例子中,confirm 的回调函数正式利用了闭包,创建了一个引用上下文id变量的函数!
例子2使用闭包实现防抖、节流函数
前端很常见的一个需求就是远程搜索,根据用户输入框的内容自动发送ajax请求,然后从后端把搜索结果请求回来。
为了简化用户的操作,有时候我们不会专门放置一个按钮来点击触发搜索事件,而是直接监听内容的变化来搜索(比如微信小程序中根据关键字搜索商品)
这时候为了避免请求过于频繁,我们可以利用”防抖“技术,即当用户停止输入一段事件时(比如500ms)后才执行
- 可以使用如下代码实现
分析:dobounce函数每次调用时,都会创建一个新的闭包函数,该函数保留了对事件逻辑处理函数func以及防抖时间间隔time以及定时器标志timer的引用
例子3—使用闭包实现节流函数
function throttle(func,time){
var lastTime=0;
return function(){
var nowTime=new Date();
if(nowTime-lastTime>time){
func.call(this);
lastTime=nowTime;//由于闭包的作用原理,这里调用外层函数的局部变量,每次调用这个lastTime变量都会发生改变
}
}
}
例子4–使用闭包解决按钮多次连续点击问题
问题:
用户点击一个表单提交按钮时,前端会向后端发送异步请求,请求还没有返回,用户焦急又多点了即此按钮,这样带来的后果就是:①消耗服务器资源②修改了后台的数据
解决:通常办法是定义一个标记变量,即在响应函数所在的作用域声明一个布尔变量lock,响应函数被调用时,先判断lock的值,为true则表示上一次请求还没有返回来,此时点击无效;为false则将lock设置为true,然后发送请求,请求结束后将lock改为fasle
很显然,lock变量会污染函数所在的作用域,而生成闭包伴随着新的函数作用域的创建,利用这一点,刚好可以解决这个问题,下面是简单的例子:
let clickButton = (function () {
let lock = false;
//postParams为发送请求时需要的参数
return function (postParams) {
if (lock) return
lock = true
//使用axios发送请求
axios.post(‘urlxxxxx’, postParams).then(
).catch(error => {
//请求失败
console.log(err)
}).finnally(() => {
//不管失败还是成功,都解锁
lock = false
})
}
})()
说明:这样lock变量就会在一个单独的作用域里,一次点击请求发出以后,必须等请求回来,才会开始下一次请求。
当然,为了避免各个地方都声明lock,修改lock,我们可以把上述逻辑抽象以下,实现一个装饰器,就像节流/防抖函数一样。
最后
你要问前端开发难不难,我就得说计算机领域里常说的一句话,这句话就是『难的不会,会的不难』,对于不熟悉某领域技术的人来说,因为不了解所以产生神秘感,神秘感就会让人感觉很难,也就是『难的不会』;当学会这项技术之后,知道什么什么技术能做到什么做不到,只是做起来花多少时间的问题而已,没啥难的,所以就是『会的不难』。
我特地针对初学者整理一套前端学习资料
说明:这样lock变量就会在一个单独的作用域里,一次点击请求发出以后,必须等请求回来,才会开始下一次请求。
当然,为了避免各个地方都声明lock,修改lock,我们可以把上述逻辑抽象以下,实现一个装饰器,就像节流/防抖函数一样。
最后
你要问前端开发难不难,我就得说计算机领域里常说的一句话,这句话就是『难的不会,会的不难』,对于不熟悉某领域技术的人来说,因为不了解所以产生神秘感,神秘感就会让人感觉很难,也就是『难的不会』;当学会这项技术之后,知道什么什么技术能做到什么做不到,只是做起来花多少时间的问题而已,没啥难的,所以就是『会的不难』。
我特地针对初学者整理一套前端学习资料
[外链图片转存中…(img-WaDqcE2o-1718714276110)]