一文详解JavaScript中的闭包

青灯夜游

青灯夜游

2023-04-24

4198人浏览

原创

javascript 闭包是一种重要的概念,在 javascript 编程中被广泛使用。尽管它可能会让初学者感到困惑,但它是理解 javascript 语言核心的关键概念之一。本文将深入探讨 javascript 闭包,让你了解它是如何工作的,以及在实际应用中的使用方法。

什么是 JavaScript 闭包?

在 JavaScript 中,闭包是指一个函数能够访问在它外部定义的变量。这些变量通常被称为“自由变量”,因为它们不是该函数的局部变量,也不是该函数的参数。闭包可以在函数内部创建,也可以在函数外部创建。

JavaScript 中的每个函数都是一个闭包,因为它们都能够访问自由变量。当一个函数被调用时,它会创建一个新的执行环境,其中包含该函数的局部变量和参数。这个执行环境还包括一个指向该函数定义所在的作用域的引用。这个引用被称为函数的“作用域链”,它是由所有包含该函数定义的作用域对象组成的链表。【推荐学习:javascript视频教程

当函数内部需要访问一个自由变量时,它会先在自己的局部变量中查找是否存在该变量。如果不存在,它会继续沿着作用域链向上查找,直到找到该变量为止。这就是闭包的核心机制。

简单来说,闭包就是一个函数,其中包含了对外部变量的引用,这些变量在函数外部定义,但在函数内部仍然可以被访问和操作。闭包的本质是将函数和其引用的外部变量封装在一起,形成了一个不受外部干扰的环境,使得函数可以访问和修改外部变量,并且这些修改也会反映到函数外部的变量中。

理解闭包的工作原理对于编写高质量的 JavaScript 代码至关重要,因为它可以让我们更好地管理变量和函数的作用域,以及实现更加复杂的功能。

闭包的用途

封装变量和函数

闭包可以用来封装变量,使其不受外部干扰。这是因为闭包可以在函数内部定义一个变量,并在函数外部创建一个访问该变量的函数。这个访问函数可以访问该变量,但是外部无法直接访问该变量,从而保证了变量的安全性。

例如,我们可以使用闭包来实现一个计数器:

