我从来不理解JavaScript闭包,但我用了它好多年

这篇具有很好参考价值的文章主要介绍了我从来不理解JavaScript闭包,但我用了它好多年。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

 📫 大家好,我是南木元元,热衷分享有趣实用的文章,希望大家多多支持,一起进步!

 🍅 个人主页:南木元元

你是否学习了很久JavaScript但还没有搞懂闭包呢?今天就来聊一下被很多人誉为JavaScript中最难理解的概念之一的闭包。


目录

闭包的概念

闭包产生的原因

作用域&作用域链

闭包的本质

闭包的表现形式

闭包的用途

封装私有变量

做缓存

闭包的缺点

结语


闭包的概念

  • 红宝书(P309)上对于闭包的定义

闭包指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。

  • MDN对闭包的定义

闭包是指那些能够访问自由变量的函数。其中自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。

总结一下就是,闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量。

下面就是一个闭包的例子。

// 外部函数
function outerFunction() {
  let outerVariable = 'outer';
  // 内部函数
  function innerFunction() {
    console.log(outerVariable);
  }
  
  return innerFunction;
}
  
const innerFunc = outerFunction();
innerFunc(); // outer

在上面的代码示例中,函数outerFunction内部有一个innerFunction函数,innerFunction函数可以访问到outerFunction函数中的变量,此时函数innerFunction就是一个闭包。

闭包产生的原因

作用域&作用域链

首先需要知道作用域和作用域链的概念。

作用域就是变量与函数的可访问范围

在js中,有三种作用域:

  • 全局作用域:变量在整个全局中都能被访问到
  • 函数作用域:变量只能在当前函数内被访问到
  • 块级作用域:变量通过ES6中的let和const来声明,只能在⼀对花括号{ }包裹的块中访问

作用域链:从当前作用域开始一层层往上找某个变量,如果找到全局作用域还没找到,就放弃寻找,这种层级关系就是作用域链。

  • 静态作用域

js 采用的是静态作用域词法作用域),即函数的作用域在函数定义时就确定了

var num = 10;
function f1(){
  console.log(num)
}
function f2(){
  var num  = 20;
  f1()
}
f2();//10

以上代码的执行结果为10,这段代码经历了这样的执行过程:

  • f2函数调用,f1函数调用
  • 在f1函数作用域内查找是否有局部变量num
  • 发现没找到,于是根据书写位置,向上一层作用域(全局作用域)查找,输出10

静态作用域也称为词法作用域,即在词法分析时生成的作用域,词法分析阶段,也可以理解为代码书写阶段,当你把函数书写到某个位置,不用执行,它的作用域就已经确定了。与之相对的是动态作用域,函数的作⽤域在函数调⽤时才确定,如果采用动态作用域,那么上述结果为20(如果想深入了解,可以去看这篇文章)。

在了解了js的作用域和作用域链后,让我们来看看下面这段代码:

var num = 10;

function fn() {
  var num = 20;
  function fun() {
    console.log(num);//20
  }
  return fun;
}
var x = fn();
x();

上述例子中有三个作用域:全局作用域、fn的函数作用域、fun的函数作用域,它们的关系如下:

我从来不理解JavaScript闭包,但我用了它好多年,javascript,javascript,前端

作用域链关系如下:

我从来不理解JavaScript闭包,但我用了它好多年,javascript,javascript,前端

在这段代码中,fn的作用域指向有全局作用域和它本身,而fun的作用域指向全局作用域、fn和它本身。而作用域是从最底层向上找,当我们试图在fun这个函数里访问变量num的时候,此时函数作用域内没有num变量,当前作用域找不到,我们需要去上层作用域(fn函数作用域)找,在这里我们找到了num为20,输出即可(如果找到全局作用域还没有的话就会报错)。

闭包的本质

问大家一个问题:那是不是只有像上述例子一样返回函数才算是产生了闭包呢?

其实,闭包产生的本质就是:当前环境中存在指向父级作用域的引用。因此我们还可以这么做:

var fun;
function fn() {
  var num = 2;
  fun = function() {
    console.log(num); //2
  }
}
fn();
fun();

