小米技术社区
小米技术社区管理员 关于小米

27岁,山西运城人,职业电商经理人,前端开发工作者,从事过网站建设、网络推广、SEO、SEM、信息流推广、二类电商、网络运维、软件开发,等相关电商工作,经验较为丰富,小米技术社区致力于为广大从事Web前端开发的人员提供一些力所能及的引导和帮助 ...[更多]

E-mail:mzze@163.com

Q Q:32362389

W X:xiaomi168527

小米技术社区大牛王飞 关于王飞

27岁,山西运城人,职业电商经理人,网络工程师兼运维,从事过运营商网络建设,企业网络建设、优化。数据中心网络维护等通过,经验丰富,座右铭:当自己休息的时候,别忘了别人还在奔跑。 ...[更多]

E-mail:wf_live@126.com

Q Q:3940019

微博:王小贱ss

小米技术社区设计小艳 关于小艳

大于花一样的年龄,河南郑州是我家,2010年在北京接触团购网,2011年进入天猫淘宝一待就是四年,如今已经将设计走向国际化(ps:误打误撞开始进入阿里巴巴国际站的设计,嘿嘿)五年电商设计,丰富经验,从事过天猫淘宝阿里各项设计,店铺运营,产品拍摄;我将我的经历与您分享是我的快乐!座右铭:越努力越幸运! ...[更多]

E-mail:97157726@qq.com

Q Q:97157726

标签云
精品推荐
  • 什么是闭包?js闭包的2个最大用途以及注意事项

    什么是闭包?js闭包的2个最大用途以及注意事项

    各种专业文献上的"闭包"(closure)定义非常抽象,很难看懂。简单的说,闭包就是能够读取其他函数内部变量的函数。由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因……
    199人已经看过了
您的位置:首页 > 前端开发 > Javascript > 原生JS

JavaScript闭包的概念_特征_缺点_作用以及用闭包解决for循环i变量丢失问题

分类: 原生JS49个赞

闭包就是能够读取其他函数内部变量的函数。


闭包(closure)是 Javascript 语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。

理解闭包,首先必须理解变量作用域。

更透彻的了解闭包,我们先来了解作用域和作用域链。

//局部变量  函数内部var声明的变量,函数外部不能使用

    function fn(){
        var a = 100;
        console.log(a);
    }
    fn()
    console.log(a);


//全局变量  函数外面声明的变量,函数内部可以使用

    var b = 200;
    function f1(){
        console.log(b);
    }
    f1();
    console.log(b);


//作用域链

    var c = 100;
    function f2(){
            //var c = 200;
        function f3(){
            // var c = 300;
            console.log(c);
        }
        f3();
    }
    f2(); //结果c=100


了解更多:JavaScript的作用域以及作用域链

通过作用域和作用域链,我们知道函数内部可以使用函数外部的变量;但函数外面不能使用函数内部的变量。

那么如何让函数外面使用函数内部的变量呢?

正常情况下,这是办不到的,只有通过变通方法才能实现。

那就是在函数的内部,再定义一个函数。


function f1(){
        var num = 100;
        function f2(){
            console.log(num);
        }
        return f2;
    }
    var res = f1();     // res = f2;  将f2函数 返回到 f1外面
    res(); // f1函数外面调用f2函数,使用到了f1函数内部的变量,这种方式就是写了一个“闭包”


原理:这就是JavaScript语言特有的”链式作用域”结构(chain scope),子级会一层一层地向上寻找所有父级的变量。

所以,父级的所有变量,对子级都是可见的,反之则不成立。

既然f2可以读取f1的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!


闭包的定义:

闭包就是能够读取其他函数内部变量的函数。

让函数通过嵌套的方式使外部的函数可以使用内部的变量这种方式叫做闭包;闭包其实就是一个内部函数,或者说闭包是一个内部函数以及函数所处的作用域环境.

在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。



闭包典型特征:

(1)两个函数嵌套

