一起锤闭包

664 阅读3分钟

一、概述

闭包,日常开发中看起来很少使用,但实际我们又经常在日常开发里无形使用,而且面试也经常被提及,很多同学开发好几年可能对闭包的理解就是只能说出个大概概念,今天咱们就来捶捶闭包。

二、浏览器的垃圾回收机制

在讲闭包之前,咱们先提及一下和闭包相关的知识点,有助于我们更好的理解闭包的产生,这个知识点就是浏览器的垃圾回收机制。由于我们的主角是闭包,所以浏览器的垃圾回收机制只简单的讲一下概念。

垃圾回收概念:

浏览器中会有一个周期性运行的垃圾回收程序,它会去跟踪各个变量的使用情况,当变量不会再被使用时,就会将变量销毁回收,释放内存。

三、闭包

首先我们看下闭包的概念:

能够访问到另一个函数内部作用域的函数。

众所周知,一个函数内部的作用域就是个局部作用域,只有函数内部或函数的子函数才能访问,看示例↓↓↓

function Fn1(){
  let a = 'a值';
  console.log('Fn1内部输出--->',a); //Fn1内部输出---> a值
  function Fn2(){
    console.log('Fn2内部输出--->',a); //Fn2内部输出---> a值
  };
  Fn2();
};
Fn1();
console.log(a); //Uncaught ReferenceError: a is not defined

遵循作用域链查找的原理,由上至下,Fn1内部和Fn2内部都能读取到变量a的值,Fn1外部读取不到a变量。而闭包的概念是在任何作用域下都能访问到Fn1函数中的变量a,如何使其产生闭包呢?此时我们要借助一些手段,看下面示例↓↓↓

function Fn1(){
  let a = 'a值';
  function Fn2(){
    console.log(a)
  };
  return Fn2;
};
let Fn3 = Fn1();
Fn3(); //a值

上面例子里,我们在Fn2中使用Fn1中的变量,然后将Fn2表达式返出去赋值给Fn3,然后执行Fn3,这样我们在Fn1内部作用域之外访问到了Fn1中的变量,这就是闭包现象。

闭包产生的原因

众所周知,浏览器中运行着一套垃圾回收程序,这个程序会定期的运行,去回收不会再被使用的变量所占的内存。所以正常来说,一个函数执行结束且之后不会再被使用,其内部变量以及它本身将会被垃圾回收程序销毁并释放内存,此处Fn1在执行后我们期望的结果就是它到此为止应该被销毁了,可是此时它的执行结果被赋值给Fn3了,跟Fn3扯上了关系,明面上Fn1之后不会再执行了,但由于它的内部和Fn3有着联系,所以Fn1在执行完并不会被销毁,这就是闭包产生原因。无图无真相,看下面的示例↓↓↓

闭包.jpg

闭包的产生形式:

闭包产生的形式不仅仅一定要像上面例子一样,只要函数内部发生了函数类型的值传递,就有可能会产生闭包,我们来看看其他闭包产生的形式:

function Fn3(fn){
  fn();
}
function Fn1(){
  let a = 'a值';
  function Fn2(){
    console.log(a)
  };
  Fn3(Fn2); //函数类型的值传递
}
Fn1();

let Fn3;
function Fn1(){
  let a = 'a值';
  function Fn2(){
    console.log(a)
  };
  Fn3 = Fn2; //函数类型的值传递
}
Fn1();
Fn3();
闭包的实际使用场景

闭包其实在我们的开发中无处不在,只是我们很少去感知,比如setTimeout,setInterval,ajax请求回调等,凡是一切用到回调函数的地方,我们都可能用到过闭包。

总结

  1. 能在任何作用域下,访问到某个函数内部的变量,被称之为闭包现象
  2. 闭包之所以会产生,是因为函数内部与其他作用域建立了引用关系,导致内存无法释放,所以在其他作用域中依然可以访问到函数中的变量。