在JS这块,免不了被问什么是闭包。javascript
从一个常见的循环问题提及。java
有一个ul列表, 里面有5个li标签,我但愿点击每一个li标签的时候,弹出每一个li标签对应的索引值(第一个弹出0,第二个弹出1...)。git
<ul id="result"> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> </ul>
当我很认真的写出一段代码:github
var lis = document.getElementsByTagName('li'), n = lis.length, i = 0; for(; i < n; i++){ lis[i].onclick = function(){ alert(i); } }
蛮高兴的作了点击测试,从第一个li标签开始,弹出"5",第二个、第三个...所有都弹出“5”。什么状况,这不是我想要的结果。出现了问题,就去找问题的缘由,当我点击每一个li标签的时候,都弹出“5”,说明for循环已经运行完了,变量 i 也跟着循环条件增长到了5,在我点击前,循环已经执行完成。闭包
通过一番思考,从新写了这段代码:ide
var lis = document.getElementsByTagName('li'), n = lis.length, i = 0; for(; i < n; i++){ lis[i].index = i; lis[i].onclick = function(){ alert(this.index); } }
把每次循环时变量 i 的值赋值给每一个li标签对象的一个属性。很神奇的这段代码作到了我要的结果,点击每一个li标签弹出了对应的索引值(第一个弹出0,第二个弹出1...)。这让我想到是由于变量 i 没有被正确的引用,才发生那都弹出5的问题。懵懵懂懂的想到要正确的引用 变量 i 。通过屡次写写改改,点击测试,写出了下面这样的代码:函数
var lis = document.getElementsByTagName('li'), n = lis.length, i = 0; for(; i < n; i++){ (function(num){ lis[num].onclick = function(){ alert(num); } }(i)); } //或者 var lis = document.getElementsByTagName('li'), n = lis.length, i = 0; for(; i < n; i++){ lis[i].onclick = function(num){ return function(){ alert(num); } }(i); }
后来半知半解的明白了这是闭包的一种运用,闭包与变量的做用域、变量的生存周期有密切的关系。要理解闭包就要理解变量的做用域、变量的生存周期。好吧,得先了解与变量有关的知识了。测试
变量的做用域this
当在一个函数中声明一个变量的时候,若是咱们没有加上关键字 var, 这个变量就是全局变量,加上了关键字var,这个变量就是局部变量,只有在这个函数内部才能访问这个局部变量,在函数外是访问不到的。 函数的参数也是局部变量,只能在函数内部访问。spa
function a(){ b = 1; //全局变量 var c = 2; //局部变量 } a(); alert(b); //1 alert(c); //出错 c未定义
定义了一个函数a,里面有个全局变量b,局部变量c。当在函数a外部访问变量b、c,正常弹出了b的值,而变量c是局部变量,没有正常访问,因此出错,提示变量c未定义。
在函数内部,局部变量优先级高于同名的全局变量。当定义一个函数的时候,也会随之建立一个函数做用域。当在函数内部访问一个变量的时候,会在函数内部做用域搜索这个变量,若是函数内部没有这个变量,会在函数外部搜索,直到找到这个名称的变量为止。若是找不到,就会抛出一个错误。
var v = 1; function a(){ var m = 2; function b(){ var n = 3; alert ( m ); // 2 alert ( v ); // 1 } b(); alelrt ( n ); //出错 } a();
上面的代码第一次弹出变量m,首先在函数b里查找,但没有找到,继续往外找,在函数a里面找,m的值为2;第二次弹出变量v,一样的函数b里找,没有找到,再在函数a里找,也没有找到,在往外找,找到了v的值为1;第三次弹出变量n,在函数a里面找,没有找到,不能在函数b里面找,由于查找是向上、向外的,不能向下、向内,最后在函数a的外面找,也没有找到,这时就抛出错误,变量n没有定义。
上面的代码有三个做用域,函数b的做用域,函数a的做用域,window全局做用域(最顶层的做用域),这些做用域联合起来,就造成了做用域链。访问变量就是在这个做用域链的一个搜索过程。
变量的生存周期
在javascript中,全局变量拥有很长的生存周期,直到把变量销毁,而局部变量会随着函数调用结束被销毁。
function a(){ v = 1; //全局变量v alert(v); //1 } a(); alert(v) //1 全局变量v还存在 function b(){ var i = 2; //局部变量i alert(i); //2 } b(); alert(i) //出错 i未定义 局部变量i已经被销毁
上面的代码定义了两个函数,函数a内部定义全局变量v,函数a执行完后全局变量v还存在;函数b内部定义了局部变量i,函数b执行完后局部变量i被销毁。
function c(){ var k = 3; //局部变量k return function(){ k++; alert(k); } } var f = c(); f(); //4 f(); //5 f(); //6
上面的代码定义了一个函数c,函数c内部定义了局部变量k,当函数c执行后返回了一个匿名函数,匿名函数访问了局部变量k,变量f的值为这个匿名函数,调用f其实是调用这个匿名函数。每次调用f(),变量k的值都会增长1。在这里局部变量k没有在函数c执行完后被销毁,反而“活”了下来,它的生存周期延长了。
什么是闭包?
当前做用域老是可以访问外部做用域中的变量, 函数是 JavaScript 中惟一拥有自身做用域的结构, 所以闭包的建立依赖于函数。
1. 一个函数能够引用外部函数的变量,这个函数就可算是一个闭包。
2. 外部函数已经执行完,内部的函数仍能够引用外部函数的变量。这个内部函数就可算是一个闭包。
3. 函数能存储其做用域的变量、能读写当前函数做用域内变量的函数可算是一个闭包。