function createCounter() {
  let count = 0;
  return function() {
    count++;
    return count;
  }
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

在这个例子中,我们使用闭包来封装了计数器变量 count,使其不受外部干扰。每次调用 counter 函数时,它都会返回计数器的下一个值。

缓存数据

使用闭包可以缓存函数的计算结果,避免多次计算同样的值,从而提高代码的性能。这种方式适用于那些计算量较大、但结果不经常变化的函数,例如斐波那契数列等。

下面看一个代码示例:

function memoize(fn) {
  const cache = {};
  return function(...args) {
    const key = JSON.stringify(args);
    if (cache[key]) {
      return cache[key];
    } else {
      const result = fn(...args);
      cache[key] = result;
      return result;
    }
  }
}

function fibonacci(n) {
  if (n <p>在这个示例中,我们定义了一个 memoize 函数,它接受一个函数作为参数,并返回了一个闭包函数。闭包函数内部维护了一个缓存对象 cache,用于保存函数的计算结果。每次调用闭包函数时,它会根据传入的参数生成一个唯一的键值,并从缓存中尝试读取计算结果。如果缓存中已经存在该键值,直接返回缓存结果,否则调用传入的函数计算结果,并将结果保存到缓存中。这种方式可以避免多次计算同样的值,从而提高代码的性能。</p><h3 data-id="heading-4"><strong>实现模块化</strong></h3><p>使用闭包可以实现模块化的编程方式,这种方式可以将代码分割成多个模块,使得每个模块只关注自己的功能,从而提高代码的可维护性和可读性。同时,闭包也可以实现公共和私有变量的封装,避免了全局变量的污染。</p><p>例如,我们可以使用闭包来实现一个简单的模块:</p><pre class="brush:js;toolbar:false;">const module = (function() {
  const privateVar = 'I am private';
  const publicVar = 'I am public';
  function privateFn() {
    console.log('I am a private function');
  }
  function publicFn() {
    console.log('I am a public function');
  }
  return {
    publicVar,
    publicFn
  };
})();

console.log(module.publicVar); // 输出 'I am public'
module.publicFn(); // 输出 'I am a public function'
console.log(module.privateVar); // 输出 undefined
module.privateFn(); // 报错,无法访问私有函数

在这个示例中,我们定义了一个立即执行函数,内部返回了一个对象。对象中包含了公共变量和函数,以及私有变量和函数。通过这种方式,我们可以将代码分割成多个模块,每个模块只关注自己的功能,从而提高代码的可维护性和可读性。同时,私有变量和函数只在函数内部可见,外部无法访问和修改它们,从而避免了全局变量的污染。

事件处理

以下是一个使用闭包进行事件处理的例子:

function createCounter() {
  let count = 0;

  function increment() {
    count++;
    console.log(`Clicked ${count} times`);
  }

  function decrement() {
    count--;
    console.log(`Clicked ${count} times`);
  }

  function getCount() {
    return count;
  }

  return {
    increment,
    decrement,
    getCount
  };
}

const counter = createCounter();

document.querySelector('#increment').addEventListener('click', counter.increment);
document.querySelector('#decrement').addEventListener('click', counter.decrement);

在这个示例中,我们定义了一个名为createCounter的函数,该函数返回一个对象,该对象包含三个方法:increment,decrement和getCount。increment方法将计数器加1,decrement方法将计数器减1,getCount方法返回当前计数器的值。

我们使用createCounter函数创建了一个计数器对象counter,并将increment方法和decrement方法分别注册为加1和减1按钮的点击事件处理函数。由于increment和decrement方法内部引用了createCounter函数内部的局部变量count,因此它们形成了闭包,可以访问和修改count变量。

这个示例中,我们将计数器对象的逻辑封装在一个函数内部,并返回一个包含方法的对象,这样可以避免全局变量的使用,提高代码的可维护性和可重用性。

函数柯里化

以下是一个使用闭包实现的函数柯里化例子:

function add(x) {
  return function(y) {
    return x + y;
  }
}

const add5 = add(5); // x = 5
console.log(add5(3)); // 输出 8
console.log(add5(7)); // 输出 12

在这个例子中,我们定义了一个名为add的函数,该函数接受一个参数x并返回一个内部函数,内部函数接受一个参数y,并返回x + y的结果。

使用HTML,CSS,JavaScript开发Android应用程序 英文文字pdf版附源文件
使用HTML,CSS,JavaScript开发Android应用程序 英文文字pdf版附源文件

如果你了解HTML,CSS和JavaScript,您已经拥有所需的工具开发Android应用程序。本动手本书展示了如何使用这些开源web标准设计和建造,可适应任何Android设备的应用程序 - 无需使用Java。您将学习如何创建一个在您选择的平台的Andr​​oid友好的网络应用程序,然后转换与自由PhoneGap框架到一个原生的Andr​​oid应用程序。了解为什么设备无关的移动应用是未来的潮流,并开始构建应用程序,提供更

下载

我们使用add函数创建了一个新的函数add5,该函数的x值为5。我们可以多次调用add5函数,每次传入不同的y值进行求和运算。由于add函数返回了一个内部函数,并且内部函数引用了add函数内部的参数x,因此内部函数形成了一个闭包,可以访问和保留x值的状态。

这个例子中,我们实现了一个简单的函数柯里化,将接收多个参数的函数转化为接收一个参数的函数。函数柯里化可以帮助我们更方便地进行函数复合和函数重用。

异步编程

以下是一个使用闭包实现的异步编程的例子:

function fetchData(url) {
  return function(callback) {
    fetch(url)
      .then(response => response.json())
      .then(data => {
        callback(null, data);
      })
      .catch(error => {
        callback(error, null);
      });
  }
}

const getData = fetchData('https://jsonplaceholder.typicode.com/todos/1');
getData(function(error, data) {
  if (error) {
    console.error(error);
  } else {
    console.log(data);
  }
});

在这个例子中,我们定义了一个名为fetchData的函数,该函数接受一个URL参数,并返回一个内部函数。内部函数执行异步操作,请求URL并将响应解析为JSON格式的数据,然后调用传入的回调函数并将解析后的数据或错误作为参数传递。

我们使用fetchData函数创建了一个getData函数,该函数请求JSONPlaceholder API的一个TODO项,并将响应解析为JSON格式的数据,然后将数据或错误传递给回调函数。由于fetchData函数返回了一个内部函数,并且内部函数引用了fetchData函数内部的URL参数和回调函数,因此内部函数形成了闭包,可以访问和保留URL参数和回调函数的状态。

这个例子中,我们使用了异步编程模型,通过将回调函数作为参数传递,实现了在异步请求完成后执行相关的操作。使用闭包可以方便地管理异步请求和相关的状态,提高代码的可读性和可维护性。

闭包的缺陷

JS 闭包具有许多优点,但也有一些缺点,包括:

内存泄漏问题

由于闭包会将外部函数的局部变量引用保存在内存中,因此如果闭包一直存在,外部函数的局部变量也会一直存在,从而导致内存泄漏。

在 JavaScript 中,闭包是指一个函数能够访问并操作其父级作用域中的变量,即便该函数已经执行完毕,这些变量仍然存在。由于闭包会引用父级作用域中的变量,因此,这些变量不会在函数执行完毕时被垃圾回收机制回收,从而占用了内存资源,这就是闭包引起内存泄漏的原因。

以下是一个闭包引起内存泄漏的示例:

function myFunc() {
  var count = 0;
  setInterval(function() {
    console.log(++count);
  }, 1000);
}

myFunc();

在这个示例中,myFunc 函数中定义了一个变量 count,然后创建了一个计时器,在每秒钟打印 count 的值。由于计时器函数是一个闭包,它会保留对 myFunc 中的 count 变量的引用,这意味着即使 myFunc 函数执行完毕,计时器函数仍然可以访问 count 变量,从而阻止 count 变量被垃圾回收机制回收。如果我们不停地调用 myFunc 函数,将会创建多个计时器函数,每个函数都会占用一定的内存资源,最终会导致内存泄漏。

性能问题

由于闭包会在每次函数调用时创建新的作用域链,因此会增加函数的内存消耗和运行时间。在循环中创建闭包时,尤其需要注意性能问题。

在JavaScript中,每当创建一个函数时,都会为该函数创建一个新的作用域链。函数作用域链是一个指向其父级作用域的指针列表,其中包含了该函数能够访问的变量和函数。

闭包是指在函数内部定义的函数,它可以访问外部函数的变量和参数,并且可以在外部函数调用后继续使用这些变量和参数。在创建闭包时,它会保存对外部函数作用域链的引用,以便在需要时可以访问它。

由于闭包保存了对外部函数作用域链的引用,因此在每次函数调用时会创建一个新的作用域链。这是因为每次调用函数都会创建一个新的函数作用域链,即使该函数是由同一闭包创建的。这意味着每个闭包都有自己的作用域链,而且每次调用该闭包都会创建一个新的作用域链。

这也是为什么在使用闭包时需要小心的原因之一。由于每次调用闭包都会创建一个新的作用域链,因此可能会导致内存消耗和性能问题。在某些情况下,可能需要手动释放闭包的资源以避免内存泄漏问题。

安全问题

由于闭包可以访问外部函数的局部变量,如果不小心将私密数据存储在局部变量中,可能会被闭包访问和修改,从而导致安全问题。

可读性问题

由于闭包会延长变量的生命周期并隐式传递数据,因此可能会使代码变得难以理解和调试,尤其是在嵌套多层函数时。

总结

因此,尽管闭包是一种强大的编程技术,但在使用时需要注意以上缺点,并选择合适的应用场景和编程风格,以确保代码的可维护性和性能表现。

更多编程相关知识,请访问:编程教学!!

Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南

相关文章

java速学教程(入门到精通)
java速学教程(入门到精通)

java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

javascript 前端

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
墨刀AI提示词教学
墨刀AI提示词教学

本合集由PHP中文网精心整理,为您提供全面的墨刀AI提示词教学。内容涵盖高质量原型撰写公式与实操窍门,助您轻松掌握AI设计工具。无论是零基础入门还是进阶技巧,都能让您快速上手,大幅提升产品设计与协作效率。

2026.08.04

10

21

墨刀AI完整入门
墨刀AI完整入门

PHP中文网为您倾力打造墨刀AI保姆级入门指南完整版!本合集从零基础讲起,涵盖AI生成原型、提示词优化、图片转原型及多轮对话等核心功能。无论您是新手还是进阶用户,都能轻松掌握产品设计全流程。快来PHP中文网,一键解锁高效设计技巧,让想法即刻成型!

2026.08.04

8

20

墨刀AI进阶技巧
墨刀AI进阶技巧

本合集由PHP中文网精心整理,为您提供墨刀AI核心进阶策略指南。内容涵盖高效提示词写作、原型智能生成与微调、结构化导图制作及行业分析报告输出等实战技巧。助您轻松掌握AI设计工具,大幅提升产品设计与团队协作效率。

2026.08.04

10

14

火山引擎实名认证失败怎么办
火山引擎实名认证失败怎么办

火山引擎实名认证失败可能与证件信息填写错误、姓名或企业信息不一致、证件照片不清晰、营业执照状态异常、手机号验证失败或审核资料不完整有关。本专题整理个人认证、企业认证、资料上传、审核退回、重新提交和认证不通过的常见处理方法。

2026.08.04

5

10

火山引擎域名备案流程详解
火山引擎域名备案流程详解

火山引擎域名备案适合需要在火山引擎云服务器、对象存储、CDN或网站服务上绑定域名的用户参考。本专题整理备案入口、账号实名认证、备案类型选择、主体信息填写、网站信息提交、资料上传、初审核验、管局审核和备案失败排查,帮助用户完成网站上线前的备案流程。

2026.08.04

1

10

火山引擎DNS解析配置步骤
火山引擎DNS解析配置步骤

使用火山引擎DNS解析网站域名时,需要确认域名已完成管理接入,并正确配置服务器IP、CNAME地址或验证记录。本专题整理域名添加、记录类型选择、TTL设置、解析状态检查、备案和访问测试等流程,适合新手搭建网站时参考。

2026.08.04

3

10

火山引擎对象存储使用教程
火山引擎对象存储使用教程

火山引擎对象存储适合用于网站图片、视频文件、备份数据、静态资源和应用附件管理。本专题整理TOS控制台入口、存储桶创建、地域选择、权限设置、文件上传、访问链接生成、CDN加速、费用查看和常见上传或访问失败问题,帮助用户快速掌握对象存储基础操作。

2026.08.04

1

10

火山引擎云服务器使用教程
火山引擎云服务器使用教程

火山引擎云服务器使用教程适合第一次购买、部署和管理云服务器的用户参考。本专题整理控制台入口、实例创建、地域和配置选择、系统镜像设置、安全组放行、远程连接、网站部署、续费计费和常见连接失败问题,帮助用户快速完成云服务器基础使用流程。

2026.08.04

5

10

火山引擎API Key绑定大模型教程
火山引擎API Key绑定大模型教程

火山引擎API Key怎么绑定大模型适合需要在火山方舟、应用后台、脚本工具或AI编程软件中调用模型的开发者参考。本专题整理控制台服务开通、API Key创建、模型权限检查、模型ID选择、Base URL填写、调用测试和鉴权失败排查,帮助用户完成从密钥到模型调用的配置流程。

2026.08.04

2

10

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
JavaScript函数与闭包
JavaScript函数与闭包

共32课时 | 4.9万人学习

JavaScript高级框架设计视频教程
JavaScript高级框架设计视频教程

共22课时 | 4.1万人学习

玩儿转Swift视频教程
玩儿转Swift视频教程

共44课时 | 9.8万人学习