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 相关文章推荐
jquery select(列表)的操作(取值/赋值)
Aug 06 Javascript
jQuery UI Dialog 创建友好的弹出对话框实现代码
Apr 12 Javascript
使用JavaScript 实现各种跨域的方法
May 08 Javascript
jQuery.lazyload+masonry改良图片瀑布流代码
Jun 20 Javascript
浅谈Webpack自动化构建实践指南
Dec 18 Javascript
vue.js实现的经典计算器/科学计算器功能示例
Jul 11 Javascript
vue删除html内容的标签样式实例
Sep 13 Javascript
浅谈redux, koa, express 中间件实现对比解析
May 23 Javascript
Vue-cli3.x + axios 跨域方案踩坑指北
Jul 04 Javascript
解决Vue在Tomcat8下部署页面不加载的问题
Nov 12 Javascript
vscode中的vue项目报错Property ‘xxx‘ does not exist on type ‘CombinedVueInstance<{ readyOnly...Vetur(2339)
Sep 11 Javascript
Webpack的Loader和Plugin的区别
Nov 09 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
PHP 七大优势分析
2009/06/23 PHP
PHP中基于ts与nts版本- vc6和vc9编译版本的区别详解
2013/04/26 PHP
PHP中绘制图像的一些函数总结
2014/11/19 PHP
PHP读取文本文件并逐行输出该行使用最多的字符与对应次数的方法
2016/11/25 PHP
Yii框架批量插入数据扩展类的简单实现方法
2017/05/23 PHP
Javascript客户端将指定区域导出到Word、Excel的代码
2008/10/22 Javascript
jQuery使用animate创建动画用法实例
2015/08/07 Javascript
jQuery实现菜单的显示和隐藏功能示例
2018/07/24 jQuery
Vue实现计算器计算效果
2020/08/17 Javascript
微信小程序实现聊天室
2020/08/21 Javascript
vue中watch的用法汇总
2020/12/28 Vue.js
[01:03:36]DOTA2-DPC中国联赛 正赛 VG vs Magma BO3 第二场 1月26日
2021/03/11 DOTA
使用优化器来提升Python程序的执行效率的教程
2015/04/02 Python
python如何使用正则表达式的前向、后向搜索及前向搜索否定模式详解
2017/11/08 Python
Centos7 Python3下安装scrapy的详细步骤
2018/03/15 Python
Python递归函数实例讲解
2019/02/27 Python
用scikit-learn和pandas学习线性回归的方法
2019/06/21 Python
解决py2exe打包后,总是多显示一个DOS黑色窗口的问题
2019/06/21 Python
python tkinter canvas使用实例
2019/11/04 Python
wxpython+pymysql实现用户登陆功能
2019/11/19 Python
OpenCV哈里斯(Harris)角点检测的实现
2020/01/15 Python
python 实现人和电脑猜拳的示例代码
2020/03/02 Python
解决python对齐错误的方法
2020/07/16 Python
python识别验证码的思路及解决方案
2020/09/13 Python
Python timeit模块原理及使用方法
2020/10/10 Python
python获取linux系统信息的三种方法
2020/10/14 Python
pycharm 多行批量缩进和反向缩进快捷键介绍
2021/01/15 Python
使用CSS3来实现滚动视差效果的教程
2015/08/24 HTML / CSS
我爱祖国演讲稿
2014/09/02 职场文书
2015公务员年度考核评语
2015/03/25 职场文书
Python+Appium新手教程
2021/04/17 Python
基于CSS3画一个iPhone
2021/04/21 HTML / CSS
react使用antd的上传组件实现文件表单一起提交功能(完整代码)
2021/06/29 Javascript
Window server中安装Redis的超详细教程
2021/11/17 Redis
Python如何用re模块实现简易tokenizer
2022/05/02 Python
Redis Lua脚本实现ip限流示例
2022/07/15 Redis