资讯专栏INFORMATION COLUMN

JS 闭包

sihai / 1726人阅读

摘要:你可能经常看到这句话创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量。这种现象称之为闭包。虽然中没有类这样的机制,但是通过使用闭包,我们可以模拟出这样的机制。

JS 闭包

JS编程的时候你一定遇到过这个问题:局部变量实现累加,看下面例子:

function aotuadd(){
    var a=1;
    a++;
    console.log(a);
}
aotuadd();//2
autuadd();//2

上面的代码无法实现累加,这时可能有的人就会选择把a放在全局作用域中,能实现累加功能,但是会使全局变量增多,这是我们不想看到的。
其实之所以把a放在全局作用域中,是因为autoadd函数的作用域被全局作用域包裹,所以我们可以在全局作用域中取值;
那么我们是不是可以给autoadd外层再包裹一个作用域(假设是wapper),然后将这个a放在wapper作用域中,问题不就解决了嘛。
我们既能访问wrapper中的a,又不必增加全局变量。因为js中只有函数能够产生作用域,所以其实就是再aotoadd外包裹一个wrapper函数,试着写一下:

function wrapper(){
    var a=1;
    function autoadd(){
        a++;
        console.log(a);
    }
}
wrapper();

写到这里发现,我们无法访问autoadd,怎么解决:

function wrapper(){
    var a=1;
    function autoadd(){
        a++;
        console.log(a);
    }
    window.bar=autoadd;
}
wrapper()
bar();//2
bar();//3

上面这种方法是能够解决无法调用的问题的,但是这回到了我们最开始遇到的问题,增加了全局变量/函数,这是我们不想看到的;另一种解决方法:

function wrapper(){
    var a=1;
    function autoadd(){//必要条件
        a++;//必要条件
        console.log(a);
    }
    return autoadd;
}
var x=wrapper()//返回一个函数,巧合的是,返回的这个函数体中,还有一个变量a要引用wrapper作用域下的a,所以这个a不能销毁,wrapper()上下文环境不被销毁,依然存在于执行上下文栈中;
x();
x();

通过返回函数的方法进行调用,上面的这种写法就是我们最常见的闭包的写法,也就是说,闭包的产生,其实并不是一定依赖于“返回函数”这个条件,只不过不通过这种方法调用有违初衷;

看懂了上面这个例子,闭包的概念也呼之欲出:闭包是指有权访问另一个函数作用域中的变量的函数,即当前作用域总能访问外部作用域中的变量。
你可能经常看到这句话:“创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量”。其实我觉得恰恰就是这句话,导致很多人无法理解闭包,换成下面这种说法更好理解:创建闭包的最常见的方式是在一个函数外部包裹另一个函数,通过在另一个函数内部定义变量的方式,使我们想要的变量驻留在外层函数中,减少全局变量。

闭包的两个必要条件:函数外层有函数/ 内层函数要使用外层函数中的变量

我们总结一下,上面var x=wrapper()和function warpper()可以利用立即执行函数合写,进一步减少全局变量:

var x=(function(){
        var a=1;
        return function(){
            a++;
            console.log(a);
        }
})();
x();//2
x();//3
x=null;//解除引用,等待垃圾回收

或者:

function Myobj(){
    var age=1;;
    this.autoadd=function(){
        age++;
        console.log(age);
        //return age;
    }
}
var obj=new Myobj();
obj.autoadd();
obj.autoadd();

另一个常见问题:
for循环给网页中一连串元素绑定,例如onclick事件:

var fn = function() {
        var divs = document.querySelectorAll("div");
        for (var i = 0; i < 3; i++) {
                divs[i].onclick = function() {
                        alert(i);
                };
        }
};
fn();

点击每个div都会弹出3。这是为什么呢?
我们先来分析一下原因:onclick事件是一个异步回调函数的指针,并不会立即执行,上面的函数表达式,并不会进行变量赋值。只有在调用一个函数时,一个新的执行上下文才会被创建出来。那么我们是不是可以通过调用函数的方法,来创建多个新的执行上下文环境,创建新的作用域,这样不同的调用就可以有不同的参数。那么解决思路也是外层包裹function的方法,即利用闭包:

