广告
返回顶部
首页 > 资讯 > 前端开发 > 其他 >一文详解JavaScript中的闭包
  • 912
分享到

一文详解JavaScript中的闭包

闭包前端JavaScript 2023-05-14 22:05:24 912人浏览 薄情痞子
摘要

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

javascript 闭包是一种重要的概念,在 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 <= 1) {
    return n;
  }
  return fibonacci(n - 1) + fibonacci(n - 2);
}

const memoizedFib = memoize(fibonacci);
console.log(memoizedFib(10)); // 输出 55
console.log(memoizedFib(10)); // 输出 55,直接从缓存中读取

在这个示例中,我们定义了一个 memoize 函数,它接受一个函数作为参数,并返回了一个闭包函数。闭包函数内部维护了一个缓存对象 cache,用于保存函数的计算结果。每次调用闭包函数时,它会根据传入的参数生成一个唯一的键值,并从缓存中尝试读取计算结果。如果缓存中已经存在该键值,直接返回缓存结果,否则调用传入的函数计算结果,并将结果保存到缓存中。这种方式可以避免多次计算同样的值,从而提高代码的性能。

实现模块化

使用闭包可以实现模块化的编程方式,这种方式可以将代码分割成多个模块,使得每个模块只关注自己的功能,从而提高代码的可维护性和可读性。同时,闭包也可以实现公共和私有变量的封装,避免了全局变量的污染。

例如,我们可以使用闭包来实现一个简单的模块:

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的结果。

我们使用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中,每当创建一个函数时,都会为该函数创建一个新的作用域链。函数作用域链是一个指向其父级作用域的指针列表,其中包含了该函数能够访问的变量和函数。

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

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

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

安全问题

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

可读性问题

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

总结

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

以上就是一文详解JavaScript中的闭包的详细内容,更多请关注编程网其它相关文章!

--结束END--

本文标题: 一文详解JavaScript中的闭包