让fn执行,给fun赋值后,等于说现在fun拥有了全局、fn和fun本身这几个作用域的访问权限,还是自底向上查找,最近是在fn中找到了num,因此输出2。

在这里是外面的变量fun存在着父级作用域的引用,因此产生了闭包,形式变了,本质没有改变。

闭包的表现形式

明白了本质后,那我们思考下,实际场景中,闭包是如何体现的呢?

  • 返回一个函数(上面已经举例)
  • 作为函数参数传递
var a = 1;
function foo(){
  var a = 2;
  function baz(){
    console.log(a);
  }
  bar(baz);
}
function bar(fn){
  // 这就是闭包
  fn();
}
// 输出2,而不是1
foo();
  • 定时器、事件监听或者任何异步中,只要使用了回调函数,实际上就是在使用闭包。

比如以下的闭包保存的仅仅是window和当前作用域。

// 定时器
setTimeout(function timeHandler(){
  console.log('111');
},100)

// 事件监听
$('#btn').click(function(){
  console.log('222');
})
  • IIFE(立即执行函数表达式)创建闭包,保存了全局作用域window和当前的函数作用域,因此可以使用全局的变量。
var a = 2;
(function IIFE(){
  // 输出2
  console.log(a);
})();

现在,你是否会感叹一句:好家伙,原来我用了闭包这么多年!

闭包的用途

闭包有两个常用的用途:

  • 封装私有变量
  • 做缓存

封装私有变量

闭包可以使我们在函数外部能够访问到函数内部的变量。通过使用闭包,可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量,以防止其被外部访问和修改。

在下面这个例子中,调用函数,输出的结果都是1,但是我们的代码效果是想让count每次加一。

function add() {
    let count = 0;
    count++;
    console.log(count);
}
add()   //输出1
add()   //输出1
add()   //输出1

一种显而易见的方法是将count提到函数体外,作为全局变量。这么做当然是可以解决问题,但是在实际开发中,一个项目由多人共同开发,你不清楚别人定义的变量名称是什么,很容易冲突,有什么其他的办法可以解决这个问题呢?

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

答案是用闭包。在上面的代码示例中,add函数返回了一个闭包a,其中包含了count变量。由于count只在add函数内部定义,因此外部无法直接访问它。但是,由于a函数引用了count变量,因此count变量的值可以在闭包内部被修改和访问。这种方式可以用于封装一些私有的数据和逻辑。

做缓存

函数一旦被执行完毕,其内存就会被销毁。而闭包可以使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收。

function foo(){
  var myName ='张三';
  let test = 1;
  var innerBar={
      getName: function(){
          console.log(test);
          return myName;
      },
      setName:function(newName){
          myName = newName;
      }
  }
  return innerBar;
}
var bar = foo();
console.log(bar.getName()); //1 张三
bar.setName('李四');
console.log(bar.getName()); //1 李四

这里var bar = foo() 执行完后本来应该被销毁,但是因为形成了闭包,所以导致foo执行上下文没有被销毁干净,被引用了的变量myName、test没被销毁,闭包里存放的就是变量myName、test,这个闭包就像是setName、getName的专属背包,setName、getName依然可以使用foo执行上下文中的test和myName。

闭包的应用是非常广泛的,比如常见的防抖和节流等其实也都是闭包的应用。

闭包的缺点

闭包也存在着一个潜在的问题,由于闭包会引用外部函数的变量,但是这些变量在外部函数执行完毕后没有被释放,那么这些变量会一直存在于内存中,这可能会带来内存泄漏问题,因此,需要及时释放闭包,即手动调用闭包函数,并将其返回值赋值为null,这样可以让闭包中的变量及时被垃圾回收器回收。

结语

本文主要介绍了被誉为JavaScript中最难理解的概念之一的闭包,闭包的表现形式多样、应用广泛,日常开发中其实都有闭包的身影,在实际的开发过程中,合理地使用闭包可以帮助我们更加高效地编写代码,提高程序的性能和可维护性。

🔥如果此文对你有帮助的话,欢迎💗关注、👍点赞、⭐收藏✍️评论支持一下博主~ 文章来源地址https://www.toymoban.com/news/detail-751652.html

