JavaScript——闭包函数及拓展题目

本文探讨了如何通过闭包函数解决全局变量污染的问题,介绍了闭包的概念、使用场景、代码实现以及其优缺点。通过实例和图解展示了如何利用闭包创建可重用且受保护的局部变量,以及如何处理多次调用和双胞胎函数的情况。
摘要由CSDN通过智能技术生成

问题引入

假设现在要设计一段程序,给出金钱总数,每使用一次钱就会在总数里扣除:

  • 全局变量
    a.优点:重用
    b.缺点:极其被污染、篡改
var total=1000;

function pay(money){
    total-=money;
    console.log(`花了${money},还剩${total}`);
}
pay(100);
pay(100);

结果正确,但是使用了全局变量,容易造成污染:
在这里插入图片描述

  • 局部变量
    a.优点:不会被污染
    b.缺点:不可重用

那如果我们使用局部变量呢?

function pay(money){
    var total=1000;
    total-=money;
    console.log(`花了${money},还剩${total}`);
}
pay(100);
pay(100);

发现局部变量是不可重用的,导致结果错误:
在这里插入图片描述

一、闭包函数

解决方案:使用闭包函数

1.什么是闭包函数?

外层函数的作用域对象,在外层函数调用后,依然被内层函数引用着,无法释放,形成了闭包。

满足上述两个条件,如果在外部函数调用这个内部函数,就成为闭包函数。

2.步骤

①用外层函数包裹要保护的变量和使用变量的内层函数
②在外层函数内部,返回内层函数对象。
③调用外层函数,用变量接住返回的内层函数对象。

3. 何时使用

希望给一个函数保护一个可反复使用的专属变量,又防止这个变量被外部篡改

4.代码结构

function f(x) {       //外部函数
    var a = x;    		//外部函数的局部变量
    //↓返回的闭包体结构
    var b = function() {    //内部函数(定义一个闭包)
        return a;    //访问外部函数中的局部变量
    };

    a++;
    return b;    //返回 内部函数整个结构!
}
var c = f(5);   //调用外部函数,返回内部函数
// c返回的是一个函数

//c是一个函数,用()才会调用 
alert(c());     //调用内部函数,返回值6

二、示例图解

如果要直接看结论,可跳过此处图解,直接看“三”

针对引入的问题,给出正确的解决代码:

//1. 用外层函数包裹要保护的变量和使用变量的内层函数
function mother(){
  var total=1000;
  //2. 在外层函数内部,返回内层函数对象。
  return function(money){
    //从总价中减去本次花的钱
    total-=money;
    console.log(`花了${money},还剩${total}`);
  }
}
//3. 调用外层函数,用变量接住返回的内层函数对象。
var pay=mother();
// pay返回的是一个函数
//pay:function(money){
    //total-=money;
    //console.log(`花了${money},还剩${total}`);
//}

//pay是一个函数,用()才会调用 
pay(100);//剩900
//别人代码中的,使用闭包后不会影响
total=0;
pay(100);//剩800

①函数定义时
window对象中有全局函数mother和全局变量pay,mother通过函数的地址0x1234引用函数,函数自有一个作用域链,一个为空,一个为window;pay暂时还未接收值,故为undefined。

0x1234

函数调用时
调用外部函数mother时,临时创建函数作用域对象,地址为0x9091,地址保存到mother函数的作用域链中。
在这里插入图片描述

接下来,会临时创建一个内层函数对象,并返回到外部来,地址为0x2345
function的底层就是new Function,new Function会在外部创建一个函数

然后根据pay = mother();把新的地址赋给pay
在这里插入图片描述
③mother函数调用后
当执行完pay = mother();,清空mother作用域链的第一个格子的地址,这时mother不再和mother的函数作用域对象关联。
由于内层函数仍引用mother函数作用域对象,故不能释放mother的函数作用域对象,此时mother作用域对象和mother函数无关系
在这里插入图片描述
执行pay(100),调用内层函数
临时创建一个pay的函数作用域,地址为0x2468,局部变量为money
在这里插入图片描述
⑤执行内层函数的total-=money
就近原则,先找第一个格子里,有money = 100,语句为total-=100;
在这里插入图片描述
然后找到第二个格子中有total,执行语句total-=100;,故total为900
在这里插入图片描述
⑥第一个pay(100)执行完毕后,清空作用域链的最近一个格子
在这里插入图片描述
⑦执行total=0,total全局变量,不能在函数内寻找,但在window中没有找到total=0,自动在全局创建
在这里插入图片描述
⑧执行pay(100,重复④⑤⑥⑦
在这里插入图片描述
调用完pay(100)后:
在这里插入图片描述

三、简化记忆

(1)外层函数:妈妈
(2)内层函数:孩子
(3)外层函数的局部变量:红包

  • 当调用外层函数(妈妈)时,生出(return)内层函数(孩子)
  • 孩子(内层函数)拿着(引用)红包(外层函数的作用域对象),独立门户,形成了闭包。
    在这里插入图片描述

四、缺点

闭包产生的内层函数比普通函数多占用一块内存空间——外层函数的作用域对象

解决:
如果一个闭包结构不再使用,记得手动释放

只要将引用内层函数的变量赋值为null->内层函数被释放->外层函数的作用域对象也就释放
例:pay = null

五、拓展题目

1.多次生孩子情况

一个妈妈生两次孩子,两个孩子分别得到属于自己的红包,互不干扰

function mother(){
  var total=1000;
  return function(money){
    total-=money;
    console.log(`花了${money},还剩${total}`);
  }
}
var pay=mother();//妈妈生第一个小孩,临时为小孩包一个红包1,小孩1拿着红包1独立门户
pay(100);//剩900
pay(100);//剩800

var pay2 = mother();//妈妈生第二个小孩,临时为小孩包一个红包2,小孩2拿着红包2独立门户
pay(100);//剩900

在这里插入图片描述

2.双胞胎情况

function fun() {//妈妈
  var n = 999;  //红包:函数作用域对象
  
  nAdd = function() { //另一个孩子
    n++;
  };
  return function() { //一个孩子
    console.log(n);
  }
}
var getN = fun();//妈妈生小孩,包一个红包
getN();//999
nAdd();
getN();//1000

解:
在函数fun中,有返回nAdd和return的两个匿名函数,这两个都是fun的孩子。但主程序中只生一次孩子(调用一次fun函数),所以这两个函数应共用一个红包(外层函数的函数作用域)
在这里插入图片描述

所以两个孩子应该与一个红包独立门户(形成闭包),在闭包中的内容加上nAdd = function(){n++};
在这里插入图片描述=>在这里插入图片描述
第一次打印结果为999,执行nAdd()后,两个孩子共用的n++,故第二次getN打印结果为1000

六、技巧

画简图,判断闭包,找两样东西:
(1)外层函数与内层函数之间都有哪些受保护的变量.
(2)外层函数共向外抛出了几个内层函数对象!
三种方式:

  • return function() {……}
  • 直接给全局变量赋值的方式,向外抛出一个对象
  • return一个对象或数组,在对象和数组中包含多个函数定义

结论: 一次外层函数调用时,抛出的多个内层函数对象,共用这次生成的一个变量。
其中一个函数修改了变量,另一个函数使用受保护的变量时,也受影响。.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你脸上有BUG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值