通俗易懂地解释JS中的闭包


Posted in Javascript onOctober 23, 2017

1. "闭包就是跨作用域访问变量。"

【示例一】

var name = 'wangxi'
function user () {
 // var name = 'wangxi'
 function getName () {
 console.log(name)
 }
 getName()
}
user() // wangxi

在 getName 函数中获取 name,首先在 getName 函数的作用域中查找 name,未找到,进而在 user 函数的作用域中查找,同样未找到,继续向上回溯,发现在全局作用域中存在 name,因此获取 name 值并打印。这里很好理解,即变量都存在在指定的作用域中,如果在当前作用中找不到想要的变量,则通过作用域链向在父作用域中继续查找,直到找到第一个同名的变量为止(或找不到,抛出 ReferenceError 错误)。这是 js 中作用域链的概念,即子作用域可以根据作用域链访问父作用域中的变量,那如果相反呢,在父作用域想访问子作用域中的变量呢?——这就需要通过闭包来实现。

【示例二】

function user () {
 var name = 'wangxi'
 return function getName () {
 return name
 }
}
var userName = user()()
console.log(userName) // wangxi

分析代码我们知道,name 是存在于 user 函数作用域内的局部变量,正常情况下,在外部作用域(这里是全局)中是无法访问到 name 变量的,但是通过闭包(返回一个包含变量的函数,这里是 getName 函数),可以实现跨作用域访问变量了(外部访问内部)。因此上面的这种说法完整的应该理解为:

闭包就是跨作用域访问变量 —— 内部作用域可以保持对外部作用域中变量的引用从而使得(更)外部作用域可以访问内部作用域中的变量。(还是不理解的话看下一条分析)

2. "闭包:在爷爷的环境中执行了爸爸,爸爸中返回了孙子,本来爸爸被执行完了,爸爸的环境应该被清除掉,但是孙子引用了爸爸的环境,导致爸爸释放不了。这一坨就是闭包。简单来讲,闭包就是一个引用了父环境的对象,并且从父环境中返回到更高层的环境中的一个对象。"

这个怎么理解呢?首先看下方代码:

【示例三】

function user () {
 var name = 'wangxi'
 return name
}
var userName = user()
console.log(userName) // wangxi

问:这是闭包吗?

答:当然不是。首先要明白闭包是什么。虽然这里形式上看好像也是在全局作用域下访问了 user 函数内的局部变量 name,但是问题是,user 执行完,name 也随之被销毁了,即函数内的局部变量的生命周期仅存在于函数的声明周期内,函数被销毁,函数内的变量也自动被销毁。

但是使用闭包就相反,函数执行完,生命周期结束,但是通过闭包引用的外层作用域内的变量依然存在,并且将一直存在,直到执行闭包的的作用域被销毁,这里的局部变量才会被销毁(如果在全局环境下引用了闭包,则只有在全局环境被销毁,比如程序结束、浏览器关闭等行为时才会销毁闭包引用的作用域)。因此为了避免闭包造成的内存损耗,建议在使用闭包后手动销毁。还是上面示例二的例子,稍作修改:

【示例四】

function user () {
 var name = 'wangxi'
 return function getName () {
 return name
 }
}
var userName = user()() // userName 变量中始终保持着对 name 的引用
console.log(userName) // wangxi
userName = null // 销毁闭包,释放内存

【为什么 user()() 是两个括号:执行 user()  返回的是 getName 函数,要想获得 name 变量,需要对返回的 getName 函数执行一次,所以是 user()()】

根据观点2,分析一下代码:在全局作用域下创建了 userName 变量(爷爷),保存了对 user 函数最终返回结果的引用(即局部变量 name 的值),执行 user()()(爸爸),返回了 name(孙子),正常情况下,在执行了 user()() 之后,user 的环境(爸爸)应该被清除掉,但是因为返回的结果 name(孙子)引用了爸爸的环境(因为 name 本来就是存在于 user 的作用域内的),导致 user 的环境无法被释放(会造成内存损耗)。

那么【"闭包就是一个引用了父环境的对象,并且从父环境中返回到更高层的环境中的一个对象。"】如何理解?

我们换个说法:如果一个函数引用了父环境中的对象,并且在这个函数中把这个对象返回到了更高层的环境中,那么,这个函数就是闭包。

还是看上面的例子:

getName 函数中引用了 user(父)环境中的对象(变量 name),并且在函数中把 name 变量返回到了全局环境(更高层的环境)中,因此,getName 就是闭包。

3. "JavaScript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里."

这句话对闭包中对变量的引用的理解很有帮助。我们看下面的例子:

var name = 'Schopenhauer'
function getName () {
 console.log(name)
}
function myName () {
 var name = 'wangxi'
 getName()
}
myName() // Schopenhauer

如果执行 myName() 输出的结果和你想象的不一样,你就要再回去看看上面说的这句话了,

JavaScript 中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里

执行 myName,函数内部执行了 getName,而 getName 是在全局环境下定义的,因此尽管在 myName 中定义了变量 name,对getName 的执行并无影响,getName 中打印的依然是全局作用域下的 name。

我们稍微改一下代码:

var name = 'Schopenhauer'
function getName () {
  var name = 'Aristotle'
 var intro = function() { // 这是一个闭包
  console.log('I am ' + name)
 }
 return intro
}
function showMyName () {
 var name = 'wangxi'
 var myName = getName()
 myName()
}
showMyName() // I am Aristotle

