Skip to content

1.闭包

参考: 闭包,是真的美 作者:张建成 链接

什么是闭包?

闭包是指有权访问另一个函数作用域中变量的函数。内部的函数存在外部作用域的引用就会导致闭包。

闭包的缺点

内存泄漏

滥用闭包会导致内存泄露,因为内存空间使用完毕以后无法被自动回收,导致内存无法继续使用。

垃圾回收

JS具有自动垃圾回收机制。当我们创建函数、变量时会自动的分配相应的内存空间,当函数、变量不再使用后就变成垃圾。

  • 标记清除

JS最常用垃圾回收方式,变量进入环境时候标为‘进入环境’,离开环境标为‘离开环境’

  • 引用计数

跟踪记录每个值被引用的次数。

经典问题

js
// 下列函数输出的是 10 10 10 10 10 10 10 10 10 10
for(var i =0; i < 10; i++) {
  setTimeout(function(){
    console.log(i)
  })
}

传统思路

js
// 下列函数输出的是  1 2 3 4 5 6 7 8 9 10
for(let i =0; i < 10; i++) {
  setTimeout(function(){
    console.log(i)
  })
}

解释:setTimeout是异步函数(拓展:事件循环中会优先执行同步代码,再去消息队列中取任务执行。而不同的消息队列是有优先级区分的,过去W3C简单的将异步任务分为宏任务和微任务,但随着现代WEB的发展,浏览器已经将任务进行了更细的区分,延时任务。)而var定义的变量i被提升为全局变量,等事件循环去延时队列取任务是,回调函数里的i(即window.i)。而改用let以后,i有了自己的块级作用域,仅对当前循环有效,便解决了这个问题。

闭包思路

js
  for(var i =0; i < 10; i++) {
    (function(j){
      setTimeout(function(){
        console.log(j)
      })
    })(i)
  }

闭包常用场景

防抖、节流

防抖:事件触发n秒后再执行回调,n秒内再次触发则重新计时

js
  function debounded(callback, time, ...args) {
    var timer = null
    return function() {
      // 存在计时器,返回
      if (timer) clearTimeout(timer)
      // 不存在则添加定时器
      timer = setTimeout(()=>{
        callback(args)
      }, time)
    }
  }

适用:滚动事件、input监听

节流:单位时间内多次触发则只生效一次(第一次)

js
  function throttle(callback, time, ...args) {
    let lastTime = 0
    return function() {
      let now = Date.now()
      if(now - lastTime > time) {
        callback(...args)
        lastTime = now
      }
    }
  }

适用:高频点击

柯里化

把一个多参函数变成单参函数(利用闭包、arguments)

js
  function curry(fn) {
    // 得到从下标1开始的参数
    const newArgs = Array.prototype.slice.call(arguments, 1);
    let that = this
    return function() {
      let nowArgs = Array.from(arguments)
      let totalArgs = [...newArgs, ...nowArgs]
      if(totalArgs.length >= fn.length) {
        return fn.apply(this, totalArgs)
      } else {
        totalArgs.unshift(fn)
        return that.curry.apply(that, totalArgs)
      }
    }
  }

应用场景

  • 参数复用 例如需要打印某些日志,日期可以固定

    js
      const addlog = (date, type, message) => {
        console.log(`${date},发生${type}类型错误,具体为${message}`)
      }
    
      const today = curry(addlog, '2099-01-30')
      run('未定义变量', 'Uncaught ReferenceError: a is not defined') // 2099-01-30,发生未定义变量类型错误,具体为Uncaught ReferenceError: a is not defined
      run('网络','network error') // 2099-01-30,发生网络类型错误,具体为network error
  • 延迟执行

    bind函数可以使用函数柯里化思想实现

KESHAOYE-知识星球