围绕一句话解释JS闭包

2 篇文章 0 订阅

一句话说:闭包就是携带状态的函数

原始闭包

     // 原始闭包
    function package() {
      var a = 0
      function lock() {
        console.log(a++);
      }
      return lock
    }
    /**
    * package()() // 0
    * package()() // 0
    */
    var packIt = package()
    packIt() // 0
    packIt() // 1

我们通过原始闭包来看四个点,以后的例子都会围绕这四个点诠释“闭包”:

  1. 条件:作用域的嵌套,在这里我们是package 嵌套着内部作用域 lock
  2. 条件:内部作用域(锁)lock 引用了 外部作用域(包) package 的 变量 a 并且被调用return lock (我称之为锁住)
  3. 核心特性:经过前两个步骤,闭包已经产生了!由于我们前两步的条件,就像一个包关上了一把内部的锁,导致package里面的状态a不会被销毁, package函数(包)是具有状态的!
  4. 使用,也是闭包的意义所在但不是必须的,我们用变量packIt存着return 出来的 lock,保证lock不会被使用完后就销毁,这样这个存着状态的作用域(包)也就有了意义,而不是pack()()这样每次都新建一个包(然后被销毁)

缓缓理解一下闭包的核心(锁包),现实中的闭包不会写的如此“清晰显眼”,我们来通过这4点深入理解不那么显眼的闭包:

实际中的闭包

实际中的闭包未必包含第四步“使用”,有很多就是单纯产生了一个包,我们接下来就是要搞清楚哪里产生了包,不先弄清楚还谈什么用呢?对吧。

	// 不显眼的闭包1
    function package() {
      var a = 0
      function lock() {
        console.log(a);
      }
      use(lock)
    }
    function use(fn) {
      fn()
    }
	 package()

这没太大变化,她也是一个闭包,不同于原始闭包,只是内部函数lock的调用方式变了而已,但方便我们理解一句话:

无论通过何种手段将内部函数传递 到所在的词法作用域以外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。

这句话有助于我们理解下面更不显眼的闭包。

	// 不显眼的闭包2
    function wait(msg) {
      setTimeout(function timer() {
        console.log(msg);
      }, 1000);
    }
    wait('hello')

这为什么也是一个闭包呢?作用域嵌套,内部引用外部都有了,那内部函数timer是如何传递 到外部的呢?
工具函数setTimeout(..) 持有一个对参数的引用,引擎会调用这个函数也就是timer (具体如何实现我们在这里不做深入探讨)

   // jQuery中
      function showName(name, selector) {
        $(selector).click(function show() {
          console.log(name);
        })
      }
      showName('box1','#box_1')
      showName('box2','#box_2')

回调函数(在另一个地方调用内部函数)也产生了闭包:
在定时器、事件监听器、Ajax请求或者其他(异步同步)任务中,只要使用了 回调函数 ,实际上就是在使用闭包

上面讲了这么多,尽管我们产生了这么多闭包,实际上却没有好好使用闭包的特性,最后我们来讲第四步: 使用

闭包的使用


    function myModule() {
      function doSomething() {
        console.log('doSomething');
      }

      function doOtherThing() {
        console.log('doOtherThing');
      }
      return {
        doSomething:doSomething,
        doOtherThing // es6简写
      }
    }
    var m = myModule()
    m.doSomething()
 	m.doOtherThing()

我们将闭包运用到模块当中,m这个模块是一个具有状态的函数 ,状态指的就是dosomething等方法,也可以是其他对象、变量 等等都行,他们都作为内部对象 被返回出来,被锁住,外部就可以调用,而不被返回出来的也就是不被调用的变量永远无法被外部接触,除非我给你这个接口

再看一个函数的柯理化:将多参数的函数变成逐层传入的单参数函数。

    function makePow(n) {
      return function (x) {
        Math.pow(x,n) // 计算x的n次方
      }
    }
    var pow2 = makePow(2) // 计算2次方
    var pow3 = makePow(3) // 计算3次方
    pow2(6) // 36
    pow3(3) // 27

这是函数式编程的冰山一角,我们将需要传入两个甚至多个参数的函数进行封装,方便重复使用,而不是每次都传入多个参数。就好比一堆样本中的许多数据都相同,不同的总是那几个,就没必要每次都输入所有数据,就像我们经常使用平方而不是4次方。

闭包带来的问题

最后我们用上面模块化的栗子举一个常见的垃圾回收上的问题:

    // 利用閉包封装函数
    (function myModule(w) {
      { // 显式声明块作用域
       	let someReallyBigData = {bigD:xxx}
        let btn = document.getElementById('myBtn') // 假设我们有一个按钮
        let clickIt = btn.addEventListener('click',fn) // 伪操作
      }
      function doSomething() {
        console.log('doSomthing');
      }

      function doOtherThing() {
        console.log('doOtherThing');
      }
      w.$m = { // 挂载在window对象上
        doSomething,
        doOtherThing
      }
    })(window)

    $m.doSomething()
    $m.doOtherThing()

当我们的闭包暴露出私有变量 之后,而剩下的变量(代码中的btn等)也会被锁在闭包的作用域中,js的机制极有可能不会将其清除,而一直残留在内存中,如果数据非常大且杂乱会导致未来无法预料的不好现象,这就是闭包带来的缺点。
因此我们希望将没有暴露的私有变量被销毁,可以显式的(大括号包住的部分)提供一个块作用域,就是let声明所在的部分,js引擎会知道这部分没必要保留(如果没有暴露接口的情况下)。

上述闭包封装得更规范一点就是这样

   (function createModule(w) {
      class createObj {
        constructor() {
          this.something = 'sayIt'
          this.otherThing = 'sayOther'
        }
        doSomething() {
          console.log(this.something);
        }

        doOtherThing() {
          console.log(this.otherThing);
        }
      }
      w.$m = new createObj()
    })(window)
    $m.doSomething() // log 'sayIt'

文章为作者原创,如有不正之处还望大家指出,也欢迎分享讨论

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值