(2)内部函数被外部函数返回了.(闭包能够使 函数外部使用函数内部的变量)

(3)闭包能够使函数内部的变量,常驻内存(数据始终保存在内存中,不会被销毁)) -- 算是缺点

闭包缺点:

由于内部数据不会被自动销毁,占用内存。所以一般不要使用太多闭包

占用内存原理:外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。

因此不能滥用闭包,否则会造成网页的性能问题。


闭包的作用:

闭包的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中

即闭包可以使得它诞生环境一直存在;


学会JavaScript闭包

闭包和匿名函数的关系


闭包可以使用匿名函数的写法

    var res = (function(){
        var num = 300;
        return function(){
            console.log(num);
        };
    })();
    res();   //结果300  res 就是内部的函数


闭包的结果取决于什么?

特殊写法:闭包的结果取决于函数定义的位置;即函数本身所处的作用域;和函数调用的位置无关


例1

var num = 100;
    var f = function(){
        console.log(num);
    };

    function f1(){
        var num = 200;
        return f;
    }
    var res = f1(); // res 就是 f函数
    res(); //结果为100


例2

var num = 100;
    var f = function(){
        console.log(num);
    };

    function f1(){
        var num = 200;
        f();
    }
    f1(); //结果为100


使用闭包解决for循环绑定事件中i丢失问题


案例:我是第 几 个li标签(默认 有4个li)

var lis = document.querySelectorAll('li');
    for(var i=0; i<lis.length; i++){
        lis[i].onclick = function(){
            //输出 我是第 几 个li标签
            console.log('我是第 ' + i + ' 个li标签');
        }
    }

结果:不管点第几个li,都是显示5(li.length+1)的结果

原因:事件函数,是在循环结束之后才出发,循环结束,全局变量i已经是5(li.length+1);事件函数中使用全局变量i,始终是5


通过匿名函数+闭包的方式解决循环事件中i丢失问题