本文链接: https://www.lsjlt.com/news/207864.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 一文详解JavaScript中的闭包
    JavaScript 闭包是一种重要的概念,在 JavaScript 编程中被广泛使用。尽管它可能会让初学者感到困惑,但它是理解 JavaScript 语言核心的关键概念之一。本文将深入探讨 JavaScript 闭包,让你了解它是如何工作...
    99+
    2023-05-14
    闭包 前端 JavaScript
  • 一文详解JavaScript闭包典型应用
    目录1.应用1.1 模拟私有变量1.2 柯里化1.3 偏函数1.4 防抖1.5 节流2.性能问题2.1 内存泄漏2.2 常见的内存泄漏3.闭包与循环体3.1 这段代码输出啥3.2 改...
    99+
    2022-11-13
  • 一文了解JavaScript闭包函数
    目录变量作用域闭包的概念闭包的用途闭包的缺点最后总结一下闭包的好处与坏处总结变量作用域 要理解JavaScript闭包,就要先理解JavaScript的变量作用域。 变量的作用域有...
    99+
    2022-11-12
  • JavaScript闭包详解
    目录1.什么是闭包?1.2 闭包的记忆性:函数能够记住定义时所处的环境1.3 闭包现象:JS中每次创建函数时都会创建闭包2.闭包的功能:记忆性、模拟私有变量2.1 记忆性2.2 模拟...
    99+
    2022-11-12
  • 一文剖析JavaScript中闭包的难点
    目录一、作用域基本介绍1. 全局作用域2. 函数作用域3. 块级作用域二、什么是闭包1. 闭包的基本概念2. 闭包产生的原因3. 闭包的表现形式三、如何解决循环输出问题1. 利用 I...
    99+
    2022-11-13
  • 详解JavaScript闭包问题
    闭包是纯函数式编程语言的传统特性之一。通过将闭包视为核心语言构件的组成部分,JavaScript语言展示了其与函数式编程语言的紧密联系。由于能够简化复杂的操作,闭包在主流JavaSc...
    99+
    2022-11-12
  • 一文搞懂JavaScript中最难理解概念之一的闭包
    目录一、闭包的概念二、怎么实现闭包三、闭包的用途1.封装私有变量2. 做缓存3. 模块化编程(实现共有变量)四、闭包的缺点五、最后的话一、闭包的概念 当通过调用外部函数返回的内部函数...
    99+
    2023-05-14
    JavaScript闭包实现 JavaScript闭包应用 JavaScript闭包
  • 一文了解你不知道的JavaScript闭包篇
    目录前言理解闭包升级版闭包循环和闭包模块小结前言 JavaScript语言中有一个非常重要又难以掌握,近似神话的概念-闭包。对于有一点JavaScript使用经验但从未真正理解闭包概...
    99+
    2022-11-13
    JavaScript 闭包
  • 关于javascript解决闭包漏洞的一个问题详解
    目录解决闭包漏洞的一个问题问题原理:方法一:方法二:解决办法;解决方法二:总结解决闭包漏洞的一个问题 在不修改下面代码的情况下,修改obj的内容 var o = (()=>{ ...
    99+
    2022-11-13
    js解决闭包漏洞的方法 js解决闭包漏洞的方法有哪些 js闭包陷阱
  • JavaScript高级之闭包详解
    目录1. 闭包的概念知识点的补充:2. 闭包的作用:3. 闭包示例3.1 点击li,输出当前li的索引号总结1. 闭包的概念 来看一般函数的执行和启发: func...
    99+
    2022-11-12
  • 详解JavaScript中的闭包是如何产生的
    目录闭包的产生多个内部函数共享一个闭包对象结尾这次从内存管理的角度来看看,闭包是怎么产生的。 我们知道,在调用函数时,其实会产生临时的 调用栈。这些调用栈保存的是 执行上下本,并实际...
    99+
    2022-12-28
    JavaScript闭包如何产生 JavaScript闭包
  • 一文详解JavaScript中的Mixin
    目录什么是 Mixin显示混入隐式混入总结类的出现最终使 JavaScript 非常容易使用继承语法,JavaScript 类比大多数人意识到的更强大,它是构建真正的 mixins ...
    99+
    2023-05-18
    JavaScript 中的 Mixin JavaScript Mixin介绍 JavaScript Mixin
  • 关于Javascript闭包与应用的详解
    目录前言一、什么是闭包?1.1闭包满足条件代码1.2闭包产生时机二、常见闭包示例2.1 子函数做为实参传递2.2 计数器使用(函数返回)三、闭包作用3.1 闭包作用四、闭包应用4.1...
    99+
    2022-11-12
  • 一文浅析Golang中的闭包
    1、什么是闭包?在真正讲述闭包之前,我们先铺垫一点知识点:函数式编程函数作用域作用域的继承关系【相关推荐:Go视频教程】1.1 前提知识铺垫1.2.1 函数式编程函数式编程是一种编程范式,看待问题的一种方式,每一个函数都是为了用小函数组织成...
    99+
    2023-05-14
    闭包 Go 后端
  • JavaScript闭包原理及作用详解
    目录简介闭包的用途柯里化实现公有变量缓存封装(属性私有化)闭包的原理垃圾收集简介实际开发中的优化简介 说明 本文介绍JavaScript的闭包的作用、用途及其原理。 闭包的定义 闭包...
    99+
    2022-11-12
  • JavaScript中的闭包
    一.什么是闭包 1.闭包的定义 闭包并不是JS特有的,因此可以从两个角度定义闭包。 1)计算机科学中 闭包又称为词法闭包(在进行词法分析的时候这个闭包就确定了),或者是函数闭包。是在支持头等函数的编程语言中(意思是函数作为一等公民的编程语言...
    99+
    2023-08-18
    javascript 前端 java
  • 一文详解JavaScript中prototype的使用
    目录prototype初步认识函数有prototype属性,函数创建的对象没有获得当前对象的属性父和子的扩展子的proto和prototype的区别扩展得到的东西到底从哪来的prot...
    99+
    2022-11-13
  • 关于Python中的闭包详解
    目录1、闭包的概念2、实现一个闭包3、在闭包中外函数把临时变量绑定给内函数4、闭包中内函数修改外函数局部变量5、注意:6、练习:总结1、闭包的概念 请大家跟我理解一下,如果在一个函数...
    99+
    2022-11-13
  • Go语言中的闭包详解
    一、函数的变量作用域和可见性 1.全局变量在main函数执行之前初始化,全局可见 2.局部变量在函数内部或者if、for等语句块有效,使用之后外部不可见 3.全局变量和局部变量同名的...
    99+
    2022-11-13
  • 详解golang中的闭包与defer
    目录闭包与defer1.闭包2.defer闭包与defer 1.闭包 闭包 : 一个函数与其相关的引用环境组合的一个实体,其实可以理解为面向对象中类中的属性与方法。如代码块中,函数f...
    99+
    2022-11-11
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作