通俗易懂的JavaScript闭包原理

开始故事前,先来做一道例题~

场景:函数只允许被调用一次

给定一个函数 fn ,它返回一个新的函数,返回的函数与原始函数完全相同,只不过它确保 fn 最多被调用一次。

  • 第一次调用返回的函数时,它应该返回与 fn 相同的结果。
  • 第一次后的每次调用,它应该返回 undefined 。

场景条件1:fn = (a,b,c) => (a + b + c), calls = [[1,2,3],[2,3,6]]

场景结果1:[{"calls":1,"value":6}]

场景使用1

const onceFn = once(fn);
onceFn(1, 2, 3); // 6
onceFn(2, 3, 6); // undefined, fn 没有被调用

场景条件2:fn = (a,b,c) => (a * b * c), calls = [[5,7,4],[2,3,6],[4,6,8]]

场景结果2:[{"calls":1,"value":140}]

场景使用2

const onceFn = once(fn);
onceFn(5, 7, 4); // 140
onceFn(2, 3, 6); // undefined, fn 没有被调用
onceFn(4, 6, 8); // undefined, fn 没有被调用

代码示例

var once = function(fn){
  let isFirst = false;
  return function(...args){
    if(!isFirst){
      isFirst = true;
      return fn(...args);
    }
    return 'returns undefined without calling fn';
  }
}

// let fn = (a,b,c) => (a + b + c)
// let onceFn = once(fn)
// console.log(onceFn(1,2,3)); // 6
// console.log(onceFn(2,3,6)); // returns undefined without calling fn

let fn = (a,b,c) => (a * b * c)
let onceFn = once(fn)
console.log(onceFn(5,7,4)); // 140
console.log(onceFn(2,3,6)); // returns undefined without calling fn
console.log(onceFn(4,6,8)); // returns undefined without calling fn

方法解析

  • once函数:接收一个函数fn作为参数,并返回一个新的函数
  • 新函数
    • 在第一次被调用时,会执行fn并返回其结果,同时将闭包中的isFirst变量设置为true;
    • 从第二次开始,因为called已经是true了,该函数只会返回returns undefined without calling fn

最终拓展

如何理解once函数中的闭包原理呢?

关于闭包的故事

想象一下你有一个房间,这个房间就是你的函数。在这个房间里,你放了一些东西,比如椅子、桌子和玩具。这些东西就相当于你在函数内部定义的变量。

现在,你有一个朋友想进这个房间看看,但你不能让他直接进去(就像你不能直接访问函数内部的变量一样),因为房间里的某些东西可能很私密或者只能在特定条件下才能看。于是,你给了你的朋友一个过滤镜,这个过滤镜就是你的函数返回的一个“东西”,通常是一个函数。

通过这个过滤镜,你的朋友可以窥视房间里的某些东西,但只能是你允许他看的那些。

  • 房间 —— 函数
  • 椅子、桌子和玩具(私密) —— 变量
  • 过滤镜 —— 函数的返回值
  • 我将房间整理,只让椅子、桌子在窗口 —— 函数内部定义的另一个函数
  • 我将房间里面的过滤镜给朋友,朋友带上过滤镜,进入房间看到椅子、桌子,将椅子拉出坐了下去 —— 内部函数访问并操作外部函数作用域中的变量

这就是闭包的核心概念:一个函数返回了另一个函数,而返回的这个函数可以访问到外部函数作用域中的变量

这里的“过滤镜”和“房间”的关系就是闭包。闭包让你可以在一个函数内部定义另一个函数,并且让内部函数访问并操作外部函数作用域中的变量,即使外部函数已经执行完毕并返回了。

为什么这很重要呢?

因为闭包提供了一种封装私有变量的方式。

  1. 外部函数可以视为一个“私有”的作用域,而内部函数则可以通过闭包来访问这个作用域中的变量,同时外部代码无法直接访问这些变量。
  2. 另外,闭包还允许你创建具有持久状态(即状态可以被保存并在后续调用中访问)的函数。因为闭包中的变量在外部函数返回后仍然存在于内存中,所以内部函数可以持续访问和修改这些变量。

再来两个小案例

1、案例:for循环里的闭包

该案例涉及到var、let的区别,可以参考小编的另一篇文章

ES6篇(上)_const a = '' a = 'zhangsan-CSDN博客文章浏览阅读943次,点赞4次,收藏3次。ES6主要内容:const的使用,let与var的区别,增强写法,解构赋值(数组解构,对象解构),深浅拷贝,高阶函数(filter、map、reduce)_const a = '' a = 'zhangsanhttps://blog.csdn.net/qq_51478745/article/details/127140261

// 错误代码
var list = document.querySelectorAll('li');
for (var i = 0; i < list.length; i++) {
  list[i].onclick = function(){
    console.log(i);//永远只输出3
  }
}
// 正确代码1
var list = document.querySelectorAll('li');
for (let i = 0; i < list.length; i++) {
  list[i].onclick = function(){
    console.log(i);//输出点击的li下标
  }
}
// 正确代码2
var list = document.querySelectorAll('li');
for (var i = 0; i < list.length; i++) {
  (function(i){
    list[i].onclick = function(){
      console.log(i);//输出点击的li下标
    }
  })(i)
}

2、案例:闭包避免变量被资源回收机制销毁

function add(){
  var a = 0;
  return function addCount(){
    a++;
    console.log(a);
  }
}
var count = add();
count();//1
count();//2

总结一下

闭包就是一个函数记住了并访问其词法作用域(即外部函数的作用域)的能力。这是通过函数返回另一个函数,而这个返回的函数访问了外部函数的局部变量来实现的。闭包在JavaScript中非常有用,它们是实现模块化和封装的关键技术之一。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

五秒法则

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

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

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

打赏作者

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

抵扣说明:

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

余额充值