JavaScript 闭包机制详解及实例代码

首先要区分两个概念,一是匿名函数,一是闭包。

所谓匿名函数,就是创建函数没有给定函数名。经常出现的包括函数表达式,就是定义一个匿名函数,然后将函数赋值给某个变量,而此时这个变量就相当于该函数的函数名,例如:

var sayHi = function(){
  alert("Hi");
}; //注意这个分号
sayHi(); //调用函数

还有一种常用匿名函数的情况是回调函数,如 JQuery 中常用到的:

$("p").click(function(){
  alert("click");
});

此外,还有利用匿名函数作为某函数的返回值:

function sayNameWithAge(age){
  return function(person){
    if(person.age == age){
      return person.name;
    }
  }
}

那么,闭包又是怎么一回事呢?所谓的闭包,其实就是一个函数,而这个函数有一点比较特别,它有权能够去访问其他函数作用域的变量。

从定义中我们发现,其实在上面的匿名函数例子中,就存在这样的闭包。在最后一个例子中,匿名函数访问了函数 sayNameWithAge 的参数 age,那么,这个作为返回值的匿名函数就是一个闭包。

要彻底理解闭包,就必须理解函数调用时的整个机制,这里从作用域链的相关知识来进行讲解。

首先看下面的例子:

function sayName(name){
  alert(name);
}
sayName("Jack");

在上面的函数 sayName 被调用的时候,就会创建一个对应的执行环境和作用域链,如下图所示:

当 sayName 函数被调用时,创建了相应的作用域链,而作用域中包含两个引用分别指向两个对象,其中一个是全局变量对象,这个全局对象是在函数创建的时候就已经创建了,只是在调用函数的时候才将其复制到作用域链中;而另一个就是函数的活动对象,这个对象是在调用函数的时候才创建的。

在函数中访问一个变量时,就会从作用域中搜索对应名字的变量。

而当函数执行完毕后,函数的活动对象会被销毁,而全局变量对象却永远保存在内存中。

但是,上面所说的都是普通函数的情况,对于闭包而言,又是另外一种情况:

以上面的 sayNameWithAge 函数为例:

function sayNameWithAge(age){
  return function(person){
    if(person.age == age){
      return person.name;
    }
  }
}
//创建函数
var sayName = sayNameWithAge(18);
//调用函数
var name = sayName({name:"Jack",age:18});
//解除对匿名函数的引用
sayName = null;

当上面的 sayName 函数被调用的时候,产生的作用域链如下所示:

当匿名函数被 return 后,它的作用域链被创建,并且包含了外部函数的活动对象和全局变量对象,这样一来,这个匿名函数就可以访问 sayNameWithAge 函数中定义的所有变量,也就是一个闭包。

这样的闭包会存在一个问题,就是当 sayNameWithAge 函数执行完毕的时候(JS 的垃圾处理机制大多是标记清除),其活动对象被闭包所引用,所以活动对象并不会被销毁,只有当匿名函数被销毁后,sayNameWithAge 的活动对象才会被销毁,所以上面的最后一行解除对匿名函数的引用不仅是为了销毁闭包的对象,也是为了销毁外部函数的活动对象。所以,慎重使用闭包!!!

关于闭包,还有一个需要注意的地方,就是在闭包中访问其他函数的变量,实际上是因为闭包的作用域链中有指向其他函数的活动对象的引用,而不是闭包自身的活动对象中保存着这些变量。看下面的例子:

function outer(){
  var result = new Array();
  for(var i = 0; i < 5; i ++){
    result[i] = function(){
      return i;
    };
  }
  return result;
}

按照设想,最后 outer 返回的数组各个项中的值应该是与其下标一致的。但是,最后的结果却是每个项的值都是 5
不难想象,在上面的所有闭包的作用域链中,都有一个引用指向了 outer 的活动对象中的参数 i,而且是指向同一个对象。

当 outer 函数执行完毕的时候,i 的值是 5。也就是说,所有闭包中访问 i 的时候取到的值都是 5

那么,我们可以通过另一种方法来实现预想的效果:

function outer(){
  var result = new Array();
  for(var i = 0; i < 5; i ++){
    result[i] = (fuction(index){
      return index;
    })(i);
  }
  return result;
}

这里我们为匿名函数定义一个参数 index,并在每次循环中立即调用该函数,将 i 的当前值复制给参数 index(注意 JS 中是按值传递),并将返回的 index 赋值给 result。

此外,闭包中需要注意的另一个问题是 this 对象。

this 对象在 JS 中是在函数运行时基于函数的执行环境绑定的。而匿名函数的执行环境具有全局性,也就是说,在匿名函数中,this 对象通常指向 window。

var name = "Tom";
var person = {
  name : "Jack",
  sayName : function(){
    return (function(){
      return this.name;
    })();
  }
}
person.sayName(); //Tom

上面在闭包中访问 this.name,其中的 this 对象并非取得自身或是 person 的 this 对象,而是指向 window。

如果需要在闭包中访问外部函数的 this 对象,那么,可以在外部函数中定义一个变量,将 this 对象传给该变量。

var name = "Tom";
var person = {
  name : "Jack",
  sayName : function(){
    var self = this;
    return (function(){
      return self.name;
    })();
  }
}
person.sayName(); //Jack

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

(0)

