「JavaScript深入」彻底理解JS中的闭包

server/2024/9/25 11:35:24/

JavaScript深入 — 闭包

    • 一、概念
    • 二、示例
    • 三、实用的闭包
    • 四、用闭包模拟私有方法
    • 五、一个常见错误:在循环中创建闭包
      • 🌰 另一个经典例子-定时器与闭包
    • 六、优劣
      • 好处
      • 坏处
      • 解决
    • 七、图解闭包
    • 八、应用 💪
      • 封装私有变量
      • 函数工厂
      • 异步操作中的回调函数
      • 柯里化(封装函数)
    • 引申


一、概念

闭包简单来说就是引用了另一个函数作用域中变量的函数

闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在JavaScript中,闭包会随着函数的创建而被同时创建


二、示例

//函数作为返回值
function test() {const a = 1;return function() {console.log('a: ',a);}
}const fn = test();
const a = 2;
fn();// a: 1

通俗来说,a这个自由变量查找的规则,它会在函数定义的地方去向上一层查找它的值,而不会在函数执行的地方向上一层去查找

//函数作为参数
function test(fn) {const a = 1;fn();
}
const a = 2;
function fn() {console.log('a:',a);
}
test(fn);//a: 2

依然是上述通俗的定义,在函数定义处查找变量a的值


三、实用的闭包

🤔 假如,我们想在页面上添加一些可以调整字号的按钮。

function makeSizer(size){return function() {document.body.style.fontSize = size + 'px';}
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
document.getElementById("size-12").onclick = size12;
document.getElementById("size-14").onclick = size14;
document.getElementById("size-16").onclick = size16;

从本质上将,makeSizer 是一个函数工厂,他创建了将字体调整至指定大小的函数,上面的示例中,我们使用函数工厂创建了三个新函数,分别将字体大小调制12、14、16px
size12size14size16 都是闭包,它们共享相同的函数定义,但是保存了不同的词法环境,在size12中,size为12,其他同理。


四、用闭包模拟私有方法

Java支持将方法声明为私有,即它们只能被同一个类中的其他方法调用,而JavaScript没有这种原生支持,但我们可以使用闭包来模拟私有方法。

我们定义了一个匿名函数,用于创建一个计数器,我们立即执行了这个匿名函数,并将他的值赋给了变量Counter。我们可以把这个函数储存在另一个变量makerCounter中,并用他来创建多个计数器

var Counter = (function () {var privateCounter = 0;function changeBy(val) {privateCounter += val;}return {increment: function () {changeBy(1);},decrement: function () {changeBy(-1);},value: function () {return privateCounter;}}
})();console.log(Counter.value()); // 0
Counter.increment();
Counter.increment();
console.log(Counter.value()); // 2
Counter.decrement();
console.log(Counter.value()); // 1

在之前的示例中,每个闭包都有它自己的词法环境,而这次我们只创建了一个词法环境,为三个函数所共享:Counter.incrementCounter.decrementCounter.value

上面使用了立即执行函数表达式(IIFE)的相关内容,使用立即执行函数表达式好处有下

  • 避免变量污染(命名冲突),例如不同的第三方库恰好使用了同一个变量名称
  • 隔离作用域
  • 提高性能(减少对作用域的查找)

(在ES6前,JS原生并没有块级作用域的概念,所以IIFE可以用函数作用域来模拟块级作用域)

每次调用其中一个计数器时,通过改变这个变量的值,会改变这个闭包的词法环境。然而在一个闭包内对变量的修改,不会影响到另外一个闭包中的变量


五、一个常见错误:在循环中创建闭包

在ECMAScript 2015引入 let 关键字之前,在循环中有一个常见的闭包创建问题

javascript">function showHelp(help) {document.getElementById("help").innerHTML = help;
}function setupHelp() {var helpText = [{ id: "email", help: "Your e-mail address" },{ id: "name", help: "Your full name" },{ id: "age", help: "Your age (you must be over 16)" },];for (var i = 0; i < helpText.length; i++) {var item = helpText[i];document.getElementById(item.id).onfocus = function () {showHelp(item.help);};}}setupHelp();

这里赋值给 onfocus 的是闭包,在循环中创建了三个闭包,但它们共享了一个词法作用域,在这个作用域中存在一个变量 item。因为变量 item 使用var进行声明,由于变量提升,所以具有函数作用域。当 onfocus 的回调执行时,由于循环早已执行完毕,item 已经指向了 helpText

有了 let 就不会有这样的问题(块级作用域)

🌰 另一个经典例子-定时器与闭包

for(var i = 1; i <= 5; i++){setTimeout(function(){console.log(i + '');},100)
}

按照预期它应该依次输出 1 2 3 4 5,而结果它输出了五次5,同上理,在 setTimeout 的回调函数开始在 Callback Queue 中依次执行时,循环早执行解释,i的值为5,而这回调函数的五个闭包共享一个词法作用域

使用 let 关键字,按照预期依次输出 1 2 3 4 5

javascript">for(let i = 1; i <= 5; i++){setTimeout(function(){console.log(i + '');},100)
}

不用 let 关键字修改如下

javascript">for(var i = 1; i <= 5; i++){(function(number) {setTimeout(function(){console.log(number);},100)})(i)
}

六、优劣

好处

  • 保护函数内的变量安全,实现封装,防止变量流入其他环境发生命名冲突,避免全局变量污染
  • 在内存中维持一个变量,可以做缓存,延长变量的生命周期
  • 匿名自执行函数可以减少内存消耗

