• Home
  • Explore
  • Detail

什么是闭包

6 views

历史背景 在 JavaScript 出线前,闭包只存在于函数式编程中,JavaScript 也是第一个在主流开发中应用闭包的语言,显著地改变了开发者编写代码的方式。 在《JavaScript函数式编程指南》一书中对闭包这样解释道: >闭包 是一种能够在函数声明过程中将环境信息和所属函数绑定在一起的数据结构。它是基于函数声明的文本位置的,因此也被称为围绕函数定义的静态作用域和词法作用域。 > > 经典闭包代码如下: > js > function zipCode(code, location) { > let _code = code; > let _location = loaction || ""; > > return { > code: function () { > return _code; > }, > location: function () { > return _loaction; > } > } > > > 如下图所示:闭包包含了在外部(全局)的作用域中声明的变量、在父函数内部作用域中声明的变量、父函数的参数以及在函数声明之后声明的变量。函数体的代码可以访问这些作用域中定义的变量和对象。而所有函数都共享全局作用域。 > > image.png ## 简单理解 通俗讲,闭包 = 函数 + 词法环境,词法环境是啥,就是定义这个函数的环境。目前普遍对闭包的解释是,在一个函数的环境中,闭包 = 函数 + 词法环境。 这样也是一个闭包: js function m() { var a = 1; function sub() { } } 定义是这样。只是目前浏览器有优化,认为这样会导致内存泄漏的严重问题,会看这个sub函数有没有用到外部的东西,用的话才建立闭包,否则不建立闭包。 ## 内存泄漏 继续上一个例子: js function m() { var a = 1; function sub() { a; } return sub; } const s = m(); 这样会导致如果sub函数不被销毁,那么词法环境也无法销毁,在例子中,s可以间接访问到a。那么这是不是内存泄漏呢,也不一定,因为这个s函数可能以后要用。 那什么时候会造成内存泄漏呢,分两种情况: 1. 持有了本该被销毁的函数。 > 比如说这段代码: > js > const handler = xxx; // 假如它是一个闭包产生的函数 > dom.addEventHandler('click', handler); > // 经历了一段场景 > dom.removeEventHandler('click', handler); > // 虽然移除了监听器,但是 handler一直存在。 > 2. 隐蔽的内存泄漏场景。(当有多个函数共享词法环境时,可能导致词法环境膨胀,产生无法访问但也无法销毁的数据。) > 比如这段代码 > js > function create() { > const big = "很大的数据"; > const small = "小数据"; > function s1() { > big; // 使用了 big > } > function s2() { > small; // 使用了 small > } > return s2; // 注意这里返回 s2 > } > const one = create(); > > 我们来捋一捋: > > s2不能被销毁,因为要使用。那么small也不能被销毁,因为s2在词法环境中。但为什么big也不会被销毁呢?因为它和small共享同一词法环境,big因为s1的关系也不会被销毁,所以会造成隐蔽的内存泄漏。