闭包

对闭包的理解:

闭包是就是函数中的函数,里面的函数可以访问外面函数的变量,外面的变量的是这个内部函数的一部分。

闭包是指有权访问另一函数作用域中的变量的函数。创建闭包的方式是在一个函数内部创建另一个函数。


闭包的作用

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))
}

 

posted @ 2018-05-24 10:51  安xiao曦  阅读(265)  评论(0编辑  收藏  举报