坏处

  • 内存泄漏:由于闭包中的函数引用了外部函数的变量,而外部函数的作用域在函数执行结束后并不会被销毁,这就导致了闭包函数中的变量也无法被销毁,从而占用了内存空间。如果闭包被滥用,可能会导致内存泄漏的问题。
  • 性能问题:闭包中的函数访问外部函数的变量需要通过作用域链来查找,而作用域链的长度决定了查找的速度。如果闭包层数较深,作用域链就会很长,从而影响了函数的执行效率。

解决

  • 及时释放闭包:如果不再需要使用闭包,可以手动将其赋值为 null,从而释放闭包中占用的内存空间。
  • 减少闭包层数:尽量减少闭包层数,避免作用域链过长,从而提高函数的执行效率。
  • 使用立即执行函数:可以使用立即执行函数来避免闭包的内存泄漏问题。由于立即执行函数在执行结束后会被立即销毁,因此其中的变量也会被释放。
  • 使用模块化编程:可以使用模块化编程来避免闭包的性能问题。在模块化编程中,每个模块都是一个独立的作用域,不会对全局作用域造成影响,从而避免了作用域链过长的问题。

七、图解闭包

在这里插入图片描述


八、应用 💪

封装私有变量

👇 闭包可以用于创建具有私有成员的对象。通过将变量放在闭包中,使之对外不可见。

function createCounter() {let count = 0;return {increment: function() {count++;},getValue: function() {return count;}};
}const counter = createCounter();
counter.increment();
console.log(counter.getValue()); // 输出 1

函数工厂

👇 如下,makeSizer是一个函数工厂,他创建了将字体调整至指定大小的函数。

javascript">function makeSizer(size){return function() {document.body.style.fontSize = size + 'px';}
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
document.getElementById("size-12").onclick = size12;
document.getElementById("size-14").onclick = size14;
document.getElementById("size-16").onclick = size16;

异步操作中的回调函数

👇 在异步编程中,回调函数通常是闭包,因为它们可以访问其定义时的上下文,这对于保存状态和数据非常有用。

javascript">function fetchData(url, callback) {// 异步操作获取数据setTimeout(function() {const data = /* 获取的数据 */;callback(data);}, 1000);
}fetchData('https://example.com/api', function(data) {console.log(data);
});

setTimeout 内部延迟执行的「匿名回调函数」可以访问外部函数fetchData的词法作用域,引用了其中变量callback,因此形成了闭包

柯里化(封装函数)

// 多参数柯里化
const curry = function(fn){return function curriedFn(...args){if(args.length<fn.length){return function(){return curriedFn(...args.concat([...arguments]));}}return fn(...args);}
}
const fn = (x,y,z,a)=>x+y+z+a;
const myfn = curry(fn);
console.log(myfn(1)(2)(3)(1));

引申

手写一个闭包?举个闭包的例子?


http://www.ppmy.cn/server/121812.html

相关文章

《让手机秒变超级电脑!ToDesk云电脑、易腾云、青椒云移动端深度体验》

前言 科技发展到如今2024年&#xff0c;可以说每一年都在发生翻天覆地的变化。云电脑这个市场近年来迅速发展&#xff0c;无需购买和维护额外的硬件就可以体验到电脑端顶配的性能和体验&#xff0c;并且移动端也可以带来非凡体验。我们在外出办公随身没有携带电脑情况下&#x…

【VUE3.0】动手做一套像素风的前端UI组件库---Message

目录 引言自己整一个UI设计稿代码编写1. 设计信息窗口基础样式2. 设置打开和关闭的方法3. 编写实例化组件的js文件4. 看下最终效果5. 组件完整代码6. 组件调用方式 总结 引言 本教程基于前端UI样式库 NES.css 的UI设计&#xff0c;自行研究复现。欢迎大家交流优化实现方法~ 此次…

【目标检测】隐翅虫数据集386张VOC+YOLO

隐翅虫数据集&#xff1a;图片来自网页爬虫&#xff0c;删除重复项后整理标注而成 数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;386 标注…

win11 wsl2安装ubuntu22最快捷方法

操作系统是win11&#xff0c;wsl版本是wsl2&#xff0c;wsl应该不用多介绍了&#xff0c;就是windows上的虚拟机&#xff0c;在wsl上可以很方便的运行Linux系统&#xff0c;性能棒棒的&#xff0c;而且wsl运行的系统和win11主机之间的文件移动是无缝的&#xff0c;就是两个系统…

Java中实现对用户发布的文章进行审核

在Java中实现对用户发布的文章进行审核&#xff0c;避免有违法内容&#xff0c;可以通过以下几个步骤&#xff1a; 集成第三方内容审核服务&#xff1a;可以使用如七牛云、百度AI等第三方服务进行内容审核。这些服务提供了文本、图片和视频的审核API&#xff0c;可以检测内容中…

CSS04-Chrome调试工具

Chrome 浏览器提供了一个非常好用的调试工具&#xff0c;可以用来调试我们的 HTML结构和 CSS 样式。

html TAB切换按钮变色、自动生成table

<!DOCTYPE html> <head> <meta charset"UTF-8"> <title>Dynamic Tabs with Table Data</title> <style> /* 简单的样式 */ .tab-content { display: none; border: 1px solid #ccc; padding: 1px; marg…

rust一些通用编程的概念

rust一些通用编程的概念 官网文档数据类型 - Rust 程序设计语言 中文版 (rustwiki.org) 变量&#xff0c;数据类型&#xff0c;条件判断&#xff0c;循环 变量 rust中变量的可变性是值得注意的 例如: fn main(){let number 1;number 2;println!("the number is {}&quo…