什么是闭包?闭包的作用及应用场景

【闭包】前端的“保护神”——闭包详解+底层原理 它们能够保持对外部数据(如事件触发时的状态、函数参数等)的访问,即使在异步操作完成后,闭包仍然能够访问这些数据。你可以将某些功能封装在闭包内部,外部只能通过公开的方法调用它们,从而达到隐藏细节、减少外部依赖的目的。闭包提供了数据封装的能力,可以将状态和行为封装在一个函数内部,并通过暴露的接口与外部进行交互。闭包可以用于权限管理和角色管理的场景中,通过闭包来封装不同角色的权限信息,从而提供灵活的权限控制。是私有的,外部无法直接访问。,即使这个函数在外部被调用时,依然能访问到其定义时的父函数的局部变量。 阅读详情
AI摘要

本文详细解释了JavaScript中闭包的概念、作用及注意事项,并通过多个示例帮助读者掌握闭包的使用技巧。

一、什么是闭包

假设,把下面三行代码放在一个立即执行函数中。
在这里插入图片描述
三行代码中,有一个局部变量local,有一个函数foo,foo里面可以访问到local变量。

好了这就是一个闭包:

「函数」和「函数内部能访问到的变量」的总和,就是一个闭包。

有的同学就疑惑了,闭包这么简单么?

「我听说闭包是需要函数套函数,然后 return 一个函数的呀!」

比如这样:

function foo(){
  var local = 1;
  function bar(){
    local++;
    return local;
  }
  return bar;
}
var func = foo()
func()

这里面确实有闭包,local变量和bar函数组成了一个闭包(Closure)。

为什么要函数套函数呢?

是因为需要局部变量,所以才把 local 放在一个函数里,如果不把 local 放在一个函数里,local 就是一个全局变量了,达不到使用闭包的目的——隐藏变量(等会会讲)。

有些人看到「闭包」这个名字,就一定觉得要用什么包起来才行。其实这是翻译问题,闭包的原文是 Closure,跟「包」没有任何关系。

所以函数套函数只是为了造出一个局部变量,跟闭包无关。

为什么要 return bar 呢?

因为如果不 return,你就无法使用这个闭包。把 return bar 改成 window.bar = bar 也是一样的,只要让外面可以访问到这个 bar 函数就行了。

所以 return bar 只是为了 bar 能被使用,也跟闭包无关。

二、闭包的作用

闭包常常用来「间接访问一个变量」。换句话说,「隐藏一个变量」。

假设我们在做一个游戏,在写其中关于「还剩几条命」的代码。

如果不用闭包,你可以直接用一个全局变量:

window.lives = 30 // 还有三十条命

这样看起来很不妥。万一不小心把这个值改成 -1 了怎么办。所以我们不能让别人「直接访问」这个变量。怎么办呢?

用局部变量。

但是用局部变量别人又访问不到,怎么办呢?

暴露一个访问器(函数),让别人可以「间接访问」。

代码如下:

!function(){

  var lives = 50

  window.奖励一条命 = function(){  // 简明起见,用了中文
    lives += 1
  }

  window.死一条命 = function(){
    lives -= 1
  }

}()

那么在其他的 JS 文件,就可以使用 window.奖励一条命() 来涨命,使用 window.死一条命() 来让角色掉一条命。

看到闭包在哪了吗?

在这里插入图片描述
闭包是 JS 函数作用域的副产品。换句话说,正是由于 JS 的函数内部可以使用函数外部的变量,所以这段代码正好符合了闭包的定义。而不是 JS 故意要使用闭包。

JS变量作用域存在于函数体中即函数体,并且变量的作用域是在函数定义声明的时候就是确定的,而非在函数运行时。只要你懂了 JS 的作用域,你自然而然就懂了闭包,即使你不知道那就是闭包。

三、使用闭包的注意点

1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

四、思考题

如果你能理解下面两段代码的运行结果,应该就算理解闭包的运行机制了。

代码片段一

var name = "The Window";

var object = {
    name : "My Object",
    getNameFunc : function(){
        return function(){
            return this.name;
        };
    }
};

alert(object.getNameFunc()()); // The Window

代码片段二

var name = "The Window";

