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


Posted in Javascript onOctober 10, 2016

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

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

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 被调用的时候,就会创建一个对应的执行环境和作用域链,如下图所示:

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

当 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 函数被调用的时候,产生的作用域链如下所示:

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

当匿名函数被 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

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

Javascript 相关文章推荐
IE事件对象(The Internet Explorer Event Object)
Jun 27 Javascript
JavaScript实现仿网易通行证表单验证
May 25 Javascript
简述JavaScript中正则表达式的使用方法
Jun 15 Javascript
JS实现霓虹灯文字效果的方法
Aug 06 Javascript
跟我学习javascript的执行上下文
Nov 18 Javascript
jQuery simplePage+AJAX plus分页插件用法实例
Feb 17 Javascript
jQuery购物车插件jsorder用法(支持后台处理程序直接转换成DataTable处理)
Jun 08 Javascript
AngularJS ng-bind 指令简单实现
Jul 30 Javascript
JavaScript 监控微信浏览器且自带返回按钮时间
Nov 27 Javascript
bootstrap常用组件之头部导航实现代码
Apr 20 Javascript
详解vue2.0模拟后台json数据
May 16 Javascript
通过实例解析vuejs如何实现调试代码
Jul 16 Javascript
Javascript 事件冒泡机制详细介绍
Oct 10 #Javascript
JS 滚动事件window.onscroll与position:fixed写兼容IE6的回到顶部组件
Oct 10 #Javascript
javaScript 事件绑定、事件冒泡、事件捕获和事件执行顺序整理总结
Oct 10 #Javascript
jQuery EasyUI tree 使用拖拽时遇到的错误小结
Oct 10 #Javascript
jQuery继承extend用法详解
Oct 10 #Javascript
mvc 、bootstrap 结合分布式图简单实现分页
Oct 10 #Javascript
针对后台列表table拖拽比较实用的jquery拖动排序
Oct 10 #Javascript
You might like
提高PHP编程效率 引入缓存机制提升性能
2010/02/15 PHP
php生成毫秒时间戳的实例讲解
2017/09/22 PHP
YII2框架中使用RBAC对模块,控制器,方法的权限控制及规则的使用示例
2020/03/18 PHP
JqGrid web打印实现代码
2011/05/31 Javascript
ASP.NET jQuery 实例12 通过使用jQuery validation插件简单实现用户注册页面验证功能
2012/02/03 Javascript
javascript学习笔记(二十) 获得和设置元素的特性(属性)
2012/06/20 Javascript
BootStrap的alert提示框的关闭后再显示怎么解决
2016/05/17 Javascript
利用Node.js检测端口是否被占用的方法
2017/12/07 Javascript
小程序自定义模板实现吸顶功能
2020/01/08 Javascript
JavaScript DOM常用操作代码汇总
2020/07/03 Javascript
[45:46]2014 DOTA2国际邀请赛中国区预选赛5.21 HGT VS DT
2014/05/23 DOTA
[54:27]TNC vs Serenity 2018国际邀请赛小组赛BO2 第一场 8.18
2018/08/19 DOTA
定制FileField中的上传文件名称实例
2017/08/23 Python
python Pandas 读取txt表格的实例
2018/04/29 Python
解决pandas使用read_csv()读取文件遇到的问题
2018/06/15 Python
Python3.5 + sklearn利用SVM自动识别字母验证码方法示例
2019/05/10 Python
pandas删除行删除列增加行增加列的实现
2019/07/06 Python
python线程的几种创建方式详解
2019/08/29 Python
opencv-python 读取图像并转换颜色空间实例
2019/12/09 Python
Python Selenium安装及环境配置的实现
2020/03/17 Python
在python里创建一个任务(Task)实例
2020/04/25 Python
Python编写memcached启动脚本代码实例
2020/08/14 Python
python Protobuf定义消息类型知识点讲解
2021/03/02 Python
ProBikeKit美国官网:自行车套件,跑步和铁人三项套件
2016/10/13 全球购物
请写出一段Python代码实现删除一个list里面的重复元素
2015/12/29 面试题
西安交大自主招生自荐信
2014/01/27 职场文书
初中生自我评价
2014/02/01 职场文书
党的群众路线学习材料
2014/05/16 职场文书
党员群众路线承诺书
2014/05/20 职场文书
服务承诺书格式
2014/05/21 职场文书
运动会横幅标语
2014/06/17 职场文书
测绘工程专业求职信
2014/07/15 职场文书
整改通知书
2015/04/20 职场文书
python中24小时制转换为12小时制的方法
2021/06/18 Python
关于Nginx中虚拟主机的一些冷门知识小结
2022/03/03 Servers
python pandas 解析(读取、写入)CSV 文件的操作方法
2022/12/24 Python