闭包是什么?
2023-04-30 12:12:34

定义

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

也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。

闭包是由函数以及声明该函数的词法环境组合而成的。该环境包含了这个闭包创建时作用域内的任何局部变量。[MND]

优点

  1. 可以从内部函数访问外部函数的作用域中的变量,且访问到的变量长期驻扎到内存中;
  2. 避免变量污染全局;
  3. 把变量存到独立的作用域中,作为私有变量存在;

缺点

  1. 对内存消耗有负面影响。因内部函数保存了对外部变量的引用,导致无法被垃圾回收,增大内存使用量,所以使用不当会导致内存泄漏
  2. 对处理速度具有负面影响。闭包的层级决定了引用的外部变量在查找时经过的作用域链长度
  3. 可能获取到意外的值(captured value)

使用场景

私有变量

var PrivateName = function ()  {
  var privateName = ""

  function Name () {}

  Name.prototype.set = function(name) {
    privateName = name
  }
  Name.prototype.say = function() {
    console.log("My name is " + privateName)
  }

  return Name
}()

var myname = new PrivateName()
myname.set("小明")
myname.say()

单例模式

假设我们要设计一个HTTP的请求,所有从这个实例发出的请求都要带一个版本号。

var MyRequest = function(){
  var instance = null
  
  return function () {
    if (!instance) {
      instance = new XMLHttpRequest()
      instance.setRequestHeader("Version", "1.0.0")
      //....
    }

    return instance
  }
}();

// 这样每一个通过 MyRequest 实例发出的请求都会带有版本号的请求头
var myRequest1 = new MyRequest()
var myRequest2 = new MyRequest()

设计一个自增ID

var getID = function(){
  var id = 0
  return function () {
    console.log(id)
    return id++
  }
}();

var id1 = getID()
var id2 = getID()

思考如何拿到正确的值

for(var i=0; i<10; i++) {
  setTimeout(function() {
    console.log(i)
  }, 1000)
}
查看解析
for(var i = 0; i < 10; i++) {
  (function (g) {
    setTimeout(function() {
      console.log(g)
    }, 1000)
  })(i);
}

如何实现防抖和节流

查看解析
function debounce(fun, delay) {
  let timer = null

  return function() {
    clearTimeout(timer)

    const args = arguments
    const that = this
    timer = setTimeout(function() {
      fun.apply(that, args)
    }, delay)
  }
}

function throttle(fun, delay) {
  let timer = null

  return function() {
    if (timer !== null) return

    const args = arguments
    const that = this
    timer = setTimeout(function() {
      fun.apply(that, args)
      clearTimeout(timer)
      timer = null
    }, delay)
  }
}