相关推荐

  • JavaScript闭包和范围实例详解

    本文实例分析了JavaScript闭包和范围.分享给大家供大家参考,具体如下: 1. if (!("a" in window)) { var a = 1; } alert(a); [分析]代码含义:如果window不包含属性a,就声明一个变量a并赋值为1 ①JS引擎会先扫描所有的变量声明 ②所有的全局变量都是window的属性 ③变量声明和赋值一起用时,Js引擎会自动将它分成两部分:变量声明提前,变量赋值没有(不将赋值提前是因为他有可能影响代码执行出不可预期的结果) 所以代码执行顺序等

  • JS延迟加载(setTimeout) JS最后加载

    第一 JS延迟加载 复制代码 代码如下: <script language="JavaScript" src="" id="my"></script> <script> setTimeout("document.getElementById('my').src='include/common.php'; ",3000);//延时3秒 </script> 一般情况下都是利用setT

  • 学习Javascript闭包(Closure)知识

    一.变量的作用域 要理解闭包,首先必须理解Javascript特殊的变量作用域. 变量的作用域无非就是两种:全局变量和局部变量. Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量. var n=999; function f1(){ alert(n); } f1(); // 999 另一方面,在函数外部自然无法读取函数内的局部变量. function f1(){ var n=999; } alert(n); // error 这里有一个地方需要注意,函数内部声明变量的时候,

  • 深入理解javascript函数参数与闭包

    最近在学习javascript的函数,函数是javascript的一等对象,想要学好javascript,就必须深刻理解函数.本人把学习的过程整理成文章,一是为了加深自己函数的理解,二是给读者提供学习的途径,避免走弯路.内容有些多,但都是笔者对于函数的总结. 1.函数参数 1.1:参数是什么 1.2:参数的省略 1.3:参数默认值 1.4:参数传递方式 1.5:同名参数 1.6:arguments对象 2.闭包 2.1:闭包定义 2.2:立即调用的函数表达式(IIFE, Immediately

  • jquery插件lazyload.js延迟加载图片的使用方法

    如果一个网页很长并且有很多图片的话,下载图片就需要很多时间,那么就会影响整个网页的加载速度,而这款延迟加载插件,会通过你的滚动情况来加载你需要看的图片,然后它才会从后台请求下载图片,最后显示出来.通过这个插件,可以在需要显示图片的时候,才下载图片,从而可以减少服务器的压力,提高页面加载速度. Lazy Load 插件原理 修改目标img元素的src属性为orginal属性,从而中断图片的加载.检测滚动状态,然后把网页可视区域中的img的src属性还原然后加载图片,从而制造了一种缓冲加载的效果.代

  • js闭包用法实例详解

    本文实例讲述了js闭包用法.分享给大家供大家参考,具体如下: 引言 在公司中需要写一个js脚本来进行网站的统计,实现类似百度统计或者站长统计的功能,在实现的过程中自己感觉写的代码还是可以的,因为之前的js代码都是这些写,但是在组长代码走查的时候却非常的不满意,因为我们在js中写的方法都是全局的方法,因为我们写的东西需要嵌入到别人的界面中,所以这些全局的东西很可能会和别人的东西重名从而引发错误,所以说组长就给我留下一句话:用js闭包包起来. 变量作用域 我们都非常的熟悉变量的作用域就分为:全局变量

  • JS页面延迟执行一些方法(整理)

    一般在JS页面延迟执行一些方法.可以使用以下的方法 jQuery.delay()方法简介 http://shawphy.com/2010/11/jquery-delay.html jQuery中queue和dequeue的用法 http://www.jb51.net/article/25481.htm Window.setTimeout http://www.jb51.net/article/20741.htm以下是我用到的一些例子. 复制代码 代码如下: //延迟查询,传一个查询btn的ID,

  • Jquery图片延迟加载插件jquery.lazyload.js的使用方法

    最新版的jquery.lazyload.js已不再是伪的延迟加载了 一.请按照基本使用方法说明设置 复制代码 代码如下: //载入JavaScript 文件<script src="jquery.js" type="text/javascript"></script><script src="jquery.lazyload.js" type="text/javascript"></sc

  • JS作用域闭包、预解释和this关键字综合实例解析

    本文实例分析了JS作用域闭包.预解释和this关键字.分享给大家供大家参考,具体如下: var number = 2; var obj = {number : 5, fn1 : ( function() { this.number *= 2; number=number*2; var number=3; return function() { this.number *= 2; number*=3; alert(number); } } )() }; var fn1 = obj.fn1; ale

  • 几句话带你理解JS中的this、闭包、原型链

    原型链 所有对象都是基于Object.prototype,Object.prototype就是JavaScript的根对象,在Object.prototype中定义的方法都可以被其它对象访问到,当然也可以被重写了,所以直接在Object.prototype上调用的是原始功能的toString()方法,该方法会放回参数对象的内置属性[[class]]的值,这个值是个字符串,比如'[Object String]' 要理解原型链机制的话,首先得知道根本原因:JavaScript中的对象都有一个内置属性

  • Js实现手机发送验证码时按钮延迟操作

    实例代码记录: <script type="text/javascript"> function start_sms_button(obj){ var count = 1 ; var sum = 30; var i = setInterval(function(){ if(count > 10){ obj.attr('disabled',false); obj.val('发送验证码'); clearInterval(i); }else{ obj.val('剩余'+pa

  • JS闭包与延迟求值用法示例

    本文实例讲述了JS闭包与延迟求值用法.分享给大家供大家参考,具体如下: var bigFunctionA = function(){ var s = 0; for(var i=0;i<10000;i++){ s += i; } return s; } var bigFunctionB = function(){ var s = "a"; for(var i=0;i<100;i++){ s += i; } return s; } function RandomThrow(s1

随机推荐