js高级篇:什么是闭包?闭包有什么用?面试时如何处理闭包问题?
须知:我们在学一个新的东西的时候,例如我们第一次见到电饭煲,我们要知道什么是电饭煲(定义),知道电饭煲有什么用(作用),知道电饭煲怎么用(用法),如何用电饭煲煮一顿好吃的饭(实践),做出来之后你就真正懂了电饭煲。学习闭包的过程亦是如此,所以我们不用害怕学习新事物!
闭包专题
1.闭包的定义
- 定义:闭包是指有权访问另一个函数作用域中的变量的一个函数。简单的说,你可以认为闭包是一个特别的函数,他能够读取其他函数内部变量的函数。
2.闭包的作用
- 作用:正常的函数,在执行完之后,函数里面声明的变量就会被垃圾回收处理掉。但是闭包可以让一个函数作用域中的变量,在执行完之后依旧没有被垃圾回收处理掉。
3.闭包的用法
闭包经典例子:
function a() { var name = '小明' return function () { console.log(name) } } var b = a() b() // 打印出:小明
注:闭包一般写成自调用函数的形式,以上的例子可以写成这样:
var b = (function () { var name = '小明' return function () { console.log(name) } })() b() // 打印出:小明
我们可以参照闭包的定义和闭包的作用来解析这个例子: 例子中的name变量是函数a的局部变量(就是只能在函数a中才能调用的变量),在
var b = a()这段代码执行完之后,就是a函数执行完之后,name变量应该被垃圾回收机制回收掉,但是在调用b方法之后,控制台打印出小明,说明name变量并没有回收。说明b函数就是一个闭包。
总结闭包的特征:
- 函数中嵌套函数
- 函数a的返回的是一个函数
- 函数a中返回的函数 调用 函数a中的声明的变量
自调用函数:顾名思义,就是函数自己调用自己。举个例子:
// 正常调用函数 function a() { console.log("haha") } a() // 自调用 (function () { console.log("haha") })()
自调用函数解析:我们可以把自调用想象成这种形式:(函数)()。第一个括号是为了把函数包裹起来,当作一个整体,第二个括号是为了调用函数。我们调用函数都是使用()来调用的。例如:a(),就是调用a函数。
4.闭包的应用
应用场景1:当我们需要把网页上的a标签都添加点击事件,点击a标签打印对应的下标。
html代码:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title></title> </head> <body> <a href="#">0</a> <a href="#">1</a> <a href="#">2</a> <a href="#">3</a> <a href="#">4</a> </body> </html>
可能有一部分人使用for循环来添加:
// 获取所有a标签 var a = document.querySelectorAll('a') // 循环添加点击事件 // a.length的值是:5 for (var i = 0; i < a.length; i++) { a[i].onclick = function () { console.log(i) } }
这时候运行起来,你发现无论你点击哪个标签,打印出来的都是:5
原因:因为for循环在页面加载完就执行完了,所以
i的值变成了5。当你点击的时候,点击的函数就回去一层一层的往上找,发现
i的值是5,就打印5了。
这时,我们就可以用闭包来解决这个bug:
var a = document.querySelectorAll('a') for (var i = 0; i < a.length; i++) { a[i].onclick = (function () { var tem = i return function () { console.log(tem) } })() }
解析:for循环每执行一遍的时候,自调用函数就会将对应的
i赋值给
tem,然后返回一个函数,返回的函数就是对应的点击事件函数。因为闭包的缘故,
tem在函数调用完之后不会被回收,所以能够被正确调用。
或者写成这种形式:
var a = document.querySelectorAll('a') for (var i = 0; i < a.length; i++) { a[i].onclick = (function (i) { return function () { console.log(i) } })(i) }
解析:这种形式,就是把
i以参数的形式传给自调用函数,就是省去声明
tem变量的步骤。
应用场景2:在一个页面实现多个计数器。
html代码:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title></title> </head> <body> <p id="text1">0</p> <button id="btn1">点击+1</button> <p id="text2">0</p> <button id="btn2">点击+1</button> </body> </html>
很多人选择这样的方式来实现:
var text1 = document.querySelector('#text1') var btn1 = document.querySelector('#btn1') var text2 = document.querySelector('#text2') var btn2 = document.querySelector('#btn2') var count1 = 0 var count2 = 0 btn1.addEventListener('click', function () { count1++ text1.innerHTML = count1 }) btn2.addEventListener('click', function () { count2++ text2.innerHTML = count2 })
如果使用闭包来实现,我们可以减少全局变量的使用,而且方便维护:
var text1 = document.querySelector('#text1') var btn1 = document.querySelector('#btn1') var text2 = document.querySelector('#text2') var btn2 = document.querySelector('#btn2') function addCount() { var count = 0 return function (text) { count++ text.innerHTML = count } } var add1 = addCount() //生成按钮1的计数器 btn2.onclick = function () { add2(text1) } var add2 = addCount() //生成按钮2的计数器 btn2.onclick = function () { add2(text2) }
5.经典面试题
经典闭包面试题:
for (var i = 0; i < 4; i++) { setTimeout(function() { console.log(i); }, 300); }
第一眼看到这个题的时候,我相信很多同学都会觉得打印出来的是:0,1,2,3。
但是这道题正确答案是:4,4,4,4。这道题跟我们上面的一个例子有点相似,但是这道题还涉及一个异步函数的知识点。
js代码执行顺序:没有异步任务时:代码从上往下执行。有异步任务是:代码先执行主线程,执行完之后才执行异步任务。setTimeout()是一个异步函数,所以这道题的代码执行顺序时,先执行for循环完,此时i
的值时4,再执行setTimeout种的方法。以至于打印出来的都是4。
使用闭包来让这道题打印出来的是:0,1,2,3
for (var i = 0; i < 4; i++) { setTimeout((function (i) { return function () { console.log(i) } })(i), 300); } // 或者写成这样 for (var i = 0; i < 4; i++) { setTimeout((function () { var tem = i return function () { console.log(tem) } })(), 300); }
由于for循环没啥变化,变化的是setTimeout的函数。我们来看一下setTimeout种的内容:因为setTimeout函数需要两个参数,第一个参数是一个函数,第二个是间隔时间。我们来看一下第一个参数:因为它是需要一个函数的类型,所以我们使用了自调用函数,让他自动执行完之后返回一个函数类型来给我们作为参数(注:自动执行函数是跟for循环一起执行的)。这里就是使用闭包的作用,延申了i
作用域,让他在自调用函数执行完之后没有被回收。
6.闭包的缺点
因为变量没有被回收,所以内存中一直存在,耗费内存。
- 在js文件中如何获取basePath处理js路径问题
- 如何通过js处理相同时间的信息整合到一起的问题
- 每日一问:面试结束时面试官问"你有什么问题需要问我呢",该如何回答?
- 问题集录--JS如何处理和解析Json数据
- nodeJS express mysql 高并发时连接数不够用问题 以及如何处理高并发
- asp.net高级反射,动态生成的bean如何处理赋值问题?
- 如何你是公司的HR,去招聘asp.net程序员,你会对前来面试的人问什么问题。
- 如何通过js处理相同时间的信息整合到一起的问题
- 在js文件中如何获取basePath处理js路径问题
- 【修真院web小课堂】js中的闭包是什么?用处如何?
- struts1.2原理:什么是struts,如何处理配置文件的大的问题?
- Nicholas C. Zakas(JS圣经:JavaScript高级程序设计作者)如何面试前端工程师
- 如何妥善处理WebBrowser对Javascript的错误问题,阻止JS弹出框,提高用户体验
- Android面试(二)ListView优化,ListView和Scrollview冲突问题,mvc模式,什么是ANR 如何避免它?
- js经典面试问题:如何让for循环中的setTimeout()函数像预想中一样工作?
- web前端人员到底如何处理浏览器兼容,到底什么是浏览器兼容问题
- 前端面试之JavaScript -- JS中如何检测一个变量是什么类型
- js经典面试问题:如何让for循环中的setTimeout()函数像预想中一样工作?
- 如何处理js的跨域问题
- 面试系列 -- 如何回答HR的 '你还有什么问题吗?'