JavaScript中闭包的详解


Posted in Javascript onApril 01, 2017

闭包是什么

在 JavaScript 中,闭包是一个让人很难弄懂的概念。ECMAScript 中给闭包的定义是:闭包,指的是词法表示包括不被计算的变量的函数,也就是说,函数可以使用函数之外定义的变量。

是不是看完这个定义感觉更加懵逼了?别急,我们来分析一下。

  • 闭包是一个函数
  • 闭包可以使用在它外面定义的变量
  • 闭包存在定义该变量的作用域中

好像有点清晰了,但是使用在它外面定义的变量是什么意思,我们先来看看变量作用域。

变量作用域

变量可分为全局变量和局部变量。全局变量的作用域就是全局性的,在 js 的任何地方都可以使用全局变量。在函数中使用 var 关键字声明变量,这时的变量即是局部变量,它的作用域只在声明该变量的函数内,在函数外面是访问不到该变量的。

var func = function(){
  var a = 'linxin';
  console.log(a);     // linxin
}
func();
console.log(a);       // Uncaught ReferenceError: a is not defined

作用域相对比较简单,我们不多讲,来看看跟闭包关系比较大的变量生存周期。

变量生存周期

全局变量,生命周期是永久的。局部变量,当定义该变量的函数调用结束时,该变量就会被垃圾回收机制回收而销毁。再次调用该函数时又会重新定义了一个新变量。

var func = function(){
  var a = 'linxin';
  console.log(a);
}
func();

a 为局部变量,在 func 调用完之后,a 就会被销毁了。

var func = function(){
  var a = 'linxin';
  var func1 = function(){
    a += ' a';
    console.log(a);
  }
  return func1;
}
var func2 = func();
func2();          // linxin a
func2();          // linxin a a
func2();          // linxin a a a

可以看出,在第一次调用完 func2 之后,func 中的变量 a 变成 'linxin a',而没有被销毁。因为此时 func1 形成了一个闭包,导致了 a 的生命周期延续了。

这下子闭包就比较明朗了。

  • 闭包是一个函数,比如上面的 func1 函数
  • 闭包使用其他函数定义的变量,使其不被销毁。比如上面 func1 调用了变量 a
  • 闭包存在定义该变量的作用域中,变量 a 存在 func 的作用域中,那么 func1 也必然存在这个作用域中。

现在可以说,满足这三个条件的就是闭包了。

下面我们通过一个简单而又经典的例子来进一步熟悉闭包。

for (var i = 0; i < 4; i++) {
  setTimeout(function () {
    console.log(i)
  }, 0)
}

我们可能会简单的以为控制台会打印出 0 1 2 3,可事实却打印出了 4 4 4 4,这又是为什么呢?我们发现,setTimeout 函数时异步的,等到函数执行时,for循环已经结束了,此时的 i 的值为 4,所以 function() { console.log(i) } 去找变量 i,只能拿到 4。

我们想起上一个例子中,闭包使 a 变量的值被保存起来了,那么这里我们也可以用闭包把 0 1 2 3 保存起来。

for (var i = 0; i < 4; i++) {
  (function (i) {
    setTimeout(function () {
      console.log(i)
    }, 0)
  })(i)
}

当 i=0 时,把 0 作为参数传进匿名函数中,此时 function(i){} 此匿名函数中的 i 的值为 0,等到 setTimeout 执行时顺着外层去找 i,这时就能拿到 0。如此循环,就能拿到想要的 0 1 2 3。

内存管理

在闭包中调用局部变量,会导致这个局部变量无法及时被销毁,相当于全局变量一样会一直占用着内存。如果需要回收这些变量占用的内存,可以手动将变量设置为null。

然而在使用闭包的过程中,比较容易形成 JavaScript 对象和 DOM 对象的循环引用,就有可能造成内存泄露。这是因为浏览器的垃圾回收机制中,如果两个对象之间形成了循环引用,那么它们都无法被回收。

function func() {
  var test = document.getElementById('test');
  test.onclick = function () {
    console.log('hello world');
  }
}

在上面例子中,func 函数中用匿名函数创建了一个闭包。变量 test 是 JavaScript 对象,引用了 id 为 test 的 DOM 对象,DOM 对象的 onclick 属性又引用了闭包,而闭包又可以调用 test ,因而形成了循环引用,导致两个对象都无法被回收。要解决这个问题,只需要把循环引用中的变量设为 null 即可。

function func() {
  var test = document.getElementById('test');
  test.onclick = function () {
    console.log('hello world');
  }
  test = null;
}

