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 相关文章推荐
extjs form textfield的隐藏方法
Dec 29 Javascript
微信JSSDK上传图片
Aug 23 Javascript
jQuery实现有动画淡出效果的二级折叠菜单代码
Oct 17 Javascript
js实现根据身份证号自动生成出生日期
Dec 15 Javascript
购物车前端开发(jQuery和bootstrap3)
Aug 27 Javascript
jQuery表单验证简单示例
Oct 17 Javascript
JS实现重新加载当前页面或者父页面的几种方法
Nov 30 Javascript
vue.js绑定class和style样式(6)
Dec 09 Javascript
JavaScript简单计算人的年龄示例
Apr 15 Javascript
Angular2 组件间通过@Input @Output通讯示例
Aug 24 Javascript
vue.js将时间戳转化为日期格式的实现代码
Jun 05 Javascript
vue awesome swiper异步加载数据出现的bug问题
Jul 03 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+Mysql日期时间如何转换(UNIX时间戳和格式化日期)
2012/07/15 PHP
php mysql PDO 查询操作的实例详解
2017/09/23 PHP
学习js所必须要知道的一些
2007/03/07 Javascript
jQuery ui1.7 dialog只能弹出一次问题
2009/08/27 Javascript
利用Jquery实现可多选的下拉框
2014/02/21 Javascript
网页广告中JS代码的信息监听示例
2014/04/02 Javascript
javascript属性访问表达式用法分析
2015/04/25 Javascript
JS for循环中i++ 和 ++i的区别介绍
2016/07/20 Javascript
AngularJS入门教程之双向绑定详解
2016/08/18 Javascript
浅谈js中的this问题
2017/08/31 Javascript
javascript 判断用户有没有操作页面
2017/10/17 Javascript
JS 中使用Promise 实现红绿灯实例代码(demo)
2017/10/20 Javascript
angular中不同的组件间传值与通信的方法
2017/11/04 Javascript
javascript修改浏览器title方法 JS动态修改浏览器标题
2017/11/30 Javascript
vue 微信授权登录解决方案
2018/04/10 Javascript
Vue实现table上下移动功能示例
2019/02/21 Javascript
微信公众平台 客服接口发消息的实现代码(Java接口开发)
2019/04/17 Javascript
微信小程序 自定义复选框实现代码实例
2019/09/04 Javascript
[01:00:59]VP VS VG Supermajor小组赛胜者组第二轮 BO3第二场 6.2
2018/06/03 DOTA
Python 性能优化技巧总结
2016/11/01 Python
python实现简单聊天应用 python群聊和点对点均实现
2017/09/14 Python
python编程通过蒙特卡洛法计算定积分详解
2017/12/13 Python
python如何查看微信消息撤回
2018/11/27 Python
Python去除字符串前后空格的几种方法
2019/03/04 Python
使用Python Pandas处理亿级数据的方法
2019/06/24 Python
python搜索包的路径的实现方法
2019/07/19 Python
keras小技巧——获取某一个网络层的输出方式
2020/05/23 Python
boostrap modal 闪现问题的解决方法
2020/09/01 HTML / CSS
美国标志性加大尺码时装品牌:Ashley Stewart
2016/12/15 全球购物
NFL官方在线商店:NFLShop
2020/07/29 全球购物
英文请假条
2014/04/11 职场文书
云冈石窟导游词
2015/02/04 职场文书
2016年师德师风学习心得体会
2016/01/12 职场文书
研究生学习计划书应该怎么写?
2019/09/10 职场文书
Mysql服务添加 iptables防火墙策略的方案
2021/04/29 MySQL
redis复制有可能碰到的问题汇总
2022/04/03 Redis