JS 闭包

JS 闭包

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

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函数,试着写一下:java

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

写到这里发现,咱们没法访问autoadd,怎么解决:node

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();

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

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

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

咱们总结一下,上面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不少理解不到位的地方,望批评指正!

相关文章
相关标签/搜索