var object = {
    name : "My Object",
    getNameFunc : function(){
        var that = this;
        return function(){
            return that.name;
        };
    }
};

alert(object.getNameFunc()()); // My Object

五、应用场景

例子1

function fn(){
	var a = 1;
	return function(){
		return  ++a;                                     
	}
}
alert(fn()());
alert(fn()());  //每次执行都会初始化 a = 1
//2 2

例子2

function outerFn(){
  var i = 0; 
  function innerFn(){
      i++;
      console.log(i);
  }
  return innerFn;
}
var inner = outerFn();  //每次外部函数执行的时候,都会开辟一块内存空间,外部函数的地址不同,都会重新创建一个新的地址
inner();
inner();
inner();
var inner2 = outerFn();
inner2();
inner2();
inner2();   
//1 2 3 1 2 3

例子3

(function() { 
  var m = 0; 
  function getM() { 
    return m; 
  } 
  function seta(val) { 
    m = val; 
  } 
  window.g = getM; 
  window.f = seta; 
})(); 
f(100);
console.log(g());   //100  闭包找到的是同一地址中父级函数中对应变量最终的值

例子4

var add = function(x) { 
  var sum = x; 
  var tmp = function(x) { 
      sum = sum + x; 
      return tmp;    
  } 
  tmp.toString = function() { 
      return sum; 
  }
  return tmp; 
} 
alert(add(1)(2)(3));     //6
alert(add(1)(2)(3)(4));  //10

上面代码中,例add(1)(2)(3),代码执行add(1)的时候,声明了add函数的局部变量sum并赋值为1,同时返回子函数tmp,这样add(1)(2)(3)就相当于tmp(2)(3),因为tmp函数需要用的sum这个变量,使得add执行完毕之后并没有清除sum这个局部变量的数据。这样执行tmp(2)的时候将2与sum相加保存在sum上,同时返回自身tmp。这时sum为3,tmp(2)(3)就相当于tmp(3),然后运行tmp(3),把3与sum相加保存在变量sum上,同时返回tmp。这时add(1)(2)(3)运行之后结果是tmp函数(sum=6),而用console.log()函数显示结果的时候会将里面的内容自动转换为字符串,所以console.log(tmp)相当于console.log(tmp.toString()),而这个toString()函数被重定义为return sum,所以结果就是console.log(sum)//6,以此类推如果后面还有括号那么sum将继续加下去到最后剩下tmp然后运行toString()返回结果。

点这里了解柯里化函数。

例子5

function fun(n,o) {
    console.log(o);
    return {
         fun:function(m) {
               return fun(m,n);
         }
    };
}
var a = fun(0);  //undefined
a.fun(1);  //0  
a.fun(2);  //0  
a.fun(3);  //0  
var b = fun(0).fun(1).fun(2).fun(3);   //undefined  0  1  2
var c = fun(0).fun(1);  
c.fun(2);  
c.fun(3);  //undefined  0  1  1

例子6

//事件处理函数中闭包的写法
var lis = document.getElementsByTagName("li");
for(var i = 0; i < lis.length; i++){
    (function(i){
        lis[i].onclick = function(){
            console.log(i);
        };
    })(i);
}  

上面代码中,事件函数中用到了循环值i,利用闭包其实就是为了把当前的循环值传递进去。

例子7

function fn(){
   var arr = [];
   for(var i = 0;i < 5;i ++){
	 arr[i] = function(){
		 return i;
	 }
   }
   return arr;
}
var list = fn();
for(var i = 0,len = list.length; i < len; i ++){
   console.log(list[i]());
}  
//5 5 5 5 5

例子8

function fn(){
  var arr = [];
  for(var i = 0;i < 5;i ++){
	arr[i] = (function(i){
		return function (){
			return i;
		};
	})(i);
  }
  return arr;
}
var list = fn();
for(var i = 0,len = list.length; i < len; i ++){
  console.log(list[i]());
}  
//0 1 2 3 4