结果和你想象的一样吗?结果留作聪明的你自己分析~

以上就是对 js 中闭包的理解,如果有误,欢迎指正。最后引用一段知乎问题下关于闭包概念的一个回答。

什么是闭包?

简单来说,闭包是指可以访问另一个函数作用域变量的函数,一般是定义在外层函数中的内层函数。

为什么需要闭包?

局部变量无法共享和长久的保存,而全局变量可能造成变量污染,所以我们希望有一种机制既可以长久的保存变量又不会造成全局污染。

特点

  • 占用更多内存
  • 不容易被释放

何时使用?

变量既想反复使用,又想避免全局污染

如何使用?

  1. 定义外层函数,封装被保护的局部变量。
  2. 定义内层函数,执行对外部函数变量的操作。
  3. 外层函数返回内层函数的对象,并且外层函数被调用,结果保存在一个全局的变量中。

好了,以上所述是小编给大家介绍的js中的闭包,希望对大家有所帮助!

Javascript 相关文章推荐
高效的表格行背景隔行变色及选定高亮的JS代码
Dec 04 Javascript
关于全局变量和局部变量的那些事
Jan 11 Javascript
url参数中有+、空格、=、%、&、#等特殊符号的问题解决
May 15 Javascript
js实现倒计时(距离结束还有)示例代码
Jul 24 Javascript
jQuery替换字符串(实例代码)
Nov 13 Javascript
jquery动态更换设置背景图的方法
Mar 25 Javascript
js对象基础实例分析
Jan 13 Javascript
js实现随屏幕滚动的带缓冲效果的右下角广告代码
Sep 04 Javascript
JS正则表达式修饰符中multiline(/m)用法分析
Dec 27 Javascript
AngularJS动态添加数据并删除的实例
Feb 27 Javascript
jQuery实现input[type=file]多图预览上传删除等功能
Aug 02 jQuery
微信小程序自定义底部弹出框动画
Nov 18 Javascript
AngularJS 教程及实例代码
Oct 23 #Javascript
浅谈Koa服务限流方法实践
Oct 23 #Javascript
浅谈vue中使用图片懒加载vue-lazyload插件详细指南
Oct 23 #Javascript
angularjs实现猜大小功能
Oct 23 #Javascript
详解在vue-cli项目中使用mockjs(请求数据删除数据)
Oct 23 #Javascript
angularjs实现天气预报功能
Jun 16 #Javascript
angular.js实现购物车功能
Oct 23 #Javascript
You might like
PHP Smarty生成EXCEL文档的代码
2008/08/23 PHP
Uchome1.2 1.5 代码学习 common.php
2009/04/24 PHP
解析PHP高效率写法(详解原因)
2013/06/20 PHP
PHP SplObjectStorage使用实例
2015/05/12 PHP
php性能分析之php-fpm慢执行日志slow log用法浅析
2016/10/17 PHP
用PHP将Unicode 转化为UTF-8的实现方法(推荐)
2017/02/08 PHP
PHP+redis实现微博的拉模型案例详解
2019/07/10 PHP
各种常用的JS函数整理
2013/10/25 Javascript
node.js中的fs.chown方法使用说明
2014/12/16 Javascript
jQuery检测某个元素是否存在代码分享
2015/07/09 Javascript
JavaScript操作class和style样式代码详解
2016/02/13 Javascript
浅谈angularJS中的事件
2016/07/12 Javascript
使用Fullpage插件快速开发整屏翻页的页面
2017/09/13 Javascript
shiro授权的实现原理
2017/09/21 Javascript
vue中实现滚动加载更多的示例
2017/11/08 Javascript
vue.js整合mint-ui里的轮播图实例代码
2017/12/27 Javascript
layui表格内容溢出的解决方法
2019/09/06 Javascript
Vue利用Blob下载原生二进制数组文件
2019/09/25 Javascript
Element InfiniteScroll无限滚动的具体使用方法
2020/07/27 Javascript
uni-app使用countdown插件实现倒计时
2020/11/01 Javascript
微信小程序实现分页加载效果
2020/11/19 Javascript
一个小示例告诉你Python语言的优雅之处
2014/07/04 Python
简单介绍Python的轻便web框架Bottle
2015/04/08 Python
Python使用tablib生成excel文件的简单实现方法
2016/03/16 Python
python 计算两个日期相差多少个月实例代码
2017/05/24 Python
jupyter notebook引用from pyecharts.charts import Bar运行报错
2020/04/23 Python
python 实现对数据集的归一化的方法(0-1之间)
2018/07/17 Python
Python Django 封装分页成通用的模块详解
2019/08/21 Python
对Python中一维向量和一维向量转置相乘的方法详解
2019/08/26 Python
python实现输入任意一个大写字母生成金字塔的示例
2019/10/27 Python
python利用递归方法实现求集合的幂集
2020/09/07 Python
秋季运动会稿件
2014/01/30 职场文书
《黄山奇石》教学反思
2014/04/19 职场文书
中秋节国旗下演讲稿
2014/09/05 职场文书
2014年德育工作总结
2014/11/20 职场文书
房产公证书
2015/01/23 职场文书