如果在 func 函数中不使用匿名函数创建闭包,而是通过引用一个外部函数,也不会出现循环引用的问题。

function func() {
  var test = document.getElementById('test');
  test.onclick = funcTest;
}
function funcTest(){
  console.log('hello world');
}

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持三水点靠木!

Javascript 相关文章推荐
struts2+jquery+json实现异步加载数据(自写)
Jun 24 Javascript
jquery uploadify 在FF下无效的解决办法
Sep 26 Javascript
js实现九宫格图片半透明渐显特效的方法
Feb 16 Javascript
javascript中一些util方法汇总
Jun 10 Javascript
javascript实现给定半径求出圆的面积
Jun 26 Javascript
js HTML5多图片上传及预览实例解析(不含前端的文件分割)
Aug 26 Javascript
Javascript使用uploadify来实现多文件上传
Nov 16 Javascript
深入理解Javascript中的作用域链和闭包
Apr 25 Javascript
bootstrap响应式导航条模板使用详解(含下拉菜单,弹出框)
Nov 17 Javascript
vue插槽slot的理解和使用方法
Apr 03 Javascript
Vue将props值实时传递 并可修改的操作
Aug 09 Javascript
nginx配置域名后的二级目录访问不同项目的配置操作
Nov 06 Javascript
基于JavaScript实现验证码功能
Apr 01 #Javascript
AngularJS1.X学习笔记2-数据绑定详解
Apr 01 #Javascript
Angularjs使用指令做表单校验的方法
Mar 31 #Javascript
JS正则获取HTML元素的方法
Mar 31 #Javascript
JS+CSS实现下拉刷新/上拉加载插件
Mar 31 #Javascript
ES6中Generator与异步操作实例分析
Mar 31 #Javascript
微信公众号菜单配置微信小程序实例详解
Mar 31 #Javascript
You might like
一个oracle+PHP的查询的例子
2006/10/09 PHP
解析PHP强制转换类型及远程管理插件的安全隐患
2014/06/30 PHP
PHP生成二维码的两个方法和实例
2014/07/01 PHP
php实现将数据做成json的格式给前端使用
2018/08/21 PHP
非常棒的10款jQuery 幻灯片插件
2011/06/14 Javascript
通过Javascript创建一个选择文件的对话框代码
2012/06/16 Javascript
jquery实现鼠标拖拽滑动效果来选择数字的方法
2015/05/04 Javascript
利用jQuery实现CheckBox全选/全不选/反选的简单代码
2016/05/31 Javascript
jQuery stop()用法实例详解
2016/07/28 Javascript
详解vue.js全局组件和局部组件
2017/04/10 Javascript
Angularjs中的验证input输入框只能输入数字和小数点的写法(推荐)
2017/08/16 Javascript
微信小程序自定义轮播图
2018/11/04 Javascript
小程序页面动态配置实现方法
2019/02/05 Javascript
深入了解响应式React Native Echarts组件
2019/05/29 Javascript
微信小程序如何调用json数据接口并解析
2019/06/29 Javascript
[03:40]DOTA2抗疫特别篇《英雄年代》
2020/02/28 DOTA
Python中__init__和__new__的区别详解
2014/07/09 Python
Python实现字符串逆序输出功能示例
2017/06/24 Python
python数字图像处理实现直方图与均衡化
2018/05/04 Python
python的scikit-learn将特征转成one-hot特征的方法
2018/07/10 Python
Python Matplotlib实现三维数据的散点图绘制
2019/03/19 Python
python2使用bs4爬取腾讯社招过程解析
2019/08/14 Python
Django连接数据库并实现读写分离过程解析
2019/11/13 Python
Python图像处理库PIL的ImageGrab模块介绍详解
2020/02/26 Python
在服务器上安装python3.8.2环境的教程详解
2020/04/26 Python
如何基于Python和Flask编写Prometheus监控
2020/11/25 Python
Python利用imshow制作自定义渐变填充柱状图(colorbar)
2020/12/10 Python
怀俄明州飞钓:Platte River Fly Shop
2017/12/28 全球购物
Osklen官方在线商店:巴西服装品牌
2019/04/25 全球购物
新西兰最大、占有率最高的综合性药房:PharmacyDirect药房中文网
2020/11/03 全球购物
实习生的自我评价
2014/01/08 职场文书
大学生个人自荐信样本
2014/03/02 职场文书
大学四年个人自我小结
2014/03/05 职场文书
七夕情人节促销方案
2014/06/07 职场文书
幼儿园五一劳动节活动总结
2015/02/09 职场文书
中学生自我评价2015
2015/03/03 职场文书