通俗易懂地解释JS中的闭包
1."闭包就是跨作用域访问变量。"
【示例一】
varname='wangxi' functionuser(){ //varname='wangxi' functiongetName(){ console.log(name) } getName() } user()//wangxi
在getName函数中获取name,首先在getName函数的作用域中查找name,未找到,进而在user函数的作用域中查找,同样未找到,继续向上回溯,发现在全局作用域中存在name,因此获取name值并打印。这里很好理解,即变量都存在在指定的作用域中,如果在当前作用中找不到想要的变量,则通过作用域链向在父作用域中继续查找,直到找到第一个同名的变量为止(或找不到,抛出ReferenceError错误)。这是js中作用域链的概念,即子作用域可以根据作用域链访问父作用域中的变量,那如果相反呢,在父作用域想访问子作用域中的变量呢?——这就需要通过闭包来实现。
【示例二】
functionuser(){ varname='wangxi' returnfunctiongetName(){ returnname } } varuserName=user()() console.log(userName)//wangxi
分析代码我们知道,name是存在于user函数作用域内的局部变量,正常情况下,在外部作用域(这里是全局)中是无法访问到name变量的,但是通过闭包(返回一个包含变量的函数,这里是getName函数),可以实现跨作用域访问变量了(外部访问内部)。因此上面的这种说法完整的应该理解为:
闭包就是跨作用域访问变量——内部作用域可以保持对外部作用域中变量的引用从而使得(更)外部作用域可以访问内部作用域中的变量。(还是不理解的话看下一条分析)
2."闭包:在爷爷的环境中执行了爸爸,爸爸中返回了孙子,本来爸爸被执行完了,爸爸的环境应该被清除掉,但是孙子引用了爸爸的环境,导致爸爸释放不了。这一坨就是闭包。简单来讲,闭包就是一个引用了父环境的对象,并且从父环境中返回到更高层的环境中的一个对象。"
这个怎么理解呢?首先看下方代码:
【示例三】
functionuser(){ varname='wangxi' returnname } varuserName=user() console.log(userName)//wangxi
问:这是闭包吗?
答:当然不是。首先要明白闭包是什么。虽然这里形式上看好像也是在全局作用域下访问了user函数内的局部变量name,但是问题是,user执行完,name也随之被销毁了,即函数内的局部变量的生命周期仅存在于函数的声明周期内,函数被销毁,函数内的变量也自动被销毁。
但是使用闭包就相反,函数执行完,生命周期结束,但是通过闭包引用的外层作用域内的变量依然存在,并且将一直存在,直到执行闭包的的作用域被销毁,这里的局部变量才会被销毁(如果在全局环境下引用了闭包,则只有在全局环境被销毁,比如程序结束、浏览器关闭等行为时才会销毁闭包引用的作用域)。因此为了避免闭包造成的内存损耗,建议在使用闭包后手动销毁。还是上面示例二的例子,稍作修改:
【示例四】
functionuser(){ varname='wangxi' returnfunctiongetName(){ returnname } } varuserName=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中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里."
这句话对闭包中对变量的引用的理解很有帮助。我们看下面的例子:
varname='Schopenhauer' functiongetName(){ console.log(name) } functionmyName(){ varname='wangxi' getName() } myName()//Schopenhauer
如果执行myName()输出的结果和你想象的不一样,你就要再回去看看上面说的这句话了,
JavaScript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里
执行myName,函数内部执行了getName,而getName是在全局环境下定义的,因此尽管在myName中定义了变量name,对getName的执行并无影响,getName中打印的依然是全局作用域下的name。
我们稍微改一下代码:
varname='Schopenhauer' functiongetName(){ varname='Aristotle' varintro=function(){//这是一个闭包 console.log('Iam'+name) } returnintro } functionshowMyName(){ varname='wangxi' varmyName=getName() myName() } showMyName()//IamAristotle
结果和你想象的一样吗?结果留作聪明的你自己分析~
以上就是对js中闭包的理解,如果有误,欢迎指正。最后引用一段知乎问题下关于闭包概念的一个回答。
什么是闭包?
简单来说,闭包是指可以访问另一个函数作用域变量的函数,一般是定义在外层函数中的内层函数。
为什么需要闭包?
局部变量无法共享和长久的保存,而全局变量可能造成变量污染,所以我们希望有一种机制既可以长久的保存变量又不会造成全局污染。
特点
- 占用更多内存
- 不容易被释放
何时使用?
变量既想反复使用,又想避免全局污染
如何使用?
- 定义外层函数,封装被保护的局部变量。
- 定义内层函数,执行对外部函数变量的操作。
- 外层函数返回内层函数的对象,并且外层函数被调用,结果保存在一个全局的变量中。
好了,以上所述是小编给大家介绍的js中的闭包,希望对大家有所帮助!