(三) js闭包

1. 案例引入

先看一个简单的需求:浏览器

  • 咱们想要实现一个简单的累加器, 每调用一次累加函数, 变量就加 1

这真的是一个简单的需求, 咱们能够立马写出来闭包

var counter = 0
function addCounter(){
  counter++
  console.log(counter);
}
addCounter()				// 1
addCounter()				// 2
addCounter()				// 3

可是你有没有想过: 为何 counter 能够一直被累加 ?ide

这里就牵扯到了js中的 垃圾回收机制函数

所谓的垃圾回收, 就是肯定哪一个变量再也不使用, 而后释放它占用的内存, 这是一个周期性的检测学习

可是对于全局变量而言, 垃圾回收机制并不知道它何时再也不使用, 所以也就没法释放其占用的内存, 全局变量也就会一直被保存在内存当中, 这也就是咱们能够一直使用它的缘由prototype

其实从执行上下文的角度来看, 全局变量的声明是在全局上下文中进行的, 被一直保存在GO中, 而全局上下文是在咱们关闭浏览器时才会被销毁, 所以其余函数就能够经过其保存的上下文环境 (也就是做用域链), 到GO中引用countercode

这样, 第一个问题咱们就解决了blog

好, 咱们再来思考另一个问题, 若是另一个函数也用到了 counter, 或者另一个开发者声明了一个一样的全局变量 counter,那么这势必会影响上面累加器的运做, 由于 counter 的值被其余函数 / 变量声明 改变了, 这显然是咱们所不但愿的ip

那既然不想counter被其余因素所改变, 那咱们把counter做为累加器的内部变量不就好了么, 这样只有函数本身能用, 行吗? 显然不行内存

由于函数在执行完毕后, 会销毁其所在的上下文环境, 保存在AO中的变量会被垃圾回收机制释放, 对于没法一直保存在内存中的变量, 咱们显然没法一直对他进行操做

那有没有办法能够解决这个需求: 咱们既想把 counter 声明为局部变量, 又让他一直保留在内存中呢 ?

答案是能够的, 这就是咱们须要闭包的缘由

2. 什么是闭包

我的理解:

  • 咱们知道: 执行上下文是一个运行时环境, 当函数执行完毕, 函数上下文会被释放, 局部变量天然也就没法一直保留

  • 而闭包的存在, 就是帮助咱们把这些会被释放的上下文保存到内存中,使其一直存在

MDN中给出闭包的以下定义:

  • 一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一块儿(或者说函数被引用包围),这样的组合就是闭包closure)。

示例

function fn() {
  var count = 0
  function addCount() {
    count++
  }
  addCount()
}

fn()

上面有没有造成闭包呢? 咱们根据MDN的定义来分析一下:

  • addCount 是否是一个函数? 是
  • addCount 对其周围状态有没有引用 ? 有, 引用了所处上下文环境中的 count

既然造成了闭包, 那能不能完成咱们上面的需求呢: **咱们既想把 counter 声明为局部变量, 又让他一直保留在内存中 **

咱们来接着调用addCount函数, 看三次调用之后, count的值是否是3

注意看 watch中count的变化

经过演示咱们发现, count 并无一直被保留在内存中, 而是每次调用都被初始化为了0

这显然没法完成咱们的需求


ECMAScript中, 给出以下定义:

  • 从理论角度:全部的函数都是闭包。由于它们都在建立的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,由于函数中访问全局变量就至关因而在访问自由变量,这个时候使用最外层的做用域。

  • 从实践角度:如下函数才算是闭包:

    • 即便建立它的上下文已经销毁,它仍然存在(好比,内部函数从父函数中返回)
    • 在代码中引用了自由变量

根据ECMAScript给出的定义, 咱们再来对比MDN给出的定义, 咱们发现, MDN中对闭包的定义其实就是ECMAScript中的闭包的理论角度,

但这对咱们的需求并无实际的做用, 那咱们再来看 从实际角度出发给出的定义

示例

function fn() {
  var count = 0
  function addCount() {
    count++
  }
  return addCount
}

var add = fn()
add()
add()
add()

注意: 与上面代码不一样的是, 咱们此次将addCount函数做为返回值传递到外部

注意此时watch中count的变化

咱们能够清晰的看到, 内部变量count, 在函数外部被一直引用, 并且实现了值的累加 !

也就是说, count被一直保留在了内存之中, 这也真正实现了咱们上面所一直所提的需求:

  • 咱们既想把 counter 声明为局部变量, 又让他一直保留在内存中

其实说了这么多, 可能仍是比较抽象, 咱们先回顾如下闭包的理解:

  • 咱们知道: 执行上下文是一个运行时环境, 当函数执行完毕, 函数上下文会被释放, 局部变量天然也就没法一直保留

  • 而闭包的存在, 就是帮助咱们把这些会被释放的上下文保存到内存中,使其一直存在

咱们举个例子:

​ 假设我家院子 (内存)里有一个大水缸 (不能释放的内存), 水缸里面有满满的水 (全局变量), 这个水过路口渴的人 (其余函数等)均可以取来喝,可是不一样的人都来喝, 不就把我家的水给 污染 了么, 何况这是我家, 大家怎么能想喝就喝呢, 因而我想出了这样一个办法:

​ 我先把水(此时变成局部变量)都移到纸箱(外层函数)里, 可是纸箱存不住水 (函数被释放)呀, 因而我又立刻把这些水都灌进水瓶 (内层函数)里, 而后把这些水瓶放在纸箱里 (函数嵌套), 这样咱们的水得以保存, 可是别人也想喝怎么办, 那就按照个人说明去纸箱里拿水瓶 (暴露给外部的引用, 到此造成闭包), 而且你只能经过暴露出的水瓶来喝水

​ 后来家家装了自来水, 你们也都不须要来我家喝水了, 因而我就把全部的水瓶丢掉(清除引用, 释放闭包的内存),


综上:

怎么理解闭包:

  • 咱们知道: 执行上下文是一个运行时环境, 当函数执行完毕, 函数上下文会被释放, 局部变量天然也就没法一直保留

  • 而闭包的存在, 就是帮助咱们把这些会被释放的上下文保存到内存中,使其一直存在

什么才算闭包:

从实践角度:如下函数才算是闭包:

  • 即便建立它的上下文已经销毁,它仍然存在(好比,内部函数从父函数中返回)
  • 在代码中引用了自由变量

3. 闭包的应用

1. 访问函数内部的变量, 也就是上面一直提的需求

function fn() {
  var count = 0
  function addCount() {
    count++
  }
  return addCount
}

var add = fn()
add()

2. 定时器传值, 本质与上面一致

function func(param) {
  return function () {
    alert(param)
  }
}

var f1 = func(1)

setTimeout(f1, 1000)

3. 利用闭包判断数据类型

function isType(type) {
  return function (target) {
    return `[object ${type}]` === Object.prototype.toString.call(target)
  }
}

const isArray = isType('Array')
console.log(isArray([1, 2, 3]));				// true
console.log(isArray({}));
仅记录本身的学习总结,若有错误,还请评论指正~
相关文章
相关标签/搜索