JavaScript闭包实例讲解
闭包是 JavaScript 语言中的难点,很多刚入行的(包括我在内)一时对他很难理解,于是在网上各种搜罗有关闭包的学习资料,但是无数的文章介绍闭包,但都是了解一个皮毛。说实在的我到现在也不敢和大家百分百的肯定掌握它。所以今天我就把我的整理的学习笔记分享给大家,希望能够对大家有用。
但要想理解闭包,首先要理解 Javascript 特殊的变量作用域。变量作用域分为两种:一种是全局变量,另一种是局部变量。
一、函数内部直接读取全局变量,代码如下:
var n = 999; function f1() { Alert(n); } fi() //999
二、函数的外部是无法读取函数内部的局部变量
function f1(){ var n=999; } alert(n); // error
注:函数内部声明变量的时候,一定要使用 var 命令。如果不用的话,你实际上声明了一个全局变量!
function f1(){ n=999; } f1(); alert(n); // 999
三、如何从外部读取局部变量?
有时候需要得到函数内的局部变量。但是,前面已经说过了,正常情况下,这是办不到的,那如何才能实现呢?
解决办法就是那就是在函数的内部,再定义一个函数。代码如下:
function f1(){ var n=999; function f2(){ alert(n); // 999 } }
从代码中我们可以看到,函数 f2 就被包在函数 f1 内部,这时 f1 内部的所有局部变量,对 f2 都是可见的。但是反过来就不行,f2 内部的局部变量,对 f1 就是不可见的。这就是 Javascript 语言特有的”链式作用域”结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
既然 f2 可以读取 f1 中的局部变量,那么只要把 f2 作为返回值,我们不就可以在 f1 外部读取它的内部变量了吗!
function f1(){ var n=999; function f2(){ alert(n); } return f2; } var result=f1(); result(); // 999
以上 f2 函数就是一个闭包。
四、那么闭包的概念是什么呢?
按照官方的意思解释说就是一个函数有权访问另一个函数作用域中的变量函数。我心里悄悄地说了一句:“我擦,俺听不懂”,在 Javascript 语言中,只有函数内部的子函数才能读取局部变量,闭包用我的话来说就是能够读取其他函数内部变量的函数。
说到这儿大家可能要问了,闭包有什么用途呢?这么难懂,在项目中用到的多吗?闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。看下面的代码:
function f1(){ var n = 999; nAdd = function(){n += 1} function f2(){ alert(n); } return f2; } var result=f1(); result(); // 999 nAdd(); result(); // 1000
在这段代码中,result 实际上就是闭包 f2 函数。它一共运行了两次,第一次的值是 999,第二次的值是 1000。这证明了,函数 f1 中的局部变量 n 一直保存在内存中,并没有在 f1 调用后被自动清除。
为什么会这样呢?原因就在于 f1 是 f2 的父函数,而 f2 被赋给了一个全局变量,这导致 f2 始终在内存中,而 f2 的存在依赖于 f1,因此 f1 也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
这段代码中另一个值得注意的地方,就是”nAdd=function(){n+=1}
“这一行,首先在 nAdd 前面没有使用 var 关键字,因此 nAdd 是一个全局变量,而不是局部变量。其次,nAdd 的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以 nAdd 相当于是一个 setter,可以在函数外部对函数内部的局部变量进行操作。
接下来为大家带来一个例子,当我们点击 li 时,弹出当前点击的这个 li 是第几个 li?
HTML 代码:
<ul id="list"> <li>第 1 行</li> <li>第 2 行</li> <li>第 3 行</li> <li>第 4 行</li> <li>第 5 行</li> <li>第 6 行</li> </ul>
JS 代码:
var $li = document.getElementById("list").getElementsByTagName("li"); var i=0; for(;i < $li.length;i++){ $li[i].onclick = function(){ console.log(i); } }
运行上面这段代码,你会发现无论你点击哪一行的 li,最后的结果都是输出 6,这到底是为什么呢,上面的 for 循环后 i 的最终值是 6,他并没有一级一级的存储下来每个 i,而是一次性输出了 6 给下面的$li[i]
,所以每次点击当然最后的 i 也只能是 6。
大家可以看到上面的函数并没有使用到闭包的功能,那我们就来改造一下代码,让我们的 js 代码能够满足我现在的需求。
var $li = document.getElementById("list").getElementsByTagName("li"); function A(){ var i=0; for(;i < $li.length;i++){ B(i); } } function B(index){ $li[index].onclick = function(){ console.log(index); } } A();
运行下我们改造后的代码,你就会发现我们点击哪个 li 就显示当前点击的是哪一个 li 的值,不再是全都输出 6 了。
上面的代码实际上告诉了我们一件事情,就是闭包是可以储存变量的,即使 A 函数里面的变量 i 已经执行完毕,被 javascript 垃圾回收机制销毁了,但是 B 函数还会保存住这个值。
如果上面的代码你还没能理解,那没关系,我们再把上面代码分解的简单点:
var i=0; for(;i < $li.length;i++){ function test(){ console.log(i); } } test();//输出的是 6
上面这段代码大家肯定会觉得奇怪为什么只输出了 6,而不是 0,1,2,3,4,5 呢?
我们看下上面的方法如果用闭包函数来做会不会输出 0,1,2,3,4,5。
function A(){ for(var i=0;i < $li.length;i++){ B(i); } } function B(index){ console.log(index); } A();//输出的是 0,1,2,3,4,5
这样子的例子是不是就是我们想要的结果了,如果能理解上面的写的例子,我想你就大概就能对闭包能有个初步的理解和认识了,至于要在什么场景下使用,那就看你对他的理解程度了。
五、使用闭包的注意点
1、由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在 IE 中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2、闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
以上面的代码为例:
function A(){ for(var i=0;i < $li.length;i++){ B(i); } } function B(index){ console.log(index); } A();//输出的是 0,1,2,3,4,5 B = null;
这样子 B 函数中的内存就被释放了。
结束语
以上是我对 javascript 闭包的一些浅解,希望对大家学习 javascript 有所帮助,如果以上言论有不对的地方,希望各位立即批评指出。
码云笔记 » JavaScript闭包实例讲解