likes
comments
collection
share

原来闭包可以这么简单

作者站长头像
站长
· 阅读数 14

引言

在JavaScript编程的世界中,经常会听到一个神秘的词汇——"闭包"。对于初学者来说,闭包可能听起来非常复杂和难以理解。但实际上,闭包是一个非常强大却不那么复杂的概念。在这篇文章中,我们将揭开闭包的神秘面纱,让你明白闭包的工作原理,以及如何简单地使用它~

闭包

接下来我们会根据这一小段实验代码来先简单认识一下闭包~

function foo () {
  var myName = "旭旭"
  let test1 = 1
  let test2 = 2
  var innerBar = {
    getName: function () {
      console.log(test1)
      return myName
    },
    setName: function (newName) {
      myName = newName
    }
  }
   return innerBar
}

var bar = foo()
console.log(bar.getName())
bar.setName('杰哥')
console.log(bar.getName())

大家可以先猜一下这一段代码的运行结果会是什么~

接下来我们通过调用栈的方法直接开始分析:

我们先编译全局,生成全局执行上下文并压入栈底

原来闭包可以这么简单

然后从上到下执行全局,执行到bar处时我们调用了foo函数,编译foo执行上下文压入栈底:

原来闭包可以这么简单

然后我们的bar获取了foo的返回值对象,此时bar = {},此时foo的调用结束,foo执行上下文应该被出栈并销毁:

原来闭包可以这么简单

然后我们继续向下执行,bar调用了自身对象中的函数,并且输出了1 '旭旭',此时你会发现,不对呀?foo已经被销毁了,其中的innerBar也应该被销毁了,怎么还会有text1 = 1存在呢?难道我们又生成了一个foo执行上下文吗?

并不是这样的,事实上是由于foo出栈销毁的时候,我们发现foo的内部函数和数据存在被外部调用的可能,于是调用栈中的一部分空间为了避免之后找不到被外部调用的函数,保留了一部分空间储存它们:

原来闭包可以这么简单

没错!这个就像调用栈背了一个小背包一样的黄色方块就是闭包!

让我们规范化的描述一下:

在js中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量的, 当内部函数被返回到外部函数之外时,即使外部函数执行结束了,但是内部函数引用了外部函数的变量,那么这些变量依旧会被保存在内存中,我们把这些变量的集合成为闭包。

内存泄露

我们每次形成闭包我们都会发现,调用栈中的一部分内存被它偷偷占用了,“泄露”并不是说内存被泄露出去了的意思,其实就是因为这一部分内存被占用没有被及时释放,导致不再使用的内存资源无法被回收,最终耗尽了可用内存。

我们要知道调用栈的内存是有上限的,一旦储存的执行上下文过多,或者说都被闭包把空间都“偷走了”,那就会导致爆栈

所以我们在使用闭包的时候一定注意小心内存泄露的问题,我们只在需要使用闭包来实现某些必要的内容的时候使用闭包,千万不要滥用哦~

闭包的应用

如果在未来你需要参加面试,很幸运的是你被问到了闭包,并且给了你这样一道小题目:

var arr = []

for(var i = 0; i < 10; i++) {
      arr[i] = function() {
        console.log(i)
    }   
}

for(let j = 0; j < 10; j++) {
     arr[j]()
 }

现在你来想一想,我们第二个for循环调用arr[j]的时候,你觉得会输出什么呢?

答案是:10 10 10 10 10 10 10 10 10 10

你是不是以为会输出 0到9? 那么我们的题目就是怎么让它输出 0到9

如果你会作用域有所理解的话,你会知道之所以输出十个十,是因为varfor中会声明一个全局变量i,所以在之后调用arr[j]的时候,我们会输出此时已经变成了10的i。

所以有一个最简单的解法就是把var i换成let i从而形成块级作用域,这样每次调用i时都是对应块中的不同i。

但是假如面试官问你你会另外一种方法吗?你该如何应对~

不过我们已经掌握了闭包!(疯狂暗示)

来吧展示:

假如我们使用闭包的思维,是不是也可以把每一次的i值都储存在闭包里呢?想法存在实践开始:

var arr = []

for(var i = 0; i < 10; i++) {
  function a(i){
    arr[i] = function() {
      console.log(i)
    }
  }
  a(i)
}

for(let j = 0; j < 10; j++) {
     arr[j]()
}

我们来理解一下,我们采用了闭包的方式,我们每次调用函数a的时候都有一个arr[i]赋值为了一个函数,而arr是声明在全局的,也就是说是函数a的外部对吧,那么每次都会有一个闭包储存了一个形参i没有被销毁,因为arr函数输出了i,它们是存在被外部调用的可能的,所以形成了对应的闭包

那么此时,我们在第二个for循环调用arr[j]的时候,成功输出了闭包中对应的i值,也就是0 1 2 3 4 5 6 7 8 9!

到这里你如果全都明白了的话,恭喜你已经真正明白了什么是闭包,闭包真的可以这么简单!

总结

在js中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量的, 当内部函数被返回到外部函数之外时,即使外部函数执行结束了,但是内部函数引用了外部函数的变量, 那么这些变量依旧会被保存在内存中,我们把这些变量的集合成为闭包

那么文章到这里就结束啦~

如果你想了解更多这类文章,点赞关注作者更新更多后续~

转载自:https://juejin.cn/post/7296442264447926324
评论
请登录