Skip to content

闭包

函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包

闭包就是有权访问另一个函数作用域中变量的函数

高阶函数

闭包 = 高阶函数

闭包的概念聚焦在私有变量缓存和垃圾回收上

高阶函数的概念聚焦在函数可以用在任何地方(函数是一等公民) 通过把一个函数(回调函数)传递给另一个函数,让另一个函数更灵活

题: for循环一个var异步函数

js
for (var i = 1; i <= 5; i++) {
  setTimeout( () => {
    console.log(i) // 6 6 6 6 6 6
  }, i * 1000)
}

👆 因为 setTimeout 的回调是异步函数,事件循环开始执行时,同步函数i++已经全部执行完了, i 已经全部是6了

🤔 这也是 eslint 限制不允许在循环中使用异步的原因,如果异步用到了循环同步逻辑里的变量,将导致不可知的异常

解决办法:

  1. i 缓存起来,让事件循环执行到的时候可以取到 也就是闭包
js
for (var i = 1; i <= 5; i++) {
  ;(function(j) {
    setTimeout(() => {
      console.log(j)
    }, j * 1000)
  })(i)
}
  1. setTimeout 的第三个参数
js
for (var i = 1; i <= 5; i++) {
  setTimeout((j) => {
      console.log(j)
    },
    i * 1000,
    i
  )
}
  1. 不用 varlet
js
for (let i = 1; i <= 5; i++) {
  setTimeout(() => {
    console.log(i)
  }, i * 1000)
}

🤔: for 里面的 let 局部变量,为什么在循环传递给异步函数不会++变化 TODO:


简单的闭包(概念)

js
function func(){
  var a=1,b=2;
  function closure(){
     return a+b
  }
return closure}

js
function func(){
   var a=1,b=2;
   return function(){
      return a+b
   }
}

↑closure()是一个闭包闭包的作用域:自身作用域+父级函数的作用域+全局作用域##

闭包的特点

1、垃圾回收机制与普通函数的不同

普通函数作用域和变量,会在函数执行完成后自动被销毁

闭包函数作用域会保留到到闭包不存在为止

因此手动清除闭包作用域是有必要的

👇 创建闭包及手动清除闭包作用域

js
function func(x){
  return function(y){
    return x+y
  }
}
// 调用函数
var test1 = func(1)
var test2 = func(2)
console.log(test1(1))
console.log(test2(1))

// 释放闭包的引用
test1 = null
test2 = null

👆 test1、test2是闭包,他们都是同一个函数,但是保存了不同的环境(变量等信息)。最后通过null释放test1、test2变量对闭包的引用

在js中,如果一个对象(函数、数组、对象)不再被引用,该对象就会被垃圾回收机制回收

如果两个对象互相引用,而不被第3者引用,那这两个对象也会被回收

2、取父级变量时的注意点,for

for循环用let和var会形成不同的作用域情况因此在for里使用闭包,闭包取父级作用域变量时需要特别注意(真实情况不怎么会这样写,这是让我们更好理解作用域

js
function func(){
   var arr = []
   for(var i=0; i<10; i++) {
     arr[i] = function(){
       return i
     }
   }

   for(var a=0; a<10; a++) {
     console.log(arr[a])
   }
}

//调用包含闭包的函数
func()  // 打印10个10

👇 把var i 改为 let i

js
function func(){
   var arr = []
   for(let i=0; i<10; i++){  // 仅修改这里
      arr[i] = function(){
         return i
      }
   }

   for(var a=0; a<10; a++){
      console.log(arr[a])
    }
}

//调用包含闭包的函数
func()  // 打印0到9

👆 要首先知道执行func()会执行什么,执行for的console.log,打印出arr数组

arr数组的值来自闭包函数return i,因此赋值给arr的时候还是个引用而不是具体的值

引用会在真正调用的时候赋值,也就是这里for的console.log的时候

当真正赋值的时候,引用会去作用域找值

也就是var的时候作用域是整个func,它的i变量已经是10了,于是赋值了10个10

而let在for的时候,会创建一个新的作用域,存储了每次i的值0-9,于是赋值的时候可以在该作用域中拿到它存储的i,0-9(var那是2个作用域,let是3个作用域

3、匿名函数的闭包this指向

普通函数this指向是:调用自身的对象(调用的对象,在最外层直接调用不带对象相当于window)

js
var name = "The Window"
var obj = {
  name: "My Object",
  getName: function(){
    return function(){
      return this.name
    }
  }
}

// 调用闭包函数
obj.getName()() // The Window

👆 首先执行obj.getName() 返回一个匿名函数,再执行匿名函数

该匿名函数的this在全局作用域中 因此会是 The Window

应用

单例对象:只有一个实例的对象普通单例对象

js
var obj = {
   name: '213',
   func: function(){
      console.log('')
   },

   getName: function(){
      return this.name
   }
}

模块模式单例对象:给普通单例对象创建私有变量和方法利用闭包有权访问另一函数变量的特点,使用函数返回对象的方式来创建单例对象,而不是直接创建对象

js
var obj = (function(){
// 私有变量
var name = \'213\',
var func:function(){

console.log(\'\')
}
// 特权方法:有权访问私有变量的公有方法
return {

msg:\'abc\',

getName:function(){


return name

}
}})()

↑ 使用到自执行函数,如果不用自执行函数,可另外赋值给obj使用闭包模块化代码,减少全局变量的污染,并且保持住了局部变量的值

闭包的缺陷:常驻内存会增大内存使用量,使用不当容易内存泄漏对脚本性能有影响,处理速度、内存消耗#

this、执行上下文

js
let func = function(){
console.log(this.name)}let obj = {
name:\'\',
func}// 花式调用func的thisfunc() 



 // undefind  this指向window obj.func() 


 // \'\' 
this指向调用者func.call(obj) 

 // \'\' 
this指向call参数ley Func = new func() 
// undfind  this指向实例Func

箭头函数的this

没有自身的this、arguments、super、new.target,没原型对象prototype不能用new做构造函数 无论对箭头函数用 .xx call 都是不能修改this指向