您当前的位置:首页 > 网站建设笔记 >

javascript 闭包

0

闭包是与作用域相关的一个概念,它指的是内部函数即使在外部函数执行完成并终止以后,仍然可以访问其外部函数的属性。当引用一个变量或方法时,JavaScript会沿着由对象执行路径构成的作用域链对作用域进行解析,查找变量最近定义的值,一旦找到,即使用该值。

作用域解析和闭包中的隐含问题往往并不是一开始就显而易见的,而且通常是我遇到过的“残缺”脚本中大部分问题的根源所在。为了说明这一点,我们来看下面的例子:

<ul>
  <li><a id="anchor1">Anchor 1</a></li>
  <li><a id="anchor2">Anchor 2</a></li>
  <li><a id="anchor3">Anchor 3</a></li>
</ul>

<script type="text/javascript">
function initAnchors(W3CEvent){
  for(var i=1;i<=3;i++){
    var anchor=document.getElementById("anchor"+i);
    LS.addEvent(anchor,"click",function(){alert("My id is anchor"+i);});
  }
}
LS.addEvent(window,"load",initAnchors);
</script>

这个例子通过在窗口载入完成时运行initAnchors()函数,为页面中的锚注册事件侦听器。现在,我们假设文档中有3个锚元素,它们的ID值分别为anchor1到anchor3,那么单击这几个锚的结果会怎样呢?你可以自己试一下并单击以下其中的几个锚。

虽然代码中的语法正确,但其中的逻辑却存在缺陷。大多数新手,甚至某些经验丰富的程序员,都会设想当单击每个锚时,会提示My id is anchorX(其中X对应着指定click事件侦听器时i的值)。如果真是这样,单击第一个锚应该提示My id is anchor1,而单击第三个应该提示My id is anchor3。但这种设想是错的,事实上单击每个锚都会提示相同的信息。为什么会这样?因为i的值实际上是在单击事件发生时才从作用域链中取得的。当单击事件发生时,initAnchors()已经执行完毕,因此i的值等于4(因为i要递增为比3大的数才能使循环停止),所以每个alert()都会显示相同的信息。

具体来说,当click事件侦听器被调用并在它的内部作用域中查找i的值时,结果没有找到(因为i的值在作为click事件侦听器的匿名函数中没有定义),因此它会到包含自己的外部作用域即initAnchors()函数中去查找。而在initAnchors()函数中i的值是4,所以它就从该处取得了这个值。要得到正确的结果,需要把事件侦听器的注册转移到一个独立的函数中,并通过该函数的参数传递适当的值:

function registerListener(anchor, i) {
  LS.addEvent(anchor, "click", function(){
    alert("My id is anchor" + i);
  });
}
function initAnchors(W3CEvent) {
  for (var i=1; i<=3; i++) {
    var anchor = document.getElementById("anchor" + i);
    registerListener(anchor, i);
  }
}
LS.addEvent(window, "load", initAnchors);

由于在作用域链中定义了额外的函数和变量,提示信息中保持了正确的值。因为click事件侦听器现在的外部作用域变成了registerListener()函数,该函数在其每个实例(每次调用registerListener()函数都会生成该函数的一个副本,以维护正确的变量作用域。)的内部作用域中都为i维护了一个唯一的值。同样地,如果将registerListener()替换成下面的函数,提示的信息将是My id is anchorX and initAnchors i is 4。同理,这也是因为i的值不是在registerListener()函数及其包含的匿名事件侦听器中定义的,因此最终还是要从initAnchors()函数的作用域中取得i的值。

function registerListener(anchor, myNum) {
  LS.addEvent(anchor, "click", function(){
    alert("My id is anchor" + myNum + " and initAnchors i is " + i);
  });
}

这种类似的情况在日常开发中并不少见,即在将方法动态赋值给对象时,必须使用额外(之所以要使用额外的函数,主要是因为无法给匿名函数传递参数直接维护内部作用域)的函数才能维护适当的变量作用域。

<script type="text/javascript">
//以下还有另一种写法
LS.addEvent(window,"load",function(W3CEvent){
  for(var i=1;i<=4;i++){
    var anchor=document.getElementById("anchor-other-"+i);
    LS.addEvent(anchor,"click",function(num){
      return function(eventObject){
        alert("My id is anchor-other-"+num);
        eventObject=eventObject||window.event;
        if(eventObject.preventDefault){
          eventObject.preventDefault();
        }else{
          eventObject.returnValue=false;
        }
      };
    }(i));
  }
});
</script>

<ul>
<li><a id="anchor-other-1" href="/">网站程序网首页</a></li>
<li><a id="anchor-other-2" href="/list-123-1.html">网站模板</a></li>
<li><a id="anchor-other-3" href="/cms/">CMS常见问题</a></li>
<li><a id="anchor-other-4" href="/wangzhanbj/">网站建设笔记</a></li>
</ul>

建站咨询

在线咨询真诚为您提供专业解答服务

咨询热线

137 1731 25507×24小时服务热线

微信交流

二维码终于等到你,还好我没放弃
返回顶部