Skip to content

js之闭包

闭包是什么

学习闭包前可以先看看作用域,只要懂了js作用域,自然而然你就懂了闭包

在mdn中是这么描述闭包的:

闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。

当内部函数使用了外层函数的变量就会产生闭包,不用等内部函数执行,定义时就已经形成闭包。

如果你认为闭包要有return,那么理解就已经错了

闭包的产生

js
function demo() {
  let n = 1;
  function aaa() {
    console.log(n);
  }
  aaa()
}
demo();

上面这个例子中,有一个变量n,一个函数demo,demo可以访问到n变量,那么这就是一个闭包

闭包产生演示图

还有一个情况,使用return将内部函数保存到外部

js
function demo() {
  let n = 1;
  function aaa() {
    console.log(n);
  }
  return aaa;
}
const test = demo();
test();

闭包return情况演示图

为什么要嵌套函数

嵌套函数是为了让变量变成局部变量,所以才把n放在一个函数中,如果不把n放在一个函数中,那么n就会是一个全局变量,从而达不到闭包私有化变量的目的。

一定要使用return吗

如果不用return将内部函数保存到外部,你就无法使用这个闭包,也可以将函数设置为window的属性,或者使用表达式赋值到外部

例如:作为对象的属性值,赋值给另一个变量(变量名 = 函数)

前提是不能作为立即执行函数赋值,如果作为立即执行函数赋值给属性或变量,那么赋值的是执行完的结果,而不是一个函数。

所以使用return只是为了能让内部函数被使用。

闭包的作用

闭包经常用于私有化一个变量,让其他函数无法修改这个变量

假设我现在有一个按钮,点击一次就显示我点了多少次,如果不用闭包,那可以直接设置为一个全局变量

但是设置一个全局变量非常不安全,别人可以随意更改这个值,此时就可以使用闭包私有化这个变量

js
function a() {
  let num = 0;
  function b() {
    num++;
    return `你已经点击了${num}次了`;
  }
  return b;
}
let demo = a();

闭包小demo

注意

如果此时再创建一个demo1,demo和demo1这两个闭包的作用域是不同

js之闭包插图5

闭包例子图解1

js
function a() {
  function b() {
    let bbb = 234;
    console.log(aaa);
  }
  let aaa = 123;
  return b;
}
let glob = 100;
let demo = a();
demo();

a函数执行前

在执行a函数前,声明的全局变量都保存在全局对象中,全局对象里面保存的属性目前是这样

闭包例子1全局对象

此处省略了全局对象的其他属性

a函数定义时将全局对象的作用域放在自己作用域的最顶端

闭包例子图解1-a函数定义时

a函数执行

a函数执行时,产生自己的作用域并将自己的作用域放在作用域链的最顶端,他的作用域里有aaa变量和b函数

a函数执行

a函数执行期间,b函数被定义,b函数定义的时候是站在a函数的肩膀上看世界的,也就是b函数出生时拿着父亲a的作用域在看世界

a函数执行,b函数被定义

b被保存到外部

a函数将b函数return到外部之后,a函数就运行完了,a函数运行完之后,a函数会销毁他的作用域(其实是断开指向,只是叫销毁),但是,b函数被保存出去了,b函数还拿着a函数的作用域,所以即使a执行完后断开了指向,但是b函数的作用域链中还有指向a函数的作用域,所以a函数的作用域不会被释放,所以b函数还是能使用a函数的作用域

a销毁作用域

所以运行demo,会打印123

js之闭包插图10

闭包例子图解2

js
function demo() {
  let food = "";
  let obj = {
    eat: () => {
      if (food === "") {
        console.error("没有食物");
      } else {
        console.log(`I'm eating ${food}`);
        food = "";
      }
    },
    add: newFood => {
      food = newFood;
    },
  };
  return obj;
}
let test = demo();

这个例子是另一种产生闭包的情况,函数作为一个对象的属性被保存到了外部,但是,闭包的形成条件就是,只要内部函数使用了外层函数的变量,那么就会形成闭包

demo函数执行

demo函数执行,将自己的作用域放在了作用域链的最顶端,里面有food变量和obj对象

demo函数执行

demo函数执行时,obj对象里的eat和add两个函数被定义,两个函数都用着demo的作用域

obj.eat和obj.add函数定义

obj被保存到外部

obj被return到外部之后,demo销毁作用域,两个函数还是指向着demo的作用域,所以不会被释放

obj被保存到了外部

由于闭包后都是使用同一个变量,所以就会产生下面的结果。

例子2演示结果

优点和缺点

优点

1.能够读取函数内部的变量

2.可以让变量一直存在内存中,使用后不会被垃圾回收机制回收

3.可以实现变量私有化,加强了封装性,不会产生变量污染

缺点

正所谓物极必反,由于闭包不会释放变量,变量会一直存在于内存中,对内存的消耗很大,如果滥用闭包,会影响页面的性能

在IE中可能会导致内存泄漏

解决办法:在退出函数时,将不用闭包置空,赋值为null

总结

闭包其实就是内部函数引用外层函数的变量,我们使用时经常会将函数保存到外部,所以内部函数保存到外部后还用着外层函数的变量,导致外层函数执行完后作用域无法释放,从而形成闭包

艾雨博客微信公众号二维码
© 版权声明
文章版权归作者所有,未经允许请勿转载.
本站资源多数存于云盘,如有失效请邮件联系我.
本网站的文章部分内容可能来源于网络,如有侵权,请联系884684993#qq.com进行删除处理.