闭包

69 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 8 天,点击查看活动详情

学习闭包前的知识准备:作用域机制,内存释放机制

作用域就是一个独立的地盘(变量绑定的有效范围),让变量不会外泄、暴露出去。也就是说作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。 内层作用域可以访问外层作用域的变量,外层作用域没有办法访问内层作用域中的变量

作用域与执行上下文

js分为解释和执行两个核心过程,分别为解释阶段和执行阶段

解释阶段:
  • 词法分析
  • 语法分析
  • 作用域规则确认
执行阶段:
  • 创建执行栈
  • 代码依次进栈执行
  • 执行完后出栈,进行垃圾回收 执行上下文在运行时确定,随时可能改变;作用域在定义时就确定,并且不会改变。

作用域链

  • 概念:多个作用域对象连续引用形成的链式结构。
  • 使用方面解释:当在Javascript中使用一个变量的时候,首先Javascript引擎会尝试在当前作用域下去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已经到了全局作用域,如果在全局作用域里仍然找不到该变量,它就会直接报错。
  • 存储方面解释:作用域链在JS内部中是以数组的形式存储的,数组的第一个索引对应的是函数本身的执行期上下文,也就是当前执行的代码所在环境的变量对象,下一个索引对应的空间存储的是该对象的外部执行环境,依次类推,一直到全局执行环境

闭包是 JS 函数作用域的副产品。 正是由于 JS 的函数内部可以使用函数外部的变量,所以这段代码正好符合了闭包的定义。而不是 JS 故意要使用闭包。

JS 堆栈内存释放

一般在函数执行完之后其私有作用域就会被释放,私有作用域中的变量就被回收了 函数执行完,但是函数的私有作用域内有内容被栈外的变量还在使用的,栈内存就不能释放里面的基本值也就不会被释放。 全局下的栈内存只有页面被关闭的时候才会被释放 这种现象就是闭包

什么是闭包

在一个函数的执行上下文中定义了一个函数,该函数引用了该执行上下文中的变量时,闭包就产生了。闭包产生以后外层函数内的局部变量不会被销毁,会一直存在。总而言之闭包是函数本身和外层作用域组合起来的结果,可以让你在一个内层函数中访问到其外层函数的作用域。

function foo(){
  var local = 1
  function bar(){
    local++
    return local
  }
  return bar
}
var func = foo()
func()

有一个局部变量 local,有一个函数 foo,foo 里面可以访问到 local 变量。就形成了闭包 因为如果不 return,你就无法使用这个闭包。把 return bar 改成 window.bar = bar 也是一样的,只要让外面可以访问到这个 bar 函数就行了。所以 return bar 只是为了 bar 能被使用,也跟闭包无关。

//以下这种方式虽然没有将直接将函数返回,但函数作为参数返回了,同样利用闭包在外层获得了函数foo()内部的变量 a
let inner = function (fn) {
    console.log(fn())
}
function foo() {
    let a = 'loacl'
    let n = function () {
        return a;
    }
    inner(n);
}
foo()  //输出 loacl

为什么需要闭包机制

写到这里估计大家已经能够掌握闭包的定义了,但是对于闭包的理解还需要从实例应用中进行分析,体会闭包在js语言编程中的作用,才能真正意义上的掌握闭包 闭包常常用来隐藏一个变量,间接访问一个变量,我们需要一个全局可以访问的变量存放值,但是直接定义为全局变量会存在值被误改的风险,闭包是解决这种场景的一种方式

其他

闭包虽然是有预期的对值进行使用所以没有被销毁,但是确实存在内存泄漏的可能,js中除了闭包之外还有一些场景下会出现内存泄漏的风险,之后有机会出一篇文章对可能导致内存泄漏的场景进行总结和相应的规避措施。