到了这里,关于我从来不理解JavaScript闭包,但我用了它好多年的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • JavaScript闭包详细介绍

    闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的常见方式是让一个内嵌函数访问其外部(父级)函数的变量,即使外部函数已经执行完毕,其变量仍然存在于内存中。闭包的主要特点是它可以“记住”外部函数的变量。 变量持久化: 闭包能够保持对外部作

    2024年02月20日
    浏览(21)
  • JavaScript 的 闭包

    在 JavaScript 中,闭包是一种强大的特性,它允许函数在结束执行后,仍能访问并控制其外部的局部变量。这种特性在许多高级 JavaScript 编程场景中都发挥着关键作用,如创建函数工厂、实现数据隐藏和封装等。 JavaScript 的变量作用域 在 JavaScript 中,变量的作用域可以是全局的

    2024年02月06日
    浏览(27)
  • JavaScript高级:闭包与作用域

    在 JavaScript 的世界里,闭包是一个令人着迷且神秘的概念,它为我们提供了一种强大的能力,能够在函数内部捕获并保留外部作用域的变量。本文将详细解释闭包的概念与应用,带你揭开 JavaScript 的神秘面纱,通俗易懂地理解闭包的奥秘。 1. 作用域与闭包的关系 作用域是指

    2024年02月13日
    浏览(28)
  • JavaScript闭包漏洞与修补措施

    请先看下面一段代码 可以看出,这是一段很典型的js闭包代码,可以通过obj调用get方法传一个参数,如果传的是a就可以得到闭包内的对象sonObj.a 如下,正确的获取到了sonObj的属性a 这是一个典型的闭包场景,这样做的目的是为了屏蔽这个obj,不让外边直接访问它,只能读取它的某一个属

    2024年02月10日
    浏览(27)
  • JavaScript 作用域与闭包

    本文内容学习于:后盾人 (houdunren.com) 1.作用域 1)函数被执行后其环境变量将从内存中删除。下面函数在每次执行后将删除函数内部的 total 变量。  function count() {         let total = 0; } count (); 2)函数每次调用都会创建一个新作用域 3)如果子函数被使用时父级环境将被保留

    2024年02月14日
    浏览(24)
  • JavaScript(函数,作用域和闭包)

    类似于Java中的方法,是完成特定任务的代码语句块 特点 使用更简单 不用定义属于某个类,直接调用执行 分类 系统函数 自定义函数 1.将字符串转换为整型数字 js示例1 从下标为0起,依次判断每个字符是否可以转换为一个有效数字 如果不是有效数字,则返回NaN,不再继续执

    2024年02月10日
    浏览(29)
  • 【面试高频】JavaScript作用域、闭包、变量提升

    目录 前言 一、作用域 1. 局部作用域 2. 全局作用域 二、作用域链 三、闭包 1. 闭包是什么 2. 闭包的运用 JavaScript 中的作用域、闭包和变量提升是 JavaScript 中的重要概念,也是面试高频考点。 作用域规定了变量的可见性和生命周期,闭包通过捕获自由变量的方式延长了变量的

    2024年02月12日
    浏览(23)
  • Go的闭包理解

    笔记仓库:gitee.com/xiaoyinhui 一点点笔记,以便以后翻阅。

    2024年02月22日
    浏览(26)
  • 闭包的理解

    变量的私有化。一个函数内的变量,随着函数的执行完毕,对于的变量也会随着销毁,闭包可以让变量在函数执行完毕之后不必销毁,通常将这个变量通过匿名函数的形式return出去,这个变量只能被访问,不能被修改。 打印的结果: 可以看出a变量每次都递增了1,从这个结论来看也可以

    2024年02月09日
    浏览(24)
  • 学习并深入理解闭包

    前言 学习闭包前,先学点别的。 程序执行时: 1.编译阶段 创建变量对象GO,包括变量和函数作用域装在一块内存中。但是没有赋值,变量都是undefined,函数:0xxx 2.创建执行上下文 里面有VO对应ao(函数里的变量,没执行,undefined,执行,被赋值) 3.代码执行 函数里的变量赋值

    2024年02月08日
    浏览(21)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包