var lis = document.querySelectorAll('li');
    for(var i=0; i<lis.length; i++){
        //套一层匿名函数自调用,形成了一个闭包环境
        //主要作用是形成一个局部作用域
        (function(m){
            lis[m].onclick = function(){
                //输出 我是第 几 个li标签
                console.log('我是第 ' + m + ' 个li标签');
            }
        })(i);

//循环5次;每次都产生一个局部作用域,局部变量m的值都不一样,互相不影响

//局部变量和全局变量 互相不影响 不同作用域的局部变量 互相不影响


闭包缺点:由于内部数据不会被自动销毁,占用内存。


例1:普通函数num++

function fn(){

        var num = 100;

        console.log(num);

        num++;

    }

    fn();//100  //第一次执行后函数体内的101就被销毁了

    fn();//100


例2:闭包num++

function f1(){
        var num = 100;
        return function(){
            console.log(num);
            num++;
        };
    }
    var res = f1(); // res就是返回的那个函数
    res(); // 100  num = 101   //闭包第一次执行后101未被销毁
    res(); // 101  num = 102
    res(); // 102  num = 103


关于JS垃圾回收机制的问题

上面例子中为什么普通函数的num++执行后就被销毁了,而闭包的没有被销毁,这和JS垃圾回收机制的问题有关系.


当我们在函数内部引入一个变量或函数时,系统都会开辟一块内存空间;还会将这块内存的引用计数器进行初始化,初始化值为0,如果外部有全局变量或程序引用了这块空间,则引用计数器会自动进行+1操作,当函数执行完毕后,变量计数器重新归零,系统会运行垃圾回收,将函数运行产生的数据销毁;如计数器不是 0 ,则不会清除数据;


这个过程就称之为 "JS的垃圾回收机制" ;



闭包其他案例:

闭包其他案例1:缓存随机数

需求:获取随机数,随机数一旦生成,会在程序中多次使用,所以,在多次使用中不变;

function f1(){
    var num = parseInt(Math.random()*100)+1;
    return function (){
        console.log(num);
    }
}
var f2 = f1();
f2();
f2();
f2();

闭包其他案例1:点赞案例

<ul>
  <li><img src="images/ly.jpg" alt=""><br/><input type="button" value="赞(1)"></li>
  <li><img src="images/lyml.jpg" alt=""><br/><input type="button" value="赞(1)"></li>
  <li><img src="images/fj.jpg" alt=""><br/><input type="button" value="赞(1)"></li>
  <li><img src="images/bd.jpg" alt=""><br/><input type="button" value="赞(1)"></li>
</ul>
<script>
  function f1() {
    var value=1;
    return function () {
      this.value="赞("+(++value)+")";
    }
  }
  //获取所有的按钮

  var btnObjs=document.getElementsByTagName("input");
  for(var i=0;i<btnObjs.length;i++){
    var ff=f1();
    //将 闭包函数 绑定给事件
    btnObjs[i].onclick=ff;
  }
</script>

闭包作用域


闭包就是能够读取其他函数内部变量的函数。


闭包(closure)是 Javascript 语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。

理解闭包,首先必须理解变量作用域。

更透彻的了解闭包,我们先来了解作用域和作用域链。

//局部变量  函数内部var声明的变量,函数外部不能使用

    function fn(){
        var a = 100;
        console.log(a);
    }
    fn()
    console.log(a);


//全局变量  函数外面声明的变量,函数内部可以使用

    var b = 200;
    function f1(){
        console.log(b);
    }
    f1();
    console.log(b);


//作用域链

    var c = 100;
    function f2(){
            //var c = 200;
        function f3(){
            // var c = 300;
            console.log(c);
        }
        f3();
    }
    f2(); //结果c=100


了解更多:JavaScript的作用域以及作用域链

通过作用域和作用域链,我们知道函数内部可以使用函数外部的变量;但函数外面不能使用函数内部的变量。

那么如何让函数外面使用函数内部的变量呢?

正常情况下,这是办不到的,只有通过变通方法才能实现。

那就是在函数的内部,再定义一个函数。


function f1(){
        var num = 100;
        function f2(){
            console.log(num);
        }
        return f2;
    }
    var res = f1();     // res = f2;  将f2函数 返回到 f1外面
    res(); // f1函数外面调用f2函数,使用到了f1函数内部的变量,这种方式就是写了一个“闭包”


原理:这就是JavaScript语言特有的”链式作用域”结构(chain scope),子级会一层一层地向上寻找所有父级的变量。

所以,父级的所有变量,对子级都是可见的,反之则不成立。

既然f2可以读取f1的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!


闭包的定义:

闭包就是能够读取其他函数内部变量的函数。

让函数通过嵌套的方式使外部的函数可以使用内部的变量这种方式叫做闭包;闭包其实就是一个内部函数,或者说闭包是一个内部函数以及函数所处的作用域环境.

在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。



闭包典型特征:

(1)两个函数嵌套

(2)内部函数被外部函数返回了.(闭包能够使 函数外部使用函数内部的变量)

(3)闭包能够使函数内部的变量,常驻内存(数据始终保存在内存中,不会被销毁)) -- 算是缺点

闭包缺点:

由于内部数据不会被自动销毁,占用内存。所以一般不要使用太多闭包

占用内存原理:外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。

因此不能滥用闭包,否则会造成网页的性能问题。


闭包的作用:

闭包的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中

即闭包可以使得它诞生环境一直存在;


学会JavaScript闭包

闭包和匿名函数的关系


闭包可以使用匿名函数的写法

    var res = (function(){
        var num = 300;
        return function(){
            console.log(num);
        };
    })();
    res();   //结果300  res 就是内部的函数


闭包的结果取决于什么?

特殊写法:闭包的结果取决于函数定义的位置;即函数本身所处的作用域;和函数调用的位置无关


例1

