闭包到底是什么?

888 阅读3分钟

面试

前言

闭包闭包,闭包是JavaScript最强大的特性,没有之一,很多强大JavaScript库比如jQuery、Vue.js都使用了闭包的特性来实现的。闭包几乎是一线互联网企业面试必问的题

如何理解闭包?

每个人的理解都不一样,我用深和浅来概括一下。

  • 深: 函数执行形成无法释放的上下文栈内存
  • 浅: 函数执行后使其内部变量能够被外界访问

闭包经典理解

  • 由于var 变量的提升,循环的时候赋值都是用一个i;
  • 因为setTimeout为宏任务,由于JS中单线程eventLoop机制,在主线程同步任务执行完后才去执行宏任务,因此循环结束后 才执行setTimeout,但输出i的时候当前作用域没有,往上一级再找,发现了i,此时循环已经结束,i变成了10;
  • 使用let可以解决 let形成块级作用域不会提升
for (var i = 0; i < 10; i++) {
    //这个时候 i已经 = 10var i提升 每次进行赋值都是同一个i;
    setTimeout(() => {
        console.log(i);// 打印1010
    }, 1000)
}

使用闭包

for (var i = 0; i < 10; i++) {
    ((i) => {
        setTimeout(() => {
            console.log(i);//打印0-9
        }, 1000)
    })(i)
}
  • 和let一个效果?那么可不可以认为闭包是一种作用域,它拷贝了一套外层函数作用域中被访问的参数、变量/函数,这个拷贝都是浅拷贝?大家踊跃讨论我是个小白x.x

闭包具体步骤(冲冲冲)

  • 创建函数时
    • 开辟一个堆内存
    • 把函数体中的代码当作字符串存进去
    • 把堆内存的地址赋值给函数名/变量名
    • 函数在哪创建,那么他指向时所需要查找的上级作用域就是谁
  • 函数指向
    • 形成一个全新的私有作用域, 执行上下文, 私有栈内存 ( 执行一个形成一个,多个自己也不会产生影响 )
    • 形参赋值,变量提升
    • 代码执行(把所属堆内存中的字符串拿出来一行行执行) 遇到变量,会依次向他的私有作用域中或者上级作用域(最顶到window)寻找; 私有变量和全局其他的变量没有必然关系,闭包保护机制 3.函数执行
    • 通过执行上下文栈执行入栈出栈过程

无法释放的栈内存

  • 正常情况下,函数执行完上下文销毁然后出栈即完毕
  • 非正常情况下,当前上下文的变量对象被外界所引用,那么这个栈内存会一直存在且不会被释放

闭包有什么作用?

  • 变量长期驻扎在内存中
  • 避免全局变量的污染
  • 私有成员的存在
  • 模块化封装,以及HOC等

上面的这些特点,促使了许多库的诞生,比如JQuery,将代码都写在闭包中,只暴露出$出来供我们调用内部的方法

闭包的缺点

javascript中的垃圾回收(GC)规则是这样的:如果对象不再被引用,或者对象互相引用形成数据孤岛后且没有被孤岛之外的其他对象引用,那么这些对象将会被JS引擎的垃圾回收器回收;反之,这些对象一直会保存在内存中。

  • 由于闭包会引用包含它的外层函数作用域里的变量/函数,因此会比其他非闭包形式的函数占用更多内存。
  • 当外层函数执行完毕退出函数调用栈(call stack)的时候,外层函数作用域里变量因为被引用着,可能并不会被JS引擎的垃圾回收器回收,因而会引起内存泄漏。过度使用闭包,会导致内存占用过多,甚至内存泄漏。