上面例子7和例子8代码对比,例子8在for循环里arr[i] = 这一句主要是赋予它一个闭包,赋予的是立即执行函数“(function(i{…}))(i)”,执行之后返回的函数保存了i的值。相比较例子7只是赋予了普通函数,返回的i在最后调用的时候才获取,这时候i已经变成5了, 如果把例子7的for循环中var i = 0改为let i = 0,结果就和例子8一样,因为let会保证i每次都是不同的。

参考文章

https://zhuanlan.zhihu.com/p/22486908
http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
https://blog.youkuaiyun.com/weixin_43586120/article/details/89456183

闭包的定义,闭包是什么?闭包解决了什么问题?闭包有哪些应用场景?使用闭包应该注意什么? 闭包(Closure)是计算机科学中的一个重要概念,特别是在函数式编程中。闭包可以定义为能够读取其他函数内部变量的函数。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。闭包包含自由(未绑定到特定对象)变量,这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义的(局部变量)。 阅读详情

相关推荐

PTA 7-8 暴力小学(二年级篇)-求出4个数字 (10分)java

PTA 7-8 暴力小学(二年级篇)-求出4个数字 (10分)java 小博是新城小学二年级学生,这孩子人很老实,数学、语文成绩都是中等。不过一旦坐在电脑前,写起代码来,则有些霸气侧漏,尤其擅长各种暴力方法。本题就是数学莫老师的题目之一,请你和小博一起暴力求解吧。 输入格式: 第一行给出这个和N(图中例子的和N为2000)。 输出格式: 第一行输出所有解的数目K,随后K行按照DCBA 4位数字从...

qryzhfj的博客 2192

js闭包的理解

\1. 什么叫闭包? 闭包就是能够读取其他函数内部变量的函数。由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成 " 定义在一个函数内部的函数" 。所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。 2.闭包的产生条件? 作用域嵌套 在父级作用域里生成了一个变量 var i=0 在子作用域里使用这个变量,这样声明的那个变量 i 就是自由变量,这种作用域嵌套环境叫做闭包环境。 在内存中存在和回收站相似的机制,叫做垃圾回收机制。...

ysj1218的博客 6917

AI时代敏捷开发新范式——意图驱动开发IDD

具体的功能代码、单元测试、集成测试用例,以及最终可运行的、支持PlantUML的Markdown转Word工具新版本。:业务方(如技术文档团队)反馈,现有的Markdown转Word工具在处理包含PlantUML代码块的文档时,无法正确生成图表,导致技术方案、架构图等关键信息丢失,严重影响了文档交付质量和团队效率。这不仅带来了交付周期显著缩短、资源效能倍增的可观收益,更深远的意义在于,它描绘了未来软件工业化、智能化的新图景:一个由精准意图驱动、AI自主执行、人类智慧引领的敏捷、高效、高质的新世界。

LiuJunjiang的专栏 557

什么是闭包?(详解闭包)

什么是闭包? 闭包的优点。 闭包的缺点。 闭包的作用。 闭包

我只是个很菜的前端 1万+

告别Keil/IAR:在VSCode中用CMake+Clangd打造丝滑的STM32开发环境(附完整配置)

本文详细介绍了如何在VSCode中使用CMake和Clangd搭建高效的STM32开发环境,替代传统的Keil/IAR工具。通过完整的配置指南和快速开发技巧,帮助开发者实现轻量级、高度可定制的现代化开发流程,显著提升STM32项目的开发效率。

weixin_26843605的博客 166

【故障处理】- ORA-01722

在执行升级脚本@?/rdbms/admin/catupgrd.sql时报错ora-01722。

weixin_39099660的博客 855

对闭包的理解

理解闭包

hddlsj的博客 2582

UEFI 基础教程 (零) - 目录

UEFI 基础教程 (十八) — UEFI Build Process简单分析。UEFI 基础教程 (十五) — 获取UEFI MemoryMap。UEFI 基础教程 (六)   —  PROTOCOL 简单使用。UEFI 基础教程 (十)   —  FileIO 简单使用。UEFI 基础教程 (一)   —  搭建UEFI开发环境。UEFI 基础教程 (九)   —  EVENT 简单使用。UEFI 基础教程 (七)   —  HOB 简单使用。UEFI 基础教程 (八)   —  PCD 简单使用。..

技术联盟 1537

面试官:谈谈对JS闭包的理解及常见应用场景(闭包的作用)

