闭包
对闭包的理解:
闭包是就是函数中的函数,里面的函数可以访问外面函数的变量,外面的变量的是这个内部函数的一部分。
闭包是指有权访问另一函数作用域中的变量的函数。创建闭包的方式是在一个函数内部创建另一个函数。
闭包的作用
1、封装细节
2.使用闭包可以访问函数中的变量。减少代码量
3.可以使变量长期保存在内存中,生命周期比较长。
如何从外部读取局部变量?
我们有时候需要得到函数内的局部变量,但是在正常情况下,这是不能读取到的,只有通过变通方法才能读取到。
例:
function count() { var arr = []; for (var i=1; i<=3; i++) { arr.push(function () { return i * i; }); } return arr; } var results = count(); var f1 = results[0]; var f2 = results[1]; var f3 = results[2]; f1(); f2(); f3();
需要注意的是:返回的函数并没有立刻执行,直到调用f()才执行。
结果全部都是16,原因在于返回的函数引用了变量i,但它并非立刻执行,而是调用完f()才执行,等到三个函数都返回,他们所引用的的变量i已经变成了4,因此最终结果为16.
返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
function count() { var arr = []; for (var i=1; i<=3; i++) { arr.push((function (n) { return function () { return n * n; } })(i)); } return arr; } var results = count(); var f1 = results[0]; var f2 = results[1]; var f3 = results[2]; f1(); // 1 f2(); // 4 f3(); // 9
闭包的应用场景
1.使用闭包代替全局变量
2.函数外或在其他函数中访问某一函数内部的参数
3.在函数执行之前为要执行的函数提供具体参数(setTimeOut)
4.为节点循环绑定click事件,在事件函数中使用当次循环的值或节点,而不是最后一次循环的值或节点(循环赋值)
5.封装相关功能
1.使用闭包代替全局变量
全局变量有变量污染和变量安全等问题。
2.函数外或在其他函数中访问某一函数内部的参数
为了解决在Ajax callback回调函数中经常需要继续使用主调函数的某一些参数。
3.在函数执行之前为要执行的函数提供具体参数(setTimeOut)
某些情况下,是无法为要执行的函数提供参数,只能在函数执行之前,提前提供参数。(如f1())
4.为节点循环绑定click事件,在事件函数中使用当次循环的值或节点,而不是最后一次循环的值或节点(循环赋值)
使用闭包注意点:
(1)由于闭包会使得函数中的变量都保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成页面的性能问题,在IE中可能会导致内存泄漏。解决方法是,在退出函数之前,将不使用的局部变量全部删除。将该变量置为null。
经典闭包面试题
function fun(n,o){ console.log(o); return { fun:function(m){ return fun(m,n); } } } var f1=fun(0);//undefined f1.fun(1);//0 f1.fun(2);//0 f1.fun(3);//0 var f2=fun(0).fun(1).fun(2).fun(3);//undefined,0,1,2 var f3=fun(0).fun(1);//undefined,0 f3.fun(2);//1 f3.fun(3);//1
解析:
(1)f1=fun(0) 执行外层的fun()函数,因为有两个参数,所以n=0,o=undefined; f1此时的返回值为一个对象,对象中包含fun属性,而这个fun属性是一个匿名函数,这个匿名函数会返回一个fun函数
f1.fun(1)执行f1返回对象的fun函数,此时m=1,返回的内层的fun(m,n),此时执行函数体,n的值向外层寻找为0;
f1.fun(2)执行f1返回对象的fun函数,此时m=2,返回的内层的fun(m,n),此时执行函数体,n的值向外层寻找为0;
(2)f2=fun(0)执行外层的fun()函数,因为有两个参数,所以n=0,o=undefined; f1此时的返回值为一个对象,对象中包含fun属性
fun(0).fun(1)会调用前者返回的对象里的fun属性,并传入1作为第一个参数,执行返回的fun函数fun(m,n)=fun(1,0)所以console.log(o)=0
fun(0).fun(1).fun(2) 这里使用的闭包是.fun(1)返回的闭包,因为每次执行fun()都会返回一个新的对象,而.fun(2)引用的是fun(1),所以n的值被保留为1
问题:实现一个暴露内部变量,而且外部可以访问修改的函数(get和set,闭包实现)
var person=(function(){ var name="xiaoan"; return { getName:function(){ return name; }, setName:function(newValue){ name=newValue; return name; } } })(); console.log(person.name);//直接访问访问不到 console.log(person.getName());//访问变量 console.log(person.setName('liming'));//修改变量
匿名函数的调用方式:使用()将匿名函数括起来,然后在后面加一对小括号。表示立即执行该匿名函数。
问题:setTimeout使用闭包,实现定时输出5,4,3,2,1
for(let i = 5; i >0; i-- ){ setTimeout(function(){ console.log(i) },1000) }
//闭包 for(var i = 5; i >0; i-- ){ setTimeout((function(num){ return function(){ console.log(num) } }(i)),1000) }
这种方式虽然是依次打印了5,4,3,2,1,但是却是1s同时输出的,执行时间没有起作用,为什么??
从闭包中接收传过来的参数i,然后setTimeout异步调用,进入异步队列,循环代码很快就执行完了,在1s后,从异步队里中返回执行后的结果,依次输出5,4,3,2,1
即首先执行同步任务将for循环结束,然后执行异步中的setTimeout(),由于是同时计时的,所以全部都是1s输出。
如何每隔1s依次打印一个结果?(设置延迟时间依次增加1s即可)
for(var i = 5; i >0; i-- ){ setTimeout((function(num){ return function(){ console.log(num) } }(i)),1000*(5-i)) }