FE-Interview icon indicating copy to clipboard operation
FE-Interview copied to clipboard

第 11 题:对闭包的看法,为什么要用闭包?说一下闭包原理以及应用场景

Open lgwebdream opened this issue 4 years ago • 14 comments

欢迎在下方发表您的优质见解

lgwebdream avatar Jun 19 '20 12:06 lgwebdream

1)什么是闭包

函数执行后返回结果是一个内部函数,并被外部变量所引用,如果内部函数持有被执行函数作用域的变量,即形成了闭包。

可以在内部函数访问到外部函数作用域。使用闭包,一可以读取函数中的变量,二可以将函数中的变量存储在内存中,保护变量不被污染。而正因闭包会把函数中的变量值存储在内存中,会对内存有消耗,所以不能滥用闭包,否则会影响网页性能,造成内存泄漏。当不需要使用闭包时,要及时释放内存,可将内层函数对象的变量赋值为null。

2)闭包原理

函数执行分成两个阶段(预编译阶段和执行阶段)。

  • 在预编译阶段,如果发现内部函数使用了外部函数的变量,则会在内存中创建一个“闭包”对象并保存对应变量值,如果已存在“闭包”,则只需要增加对应属性值即可。
  • 执行完后,函数执行上下文会被销毁,函数对“闭包”对象的引用也会被销毁,但其内部函数还持用该“闭包”的引用,所以内部函数可以继续使用“外部函数”中的变量

利用了函数作用域链的特性,一个函数内部定义的函数会将包含外部函数的活动对象添加到它的作用域链中,函数执行完毕,其执行作用域链销毁,但因内部函数的作用域链仍然在引用这个活动对象,所以其活动对象不会被销毁,直到内部函数被烧毁后才被销毁。

3)优点

  1. 可以从内部函数访问外部函数的作用域中的变量,且访问到的变量长期驻扎在内存中,可供之后使用
  2. 避免变量污染全局
  3. 把变量存到独立的作用域,作为私有成员存在

4)缺点

  1. 对内存消耗有负面影响。因内部函数保存了对外部变量的引用,导致无法被垃圾回收,增大内存使用量,所以使用不当会导致内存泄漏
  2. 对处理速度具有负面影响。闭包的层级决定了引用的外部变量在查找时经过的作用域链长度
  3. 可能获取到意外的值(captured value)

4)应用场景

应用场景一: 典型应用是模块封装,在各模块规范出现之前,都是用这样的方式防止变量污染全局。

var Yideng = (function () {
    // 这样声明为模块私有变量,外界无法直接访问
    var foo = 0;

    function Yideng() {}
    Yideng.prototype.bar = function bar() {
        return foo;
    };
    return Yideng;
}());

应用场景二: 在循环中创建闭包,防止取到意外的值。

如下代码,无论哪个元素触发事件,都会弹出 3。因为函数执行后引用的 i 是同一个,而 i 在循环结束后就是 3

for (var i = 0; i < 3; i++) {
    document.getElementById('id' + i).onfocus = function() {
      alert(i);
    };
}
//可用闭包解决
function makeCallback(num) {
  return function() {
    alert(num);
  };
}
for (var i = 0; i < 3; i++) {
    document.getElementById('id' + i).onfocus = makeCallback(i);
}

Genzhen avatar Jun 23 '20 03:06 Genzhen

厉害

GolderBrother avatar Jul 20 '20 15:07 GolderBrother

写的不错

wkm940516 avatar Jul 28 '20 02:07 wkm940516

  function foo () {
    var a = 1
    function bar () {
      a++;
      console.log(a)
    }
  }
  var b = foo()
  b()

850964517 avatar Aug 10 '20 07:08 850964517

function foo () { var a = 1; function bar () { a++; console.log(a) } return bar } var b = foo() setInterval(function(){ b()
},1000)

cucurbitboy avatar Sep 11 '20 03:09 cucurbitboy

应用场景二说错了,都会弹出 3 是因为 var 的变量提升导致 i 提升成了函数作用域的变量而不是 for 作用域内的变量。解决方法不是创建闭包而是将 var 改成 let 防止循环变量提升成函数变量。

sweetliquid avatar Nov 04 '20 11:11 sweetliquid

应用场景二说错了,都会弹出 3 是因为 var 的变量提升导致 i 提升成了函数作用域的变量而不是 for 作用域内的变量。解决方法不是创建闭包而是将 var 改成 let 防止循环变量提升成函数变量。

@Genzhen 感谢提出见解

闭包可以缓存值,不被回收,也是这种场景的一种解决方案,在没有let之前,也是经常采用的

Genzhen avatar Nov 05 '20 02:11 Genzhen

写的可以

qianyong123 avatar Dec 09 '20 07:12 qianyong123

在vue中有个经常使用的场景,在事件回调中传入额外的值,比如for循环的index。

<element v-for="(item, index) in data> <select @change="(value, option) => selectChange(value, option, index)">

meiyuesheng avatar Mar 11 '21 15:03 meiyuesheng

闭包的应用场景非常多,只要用到了函数柯里化的地方就有闭包的身影,比如防抖节流、定时器、惰性处理等

woshiitdaniu avatar May 16 '21 04:05 woshiitdaniu

写的很全面,厉害啊 ,但是 闭包函数执行后返回的结果是一个内部函数 这... 有点不理解

2130902026 avatar Sep 23 '21 01:09 2130902026

防抖和节流都用到了闭包

zizxzy avatar Nov 01 '21 10:11 zizxzy

对象是有功能的数据结构。闭包是有数据的功能

rambo-panda avatar Jun 07 '22 03:06 rambo-panda

/**
 * 闭包是函数
 * 是一个可以访问别的函数作用域里的函数
 * 优点:同一个函数创建出来的多个闭包都是独立的实例,他们不会共享作用域,防止变量污染
 * 优点:闭包函数可以访问到外部函数作用域中的变量
 * 防抖节流就用到了闭包,返回的函数中需要用到外部函数作用域中的timer
 * 缺点:变量会驻留在内存中,造成内存损耗问题
 * 
 */

function outer() {
  let count = 0;
  return function inner() {
    count++;
    console.log(count);
  };
}

const foo = outer()
const bar = outer()

foo() // 1
foo() // 2
foo() // 3
bar() // 1

Kisthanny avatar Mar 20 '24 09:03 Kisthanny