var fn = function() {
        var divs = document.querySelectorAll("div");
        for (var i = 0; i < 3; i++) {
                divs[i].onclick = (function(a) {//立即执行函数,创建新的执行上下文
                        alert(a);
                })(i);
        }
};
fn();

或者:

var fn = function() {
        var divs = document.querySelectorAll("div");
        for (var i = 0; i < 3; i++) {
                (function(i){//立即执行函数,创建新的执行上下文,将i驻留在内存中
                    divs[i].onclick = function() {
                        alert(i);
                })(i);
        }
};
fn();

另一种解决方法:(利用事件代理)

var ul=document.querySelector("ul");
var lis=ul.querySelectorAll("ul li");
ul.addEventListener("click", function (e) {
    var target= e.target;
    if(target.nodeName.toUpperCase()==="LI"){
        alert([].indexOf.call(lis,target));
    }
},false)

理解闭包的关键就是下面这句:
闭包:当一个函数在定义它的作用域以外的地方被调用时,它访问的依然是定义它时的作用域。这种现象称之为闭包。
JavaScript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里。——《JavaScript语言精粹》

闭包的优点:
1)使变量驻留在内存中(多了变缺点);
2)避免全局变量污染;
3)私有化变量;
闭包的缺点:
1)因为闭包会携带包含它的函数的作用域,所以比其他函数占用更多内存;
2)使用不当会造成内存泄漏;

闭包应用场景(来自《javascript高级程序设计》)
1.使用闭包可以在JS中模拟块级作用域(ECMAScript6标准之前的JavaScript本身没有块级作用域的概念);

function outputNumbers(count){
      (function(){
               for(var i = 0; i < count; i++){
               alert(i);
               }
      })();
  alert(i); //导致一个错误!
}

2.闭包可以用于在对象中创建私有变量;

// 1.2.闭包可以用于在对象中创建私有变量

  function MyObject(){
    // 私有变量和私有函数
    var privateVariable = 10;
    function privateFunction(){
      return false;
    }
    // 特权方法,调用私有方法、函数
    this.publicMethod = function(){
      privateVariable++;
      return privateFunction();
    }
  }

闭包的运用
1.匿名自执行函数
我们在实际情况下经常遇到这样一种情况,即有的函数只需要执行一次,其内部变量无需维护,比如UI的初始化,那么我们可以使用闭包:

//将全部li字体变为红色
(function(){    
    var els = document.getElementsByTagName("li");
    for(var i = 0,lng = els.length;i < lng;i++){
        els[i].style.color = "red";
    }    
})();  

我们创建了一个匿名的函数,并立即执行它,由于外部无法引用它内部的变量,
因此els,i,lng这些局部变量在执行完后很快就会被释放,节省内存!
关键是这种机制不会污染全局对象。
2. 实现封装/模块化代码

var person= function(){    
    //变量作用域为函数内部,外部无法访问    
    var name = "default";       
return {    
       getName : function(){    
           return name;    
       },    
       setName : function(newName){    
           name = newName;    
       }    
    }    
}();
console.log(person.name);//直接访问,结果为undefined    
console.log(person.getName());  //default 
person.setName("jozo");    
console.log(person.getName());  //jozo

3. 实现面向对象中的对象
这样不同的对象(类的实例)拥有独立的成员及状态,互不干涉。虽然JavaScript中没有类这样的机制,但是通过使用闭包,
我们可以模拟出这样的机制。还是以上边的例子来讲:

function Person(){    
    var name = "default";       
return {    
       getName : function(){    
           return name;    
       },    
       setName : function(newName){    
           name = newName;    
       }    
    }    
};    
var person1= Person();    
print(person1.getName());    
john.setName("person1");    
print(person1.getName());  // person1  
var person2= Person();    
print(person2.getName());    
jack.setName("erson2");    
print(erson2.getName());  //person2