var num = 100;
    var f = function(){
        console.log(num);
    };

    function f1(){
        var num = 200;
        return f;
    }
    var res = f1(); // res 就是 f函数
    res(); //结果为100


例2

var num = 100;
    var f = function(){
        console.log(num);
    };

    function f1(){
        var num = 200;
        f();
    }
    f1(); //结果为100


使用闭包解决for循环绑定事件中i丢失问题


案例:我是第 几 个li标签(默认 有4个li)

var lis = document.querySelectorAll('li');
    for(var i=0; i<lis.length; i++){
        lis[i].onclick = function(){
            //输出 我是第 几 个li标签
            console.log('我是第 ' + i + ' 个li标签');
        }
    }

结果:不管点第几个li,都是显示5(li.length+1)的结果

原因:事件函数,是在循环结束之后才出发,循环结束,全局变量i已经是5(li.length+1);事件函数中使用全局变量i,始终是5


通过匿名函数+闭包的方式解决循环事件中i丢失问题

var lis = document.querySelectorAll('li');
    for(var i=0; i<lis.length; i++){
        //套一层匿名函数自调用,形成了一个闭包环境
        //主要作用是形成一个局部作用域
        (function(m){
            lis[m].onclick = function(){
                //输出 我是第 几 个li标签
                console.log('我是第 ' + m + ' 个li标签');
            }
        })(i);

//循环5次;每次都产生一个局部作用域,局部变量m的值都不一样,互相不影响

//局部变量和全局变量 互相不影响 不同作用域的局部变量 互相不影响


闭包缺点:由于内部数据不会被自动销毁,占用内存。


例1:普通函数num++

function fn(){

        var num = 100;

        console.log(num);

        num++;

    }

    fn();//100  //第一次执行后函数体内的101就被销毁了

    fn();//100


例2:闭包num++

function f1(){
        var num = 100;
        return function(){
            console.log(num);
            num++;
        };
    }
    var res = f1(); // res就是返回的那个函数
    res(); // 100  num = 101   //闭包第一次执行后101未被销毁
    res(); // 101  num = 102
    res(); // 102  num = 103


关于JS垃圾回收机制的问题

上面例子中为什么普通函数的num++执行后就被销毁了,而闭包的没有被销毁,这和JS垃圾回收机制的问题有关系.


当我们在函数内部引入一个变量或函数时,系统都会开辟一块内存空间;还会将这块内存的引用计数器进行初始化,初始化值为0,如果外部有全局变量或程序引用了这块空间,则引用计数器会自动进行+1操作,当函数执行完毕后,变量计数器重新归零,系统会运行垃圾回收,将函数运行产生的数据销毁;如计数器不是 0 ,则不会清除数据;


这个过程就称之为 "JS的垃圾回收机制" ;



闭包其他案例:

闭包其他案例1:缓存随机数

需求:获取随机数,随机数一旦生成,会在程序中多次使用,所以,在多次使用中不变;

function f1(){
    var num = parseInt(Math.random()*100)+1;
    return function (){
        console.log(num);
    }
}
var f2 = f1();
f2();
f2();
f2();

闭包其他案例1:点赞案例

<ul>
  <li><img src="images/ly.jpg" alt=""><br/><input type="button" value="赞(1)"></li>
  <li><img src="images/lyml.jpg" alt=""><br/><input type="button" value="赞(1)"></li>
  <li><img src="images/fj.jpg" alt=""><br/><input type="button" value="赞(1)"></li>
  <li><img src="images/bd.jpg" alt=""><br/><input type="button" value="赞(1)"></li>
</ul>
<script>
  function f1() {
    var value=1;
    return function () {
      this.value="赞("+(++value)+")";
    }
  }
  //获取所有的按钮

  var btnObjs=document.getElementsByTagName("input");
  for(var i=0;i<btnObjs.length;i++){
    var ff=f1();
    //将 闭包函数 绑定给事件
    btnObjs[i].onclick=ff;
  }
</script>

闭包作用域


小米技术社区