IT不死旗下博客隆重开通啦,欢迎各位小伙伴们来围观 。

JS中的闭包二三事-2

Javascript Xueqi 1252℃ 0评论

我们先来看一下闭包的概念:

当内部函数在定义它的作用域  的外部引用时,
就创建了该内部函数的闭包 ,如果内部函数引用了位于外部函数的变量,
当外部函数调用完毕后,这些变量在内存不会被释放,因为闭包需要用到它们.

官方的解释是:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式
(通常是一个函数),因而这些变量也是该表达式的一部分。
更通俗一些的理解:
当函数a的内部函数b被函数a外的一个变量引用的时候,就创建了一个闭包。
闭包的作用:或者说为什么要使用闭包?
闭包的存在使得在函数外面可以访问函数内的局部变量,创建闭包的常见方式就是在一个函数a内部创建另一个函数b,并且返回这个函数b,外部变量就可以通过函数b来访问函数a里的局部变量了.

//a函数的定义:
function a(){
   var user="ZDL";
   //直接返回一个匿名函数
   return function(){
   return user;
}
}
//或者这样定义a函数
function a(){
   var user="ZDL";
   function b(){return user;}
   //返回具名函数(有名字的函数)
   return b;
}
//再或者
function a(){
  var user="ZDL";
  //把函数赋给一个变量,再返回这个变量
  var b=function(){return user;}
  return b;
}
//这几个定义方式的效果都是一样的,都是在a函数的内部返回了一个函数
console.log(a()()); // ZDL
var fn=a(); // b保存了A函数里的返回的匿名函数
console.log("另一种方式调用:",fn()); //另一种方式调用: ZDL

在这个例子中,我们在函数外部成功的访问到了函数a内的局部变量user,
而正常情况,在函数的外部是无法访问函数内的局部变量的,这是由于JS的作用域链的
缘故,关于js中的作用域链,看这篇文章,详解JS中的作用域
而闭包的存在,使得a的整个执行环境保留了下来,里面的变量的内存也都保留了下来
这就是闭包的一个优点,也可以说是它的缺点就是可以把局部变量驻留在内存中,可以避免使
用全局变量。(全局变量污染导致应用程序不可预测性,每个模块都可调用必将引来灾难,
所以推荐使用私有的,封装的局部变量)。
下面我们用闭包实现一些功能:
1. 实现一个局部变量的累加

function Test(){
  var age=100;
  return function(){
     age++;
    return age;
  }
}
var box=Test();//取得Test里的匿名函数
box(); //第一次调用匿名函数 101
box(); //第二次调用匿名函数 102
box(); //第三次调用匿名函数 103
console.log(box()); //104
//这里正是由于闭包的存在,使得函数Test里的局部变量age一直保存在内存中,
//大量使用闭包可能会导致性能下降,建议在非常有必要的时候才使用闭包

2.
作用域链的机制导致一个问题,在循环中里的匿名函数取得的任何变量都是循环后的最后一个值
问题描述:

问题1:我想定义一个函数数组,数组里每一个函数的功能是打印出这个函数在函数中的索引。
类似的

 

问题2:实现给ul下的所有li添加一个事件,使得点击li时,弹出自己在ul中的索引。即点击
第一个li弹出0,第二个li弹出1,第n个li弹出n-1;
这几个是常见的面试题,经常会问到,我们有必要熟练掌握它。
情况1:

function box(){
var arr=[];
for(var i=0;i<5;i++){
    arr[i]=function(){
    return i;
   }
 }
 return arr;
}
var b=box();
console.log("数组长度:",b.length);
for(var i=0;i<b.length;i++){
console.log(b[i]());
}

这里的例子输出的结果都是5,也就是循环结束后得到的i的值。因为b[i]调用的是匿
名函数,匿名函数并没有自我执行,等到调用的时候,box()已执行完毕,i 早已变成5,
同时每个函数访问到的都是同一个i,也就是循环结束后,值变为5的那个i。

使用闭包,写法1:

function box(){
   var arr=[];
   for(var i=0;i<5;i++){
   arr[i]=(function(n){
  return function(){
    return n;
  }
})(i);
}
 return arr;
}
var b=box();
console.log("数组长度:",b.length);
for(var i=0;i<b.length;i++){
    console.log(b[i]());
}

//闭包的写法2:
function box(){
 var arr=[];
 for(var i=0;i<5;i++){
  (function(n){
     arr[i]=function(){
       return n;
    }
  })(i);
 }
  return arr;
}
var b=box();
console.log("数组长度:",b.length);
for(var i=0;i<b.length;i++){
    console.log(b[i]());
}

 

 

 

 

 

 

 

 

这里通过闭包,自执行匿名函数的执行环境保存了下来,
每一个函数使用的都有自己的一个n,也就是当时自执行匿名函数传过来的i

接下来再来看看给ul下的所有li添加一个事件,点击各自实现出自己在ul中的索引:
html部分:
<ul id=”ul1″>
<li>我是第1个Li</li>
<li>我是第2个Li</li>
<li>我是第3个Li</li>
<li>我是第4个Li</li>
<li>我是第5个Li</li>
</ul>

我们第一想到的代码:
JS代码:

var aLis=document.getElementById("ul1").getElementsByTagName("li");
for(var i=0;i<aLis.length;i++){
     aLis[i].onclick=function(){
     alert(i);
   }
}

这里的结果想必大家都知道了,点击每一个Li弹出的都是同一个值,都是5
也就是循环结束后,i的值,其实,由于作用域链的原因(关于 作用域链,可以看这篇文章)   详解JS中的作用域
每一个Li函数里访问到的都是同一个i,也就是循环结束后的那个i的。
解决办法:也是通过闭包来解决,这里直接附上代码:
写法1:

for(var i=0;i<aLis.length;i++){
  aLis[i].onclick=(function(n){
     return function(){
       alert(n);
     }
  })(i);
}

这里,每一个循环里都会执行一个自执行匿名函数,传入i,同时返回一个匿名函数
这相当于:

aLis[i].onclick=function(){
   alert(n);
}

// 当点击Li时,执行alert(n),这个n是在哪里找到的呢? 就是在上面它对应索引的那个匿名函数
找到的n,也就是传进来的i, 这个外面的自执行匿名函数相当于我们前面说到的函数a,里面返回的
那个匿名函数相当于函数b,函数b里用到了a函数的n,而b函数又被a函数外面的aLis[i]引用了
这就产生了闭包,而每执行一次循环,就会产生一个闭包,当执行Li的点击事件时,n就会在它们
各自对应的闭包里找到n,也就是找到了我们想到的索引值.

写法2:

for(var i=0;i<aLis.length;i++){
  (function(n){
    aLis[i].onclick=function(){
      alert(n);
    }
   })(i);
}

同样的,这样写和上面的原理是差不多的

写了这么多,也不知道你看懂了没有,第一次写这么长的文章,不容易呐

另一篇文章:详解JS中的作用域

转载请注明:凌风阁 » JS中的闭包二三事-2

喜欢 (18)
发表我的评论
取消评论
表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址