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 FF OPERA都可用的弹出层实现代码
Sep 29 Javascript
extjs grid设置某列背景颜色和字体颜色的实现方法
Sep 06 Javascript
封装的jquery翻页滚动(示例代码)
Nov 18 Javascript
响应式表格之固定表头的简单实现
Aug 26 Javascript
Vue.js中数组变动的检测详解
Oct 12 Javascript
BootStrap tab选项卡使用小结
Aug 09 Javascript
Node.js中的require.resolve方法使用简介
Apr 23 Javascript
详解Vue 事件修饰符capture 的使用
Dec 29 Javascript
vue移动端城市三级联动组件使用详解
Jul 26 Javascript
JS数组进阶示例【数组的几种函数用法】
Jan 16 Javascript
Vue实现省市区三级联动
Dec 27 Vue.js
Angular性能优化之第三方组件和懒加载技术
May 10 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
教你如何开启shopnc b2b2c 伪静态
2014/10/21 PHP
PHP curl伪造IP地址和header信息代码实例
2015/04/27 PHP
php简单复制文件的方法
2016/05/09 PHP
javascript parseInt与Number函数的区别
2010/01/21 Javascript
javascript setAttribute, getAttribute 在不同浏览器上的不同表现
2010/08/05 Javascript
extjs grid设置某列背景颜色和字体颜色的方法
2010/09/03 Javascript
JavaScript OOP面向对象介绍
2010/12/02 Javascript
jQuery 对Select的操作备忘记录
2011/07/04 Javascript
在jQuery ajax中按钮button和submit的区别分析
2012/10/07 Javascript
jQuery获取选中内容及设置元素属性的方法
2014/07/09 Javascript
jquery实现拖动效果
2016/08/10 Javascript
JS实现表单多文件上传样式美化支持选中文件后删除相关项
2016/09/30 Javascript
JS实现表单验证功能(验证手机号是否存在,验证码倒计时)
2016/10/11 Javascript
JS实现json的序列化和反序列化功能示例
2017/06/13 Javascript
认识jQuery的Promise的具体使用方法
2017/10/10 jQuery
react-native-video实现视频全屏播放的方法
2018/03/19 Javascript
socket在egg中的使用实例代码详解
2019/05/30 Javascript
微信小程序实现蒙版弹出窗功能
2019/09/17 Javascript
js 计算月/周的第一天和最后一天代码
2020/02/01 Javascript
vue cli4.0项目引入typescript的方法
2020/07/17 Javascript
python输出指定月份日历的方法
2015/04/23 Python
Python反射用法实例简析
2017/12/22 Python
python使用sklearn实现决策树的方法示例
2019/09/12 Python
用于ETL的Python数据转换工具详解
2020/07/21 Python
美国乡村商店:Plow & Hearth
2016/09/12 全球购物
英国女装网上商店:I Saw It First
2018/10/18 全球购物
装潢设计专业推荐信模板
2013/11/26 职场文书
国贸专业大学生职业生涯规划范文
2014/01/10 职场文书
迟到检讨书400字
2014/01/13 职场文书
见习期自我鉴定
2014/01/31 职场文书
2014年道德讲堂实施方案
2014/03/05 职场文书
金融系毕业生自荐书
2014/07/08 职场文书
学习普通话的体会
2014/11/07 职场文书
2015年中学元旦晚会活动方案
2014/12/09 职场文书
三潭印月的导游词
2015/02/12 职场文书
Python道路车道线检测的实现
2021/06/27 Python