JavaScript Core -- 理解闭包

来源:互联网 发布:混沌与秩序2救赎数据库 编辑:程序博客网 时间:2024/06/15 00:01

JavaScript 闭包究竟是什么

引言:理论基础

    闭包就是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),可以读取函数外部的变量(其他函数内部变量),让这些执行环境始终保持在内存中。由于在javascript语言中,只有函数内部的子函数可以读取局部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”,从而可以连接内部函数和外部函数。

    也就是说,闭包是有权访问另一个函数作用域的变量的函数,创建一个闭包最直接的方式就是在函数内创建另一个函数,如内部函数。本质上所有函数都是闭包函数。

function test(){     for(var i = 0; i < 10 ; i++){         setTimeout(function(){            console.log(i)        }, 0);      }}test(); // 10 10 10 10 10 <span style="font-family: Arial;">10 10 10 10 10</span>

    以上函数原本想输出0到9,结果输出了10个10。原因是test()执行时会创建一个运行时期的上下文,而setTimeout内部的函数会放在for循环队列之后,等到for循环执行完之后才开始执行。
function(){console.log(i)}执行是首先会寻找函数内部的i标示符。此时找不到i,再寻找test中的i(闭包的概念:访问函数外的变量,这些变量只有等到闭包不使用才会被销毁),而此时的i值已经变为了10,所以十次执行都会输出10。
解决这个问题可以在作用域链上增加一个节点,保存i变量。
function test(){for(var i = 0; i < 10 ; i++){     (function(li){        setTimeout(function(){            console.log(li)         }, 0);     })(i);    }}test(); // 0...9(0到9)
    通过增加一个变量保存每次执行需要输出的i值,实现0到9的输出。

    闭包有如下明显优点:能够读取函数内部的变量,并且让这些变量的值始终保存在内存中。使用闭包和匿名自执行函数实现模块化。

    闭包也有如下明显缺点

    1.既然不释放内存,则必然会对内存的消耗很大,造成页面访问的性能问题。解决办法:将不使用的局部变量全部删掉,当你退出函数时。

    2.闭包会改变父函数的私有属性,在你不经意的时候

    再举一个例子

 function f1(){    var n=999;    nAdd=function(){n+=1}//定义了一个全局变量,相当于setter,方便在函数外部对函数内部的变量进行操作,不是本文要说明的重点    function f2(){      alert(n);    }    return f2;}var result=f1();result(); // 999nAdd();result(); // 1000,可见变量保存在了内存中;var result2 = f1();result2();//999 这里的值为什么不是1000呢?带着疑问往下看

    首先,我们要了解什么事内部函数:内部函数就是定义在另一个函数中的函数。例如:

function outerFn () {    function innerFn () {}}outerFn();//可以这么调用innerFn();//不能这么直接调用内部函数
    一个函数当没有依赖关系存在,就会有被垃圾回收机制回收的可能,但是如果像上文案例f1()那样将内部函数作为外部函数返回值赋值给一个全局变量,则会改变这种潜在的关系。这种即使离开函数作用域的情况下仍然能够通过引用调用内部函数的事实,意味着只要存在调用内部函数的可能,JavaScript就需要保留被该内部函数引用的函数(本文中就是outerFn)。而且JavaScript运行时需要跟踪引用这个内部函数的所有变量,直到最后一个变量废弃,JavaScript的垃圾收集器才能释放相应的内存空间(将内部函数置为null)。
   根据上面的论述,我们进一步简略分析一下开篇第二个例子中的结果,同时也正是一下我们的推论:f1()函数的返回值f2()赋值给了result全局变量,导致f2始终在内存中未被清除,而f2()保存有f1()的依赖关系,所以f1()也在内存中,所以我们可以再外部访问到f1()中的内部变量。而之所以第二次调用var result2 = f1();结果仍然为999。是因为我们创建新的封闭环境,本质上是创建了一个新对象,而闭包就是这个对象的实例方法。而且这些变量也是私有的,因为不能在封装它们的作用域外部直接引用这些变量,从而确保了面向对象数据的专有性。

2 )其他

闭包的一些经典题目或错误

1) javascript事件绑定

页面上有若干个div, 我们想给它们绑定一个onclick方法,于是有了下面的代码

<div id="divTest">        <span>0</span> <span>1</span> <span>2</span> <span>3</span>    </div>    <div id="divTest2">        <span>0</span> <span>1</span> <span>2</span> <span>3</span>    </div>

$(document).ready(function() {            var spans = $("#divTest span");            for (var i = 0; i < spans.length; i++) {                spans[i].onclick = function() {                    alert(i);                }            }        });

很简单的功能可是却偏偏出错了,每次alert出的值都是4,简单的修改就好使了

var spans2 = $("#divTest2 span");        $(document).ready(function() {            for (var i = 0; i < spans2.length; i++) {                (function(num) {                    spans2[i].onclick = function() {                        alert(num);                    }                })(i);            }        });

    解释:上面代码在页面加载后就会执行,当i的值为4的时候,判断条件不成立,for循环执行完毕,但是因为每个span的onclick方法这时候为内部函数,所以 i 被闭包引用,内存不能被销毁,i的值会一直保持4,直到程序改变它或者所有的onclick函数销毁(主动把函数赋为null或者页面卸载)时才会被回收。这样每次我们点击span的时候,onclick函数会查找i的值(作用域链是引用方式),一查等于4,然后就alert给我们了。而第二种方式是使用了一个立即执行的函数又创建了一层闭包,函数声明放在括号内就变成了表达式,后面再加上括号括号就是调用了,这时候把i当参数传入,函数立即执行,num保存每次i的值。


    白了个白~

0 0
原创粉丝点击