对JS闭包的理解及常见应用场景(闭包的作用); 变量作用域 如何从外部读取函数内部的变量 setTimeout传参,封装变量,为节点循环绑定click事件 3.闭包概念 4.闭包用途 5.闭包的理解 6.闭包应用场景 7.优缺点及解决办法 8.垃圾回收机制

LYFlied的博客 2万+

【Python基础】FastAPI 从入门到项目实战操作详解

【Python基础】FastAPI从入门到使用实战操作详解

congge_study的博客 454

TMS320F28335调试跟踪的问题:DSP从上电复位,一直到执行main()的第一条语句之间,都干了些什么,以及CCS调试时程序查找的问题

🏆 本文收录于《全栈Bug调优(实战版)》专栏,致力于分享我在项目实战过程中遇到的各类Bug及其原因,并提供切实有效的解决方案。无论你是初学者还是经验丰富的开发者,本文将为你指引出一条更高效的Bug修复之路,助你早日登顶,迈向财富自由的梦想🚀!同时,欢迎大家关注、收藏、订阅本专栏,更多精彩内容正在持续更新中。让我们一起进步,Up!Up!Up!    备注: 部分问题/难题源自互联网,经过精心筛选和整理,结合数位十多年大厂实战经验资深大佬经验总结所得,数条可行方案供所需之人参考。

**My Coding Family** 1213

了解js基础知识中的作用域和闭包以及闭包的一些应用场景,浅析函数柯里化

js基础知识中的作用域和闭包一、作用域1、作用域、自由变量简介(1)作用域定义(2)作用域实例演示(3)自由变量定义(4)自由变量实例演示2、作用域链简介(1)作用域链定义(2)作用域链实例演示3、全局作用域、函数作用域和块级作用域(1)全局作用域(2)函数作用域(3)块级作用域(ES6新增)二、闭包1、闭包是什么?(1)定义(2)本质(3)特性(4)优缺点(5)闭包的解决方法2、一般如何产生闭包?(1)函数作为返回值被传递(2)函数作为参数被返回3、闭包的应用场景(1)通过循环给页面上多个dom节点绑定事

moandaylab 551

轴承寿命预测 | 基于BiLSTM双向长短期记忆神经网络的轴承寿命预测附matlab完整代码

轴承寿命预测 | 基于BiLSTM双向长短期记忆神经网络的轴承寿命预测附matlab完整代码

默默科研仔的博客 275

Web前端入门第 66 问:JavaScript 作用域应用场景(闭包)

就像孙悟空给唐僧画个圈圈一样,这个圈圈就可以称之为作用域,这个比喻可能不太形象。作用域和孙悟空的圈圈还是有点区别,作用域内部可以获得作用域外部的变量,而内部的变量无法逃逸到作用域外面,如果逃逸出去了,那就造成内存泄漏了,程序将会出现崩溃!JS 的代码,闭包概念随处可见,在使用时也需特别小心,不放心的时候,就将变量释放xx = null!原创作者: linx转载于: https://www.cnblogs.com/linx/p/189324491.Web前端入门第1问:英语是否很重要?

weixin_41167863的博客 75

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

JavaScript的闭包是什么意思以及作用和应用场景

JavaScript闭包 1、什么是闭包   百度百科对于闭包的解释是:闭包是指可以包含自由(未绑定到特定对象)变量的代码块;这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)。 以代码进行阐释: function a(){  //建立函数a var i=0;    //定义变量i function b(){    //在...

weixin_30892763的博客 111

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

闭包 - 是什么- 有什么作用 - 它应用场景

闭包是这样的一种机制:函数嵌套函数,内部函数可以引用外部函数的参数和变量。参数和变量不会被垃圾回收机制收回。

W256999的博客 140

什么是闭包?闭包的作用?闭包的应用场景? 惰性函数 高阶函数

什么是闭包?闭包的作用?闭包的应用场景? 惰性函数 高阶函数

qq_37831545的博客 1381
上一篇: js立即执行函数
下一篇: 前端节流(throttle)和防抖(debounce)的区别和用法,并封装成vue指令
hst❀
博客等级 码龄8年 2364粉丝 147原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值