JavaScript--我所理解的闭包

关于闭包大家肯定不陌生, 刚知道闭包这个特性那会真的被折磨, 现在找个时间把这些都记下来, 希望能帮新人理解和记忆.

-------------------------------------------------------------------------------------------

闭包的格式有很多种, 但基本都是将一个嵌套函数中的内层函数返回到外层函数外面, 使外层函数体外也能访问和操作外层函数中"封住"的局部变量, 与此同时, 外层函数中的变量不会被销毁, 会"长久记忆".

最常见的闭包:

function outer(){
    var i = 100;              //保存于外层函数体内的局部变量
    return function(){     //用于返回内部函数
          console.log(i);   //内部函数体中对 "它" 自己的上下文中的变量进行了操作
    }
}
    var inner = outer();
    inner();    

闭包的形式知道了, 重点是怎么调用, 关于调用的方式有很多:

 

第一种:

function outer(){
    var i = 100;              
    return function(){
          i++;          
          console.log(i);   
    }
}
    var inner1 = outer();
    var inner2 = outer();
    inner1();  //输出结果为 101;
    inner2();  //输出结果还是 101;

这是为何呢? 原因是  当 外层函数每执行一次, 就会创建新的闭包, 即每次外层函数的执行都开辟新的内存空间, 虽然 inner1 和 inner2 都是函数的引用, 但每次返回的函数都是"新的", 所以 inner1 是 inner1, inner2 是 inner2.

 

第二种

function outer(){
    var i = 100;              
    return function(){
          i++;          
          console.log(i);   
    }
}
    var inner1 = outer();
    var inner2 = inner1;
    inner1(); //输出 101
    inner2(); //输出 102

这种调用方式和第一种相比就很好理解了, 因为 inner1 和 inner2 都是"同一个函数"的引用, 所以出现 101 和 102 也就通了.

 

第三种

function fo(){
    var i = 0;
    return function(n){
    return n + i++;
    }
}

var f = fo();      //现在变量f是一个函数的引用
var a = f(15);     //输出 15, i此时的值是0, 不是undefind, 闭包能够记住自己认识变量i
var b = fo()(15);  //输出 15, fo()会返回新的内层函数, 也就是说产生了新的闭包(对比第一种理解), i已经被清零
var c = fo()(20);  //输出 20, 同理
var d = f(20);    //输出 21

第三种函数做了稍稍改变, 内层函数中要传入形式参数, 其实道理是一样的.

 

第四种

function fo(m){
    return function(n){
    return m + n;
    }
}

var f = fo(1);      //变量 f 是一个内层函数的引用, 此时 f 已"记住" f 自己中要调用的 m 的值为 1
var a = f(2);       //输出 3, 因为闭包的存在, 所以在 f(2) 执行时, f 内部的 m 是 1
var b = fo(10)(1);  //输出 11, 每当外层函数 fo 执行一次, 都会产生新的闭包并返回一个函数
var c = fo(10)(2);  //输出 12。同理。
var d = f(3);       // 和 f(2) 同理.

 

第五种

其实第五种来说, 不单单是闭包的影响, 还有就是关于"异步"的一些概念, 就是浏览器给元素添加监听是要消耗时间的, 所以在给一个类数组中的元素通过循环添加监听是不能直接添加的.下面是 html 结构.

    <ul>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
    </ul>

下面是对应的 JS 代码:


var lis = document.getElementsByTagName("li");
for(var i = 0, length = lis.length; i < length; i++){
  lis[i].onclick = function (){
    console.log("我被点击了, 我是第" + i);
  };
}

初学者可以试试, 会发现每个 li 触发点击后再控制台输出的都是 "我被点击了, 我是第 5", 原因是 当 浏览器给 元素添加监听时, 不会阻塞程序的执行, 就是说在给 lis[0] 添加事件监听时, for 循环还在走, i 还在 ++.

解决方法最好用的就是 IIFE(即立即执行函数):

var lis = document.getElementsByTagName("li");
for(var i = 0, length = lis.length; i < length; i++){
    lis[i].onclick = (function(i){
                return function (){
                    console.log("我被点击了, 我是第" + i);
                                };
            })(i);
}        

外层函数是立即执行的, 并且把 i 当做参数传入函数体, 因此返回的内层函数就会拿到"合适的" i 值.

 

以上是我对闭包的一些理解, 在此抛砖引玉, 有误导之处还请不吝指点 ^^.

posted @ 2016-11-04 18:35  _但行好事  阅读(278)  评论(0编辑  收藏  举报