Person的两个实例person1 和 person2 互不干扰!因为这两个实例对name这个成员的访问是独立的 。

初学js很多理解不到位的地方,望批评指正!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/92541.html

相关文章

  • JS 中的闭包是什么?

    摘要:大名鼎鼎的闭包面试必问。闭包的作用是什么。看到闭包在哪了吗闭包到底是什么五年前,我也被这个问题困扰,于是去搜了并总结下来。关于闭包的谣言闭包会造成内存泄露错。闭包里面的变量明明就是我们需要的变量,凭什么说是内存泄露这个谣言是如何来的因为。 本文为饥人谷讲师方方原创文章,首发于 前端学习指南。 大名鼎鼎的闭包!面试必问。请用自己的话简述 什么是「闭包」。 「闭包」的作用是什么。 首先...

    Enlightenment 评论0 收藏0
  • js闭包的本质

    摘要:也正因为这个闭包的特性,闭包函数可以让父函数的数据一直驻留在内存中保存,从而这也是后来模块化的基础。只有闭包函数,可以让它的父函数作用域永恒,像全局作用域,一直在内存中存在。的本质就是如此,每个模块文件就是一个大闭包。 为什么会有闭包 js之所以会有闭包,是因为js不同于其他规范的语言,js允许一个函数中再嵌套子函数,正是因为这种允许函数嵌套,导致js出现了所谓闭包。 function...

    qianfeng 评论0 收藏0
  • 谈谈我所理解的闭包js、php、golang里的closure

    摘要:当初看这个解释有点懵逼,理解成闭包就是函数中的函数了。里的闭包最近不满足于只干前端的活,开始用起了。里的闭包最近在学习语言,让我们来看一下语言里的闭包。在中,闭包特指将函数作为值返回的情况,被返回的函数引用了生成它的母函数中的变量。 本人开始接触编程是从js开始的,当时网上很多人说闭包是难点,各种地方对闭包的解释也是千奇百怪。如今开始接触js以外的各种编程语言,发现不光是js,php、...

    betacat 评论0 收藏0
  • 谈谈我所理解的闭包js、php、golang里的closure

    摘要:当初看这个解释有点懵逼,理解成闭包就是函数中的函数了。里的闭包最近不满足于只干前端的活,开始用起了。里的闭包最近在学习语言,让我们来看一下语言里的闭包。在中,闭包特指将函数作为值返回的情况,被返回的函数引用了生成它的母函数中的变量。 本人开始接触编程是从js开始的,当时网上很多人说闭包是难点,各种地方对闭包的解释也是千奇百怪。如今开始接触js以外的各种编程语言,发现不光是js,php、...

    zhoutao 评论0 收藏0
  • 详解js闭包

    摘要:但闭包的情况不同嵌套函数的闭包执行后,,然后还在被回收闭包会使变量始终保存在内存中,如果不当使用会增大内存消耗。每个函数,不论多深,都可以认为是全局的子作用域,可以理解为闭包。 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。 闭包的特性 闭包有三个特性: 1.函数嵌套函数 2.函数内部可以引用外部的参数和变量 3.参数和变量不会...

    Chiclaim 评论0 收藏0
  • JS脚丫系列】重温闭包

    摘要:内部的称为内部函数或闭包函数。过度使用闭包会导致性能下降。,闭包函数分为定义时,和运行时。循环会先运行完毕,此时,闭包函数并没有运行。闭包只能取得外部函数中的最后一个值。事件绑定种的匿名函数也是闭包函数。而对象中的闭包函数,指向。 闭包概念解释: 闭包(也叫词法闭包或者函数闭包)。 在一个函数parent内声明另一个函数child,形成了嵌套。函数child使用了函数parent的参数...

    MartinDai 评论0 收藏0

发表评论

0条评论

sihai

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<