如何才能通俗易懂的解释js里面的‘闭包’?

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 函数),能够实现跨做用域访问变量了(外部访问内部)。所以上面的这种说法完整的应该理解为:spa

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

 

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

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

function user () {
  var name = 'wangxi'
  return name
}

var userName = user()
console.log(userName) // wangxi

:这是闭包吗?blog

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

可是使用闭包就相反,函数执行完,生命周期结束,可是经过闭包引用的外层做用域内的变量依然存在,而且将一直存在,直到执行闭包的的做用域被销毁,这里的局部变量才会被销毁(若是在全局环境下引用了闭包,则只有在全局环境被销毁,好比程序结束、浏览器关闭等行为时才会销毁闭包引用的做用域)。所以为了不闭包形成的内存损耗,建议在使用闭包后手动销毁。仍是上面示例二的例子,稍做修改:ip

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中的函数运行在它们被定义的做用域里,而不是它们被执行的做用域里。" ——《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 中闭包的理解,若是有误,欢迎指正。最后引用一段知乎问题下关于闭包概念的一个回答。

做者:萧潇 连接:https://www.zhihu.com/question/34547104/answer/197642727

什么是闭包?

简单来讲,闭包是指能够访问另外一个函数做用域变量的函数,通常是定义在外层函数中的内层函数。

为何须要闭包?

局部变量没法共享和长久的保存,而全局变量可能形成变量污染,因此咱们但愿有一种机制既能够长久的保存变量又不会形成全局污染。

特色

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

什么时候使用?

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

如何使用?

  1. 定义外层函数,封装被保护的局部变量。
  2. 定义内层函数,执行对外部函数变量的操做。
  3. 外层函数返回内层函数的对象,而且外层函数被调用,结果保存在一个全局的变量中。
相关文章
相关标签/搜索