十分钟,快速理解JavaScript中的闭包概念
海阔凭鱼跃,天高任鸟飞。Hey 你好!我是猫力Molly
闭包已经是一个老生常谈的问题了,不同的人对闭包有不同的理解。今天我来浅谈一下闭包,大家一起来就“闭包”这个话题,展开讨论,希望能擦出一些不一样的火花。
理解闭包之前,我们得先了解“上下文”和“作用域”两个知识点
上下文
浏览器引擎在解析js代码的时候,大致会经过两个阶段。解析阶段和执行阶段
解析阶段:一段代码说白了只是一段有规则的代码文本而已,所以js
引擎会拿到代码时会事先解析代码,初始化过程中会将变量,参数,函数,表达式,运算符等等提取并存起来,并且将变量默认赋值为undefined
,函数默认为函数块,确定上下文关系等一系列准备工作
执行阶段:由上往下逐行执行代码,遇到对应的变量或函数,则去仓库里面匹配执行
console.log(str);
console.log(fun);
var str = "molly";
let str1;
console.log(str1);
function fun(a, b) {return a + b;
}
定义: 上下文可分为全局上下文和局部上下文,上下文决定了变量或函数他们可以访问哪些数据,以及他们的行为(在初始化阶段就已经确定好),每一个上下文都有一个变量对象(环境记录) ,这个上下文中定义的所有变量和函数都会存储在这个变量对象当中,我们无法直接通过代码访问到这个变量对象,但是我们可以通过打断点的方式查看到。
全局上下文会在程序退出前(例如关闭网页或退出浏览器)被销毁,局部上下文会在其代码执行完毕后被销毁
那么这个变量对象长啥样呢?不着急,我们接着往下看
上下文执行栈
定义: 每个函数调用都有自己的上下文,当函数执行时,函数的上下文会被推入到一个上下文执行栈上,在函数执行完毕后,上下文执行栈会弹出该函数的上下文,将控制权返还给之前的执行上下文。
// 一个简单的例子,断点调试调用栈和上下文变量对象
let a_name = "猫力";
var a_sex = "男";
var a = "111";
function a_molly(age) {let a_like = "爱学习";var a_like2 = "爱运动";a_say(a_like);var a = "222";console.log(a);let test = "来啦?";
}
function a_say(a_like) {let code = "敲代码";console.log(a_like);
}
a_molly();
执行如上示例代码,我们在控制台打上断点,来观察执行栈(call stack) 的过程
注意观察左边的call stack(执行栈)
和右边的scope(环境记录)
还有断点位置
通过观察断点调试结果,我们可以得到以下结论:
- 当脚本程序初始化时,会往所有的上下文执行栈底部推入一个全局上下文,也就是
Global
属性 - 每当执行到函数时,会往执行栈里面追加一个 “函数上下文”
- 当函数执行完毕之后,会清除对应的 “函数上下文”
- 每个上下文内部,确定了可以访问的数据
作用域和作用域链
上下文中的代码在执行的时候,会创建变量对象的一个作用域链。这个作用域链决定了各级上下文中的代码在访问变量和函数时的顺序。作用域是包含关系,全局包含局部。
总结:
上下文关联了变量对象决定了函数可以访问哪些数据,而作用域则是决定了数据访问的规则。
简单来说,作用域的访问规则可以总结为:由内向外查找访问,内部可以访问外部而外部无法访问内部,这样的访问方式可以称之为作用域链
函数参数被认为是当前上下文中的变量,因此也跟上下文中的其他变量遵循相同的访问规则。
闭包
现在我们已经简单了解了 “上下文”和“作用域”两个知识点,再来谈闭包就十分友好了
闭包的定义
红宝书: 闭包指的是那些引用了另一个函数作用域中变量的函数
MDN: 一个函数和对其周围状态(lexica environment
,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure
)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript
中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
阮一峰: 闭包就是能够读取其他函数内部变量的函数。只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。
总结一下: 函数的执行,可以触发另一个函数的定义(函数声明,函数表达式),并且支持引用另一个函数作用域中的变量。那么这个函数就是一个闭包
为什么会有闭包?
综上理论我们可以得知,局部作用域可以访问全局作用域,而全局无法访问到局部,两个不相干的局部也无法相互访问,那么,只要思想不滑坡,办法总比困难多,要解决此类问题,我们需要变通一下,结论就是:“闭包”。
闭包就好像是一座桥梁,把多个不相干的作用域串联起来,实现互通。
闭包的原理正是利用了变量环境和作用域链访问规则
闭包的用途
闭包最大的用途有两个
- 可以读取函数体的内部变量
- 让闭包的变量始终保持在内存中
闭包的形式:
把函数视为一等公民,视为一个普通变量
1:返回一个函数
function fun(){var aaa = 111return function(b){return aaa+b}
}
fun()()
经典场景:防抖节流
2:返回一个函数变量
function fun(a){let fn = function(b){return a+b}return fn;
}
fun()()
3:作为全局的闭包函数
var call;
function fun(a){call = function(b){return a+b}
}
fun()
call()
4:作为函数参数传递
function fun1(fn){fn() //这个fn函数就是闭包
}
function fun2(){let str = 'molly'function fun3(){console.log(str)}fun1(fun3)
}
fun2()
5:回调函数
function ajax(data){console.log(data)
}
function sync(){const obj = {name:'molly',a:a}ajax(obj)
}
6:IIFE 立即执行函数
;!(function(){...
});经典场景:
for(var i = 1;i <= 5;i++){(function(j){setTimeout(function timer(){console.log(j)}, 0)})(i)
}
jquery自定义封装插件也是从立即执行函数开始的
闭包的优势:
- 可以访问到两个不相干的作用域变量
- 作为一个沙箱,存储变量
- 代码封装,工具函数
- 函数作为值,进行入参和返回
闭包的劣势:
- 内存泄露
如何规避内存泄露呢?
- 将函数指针指向
null
,纳入垃圾回收范围 - 将闭包执行完毕
function fun(a){return function(b){return a+b}
}
let a = fun()
a=null再或者将闭包也执行完毕a()
这里简单提一嘴内存为什么闭包会导致内存泄露,js的垃圾回收机制大致分为 “标记清理” 和 “计数引用” 两种。当闭包内的变量在另一个函数中有使用时,这个变量则不会被识别为垃圾,而是常驻内存当中不会被清理。导致额外内存消耗
提问?
你知道有哪些巧用闭包的场景或代码么?欢迎评论区留言讨论!
感谢
欢迎关注我的个人公众号前端有猫腻每天给你推送新鲜的优质好文。回复 “福利” 即可获得我精心准备的前端知识大礼包。愿你一路前行,眼里有光!
感兴趣的小伙伴还可以加我微信:猫力molly或前端交流群和众多优秀的前端攻城狮一起交流技术,一起玩耍!
十分钟,快速理解JavaScript中的闭包概念相关推荐
- 解析面试常问题之JavaScript中的闭包概念及应用,顺便普及一下大家口中常说的内存泄漏问题
JavaScript中的闭包是一个面试中经常被考到的问题,大家可能都对这个概念多多少少都有一些模糊的概念或者一点都不了解,那么今天就来给大家讲解一下. 公众号:前端印象 不定时有送书活动,记得关注~ ...
- 三分钟快速理解javascript内存管理
javascript中具有垃圾自动回收机制(Garbage Collection),也就是执行环境会负责管理代码执行过程中使用的内存,在开发过程中就可以不考虑内存的分配,以及无用内存释放的问题.但是触 ...
- 深入理解JavaScript中的闭包
闭包没有想象的那么简单 闭包的概念在JavaScript中占据了十分重要的地位,有不少开发者分不清匿名函数和闭包的概念,把它们混为一谈,我希望借这篇文章能够让大家对闭包有一个清晰的认识. 大家都知道变 ...
- .net label在父容器中占两行显示_十分钟快速了解 JS 中的 offset、scroll、client
经常碰到offset.scroll.client这几个关键字,每次都要各种实验,这里总结一下. 两张图镇楼,随时翻阅 1. offset offset 指偏移,包括这个元素在文档中占用的所有显示宽度, ...
- extjs中滚动条属性_十分钟快速了解 JS 中的 offset、scroll、client
(给前端大全加星标,提升前端技能) 作者:前端下午茶 公号 / SHERlocked93 在下开发中经常碰到 offset.scroll.client 这几个关键字,比如 offsetLeft.of ...
- 如何快速理解JavaScript 中重要语句for循环
一.基本结构:for(起始状态:判断条件:状态改变){ 执行语句: } 执行顺序:for(var i=1;i<3;i++){ alert(i); } 1.判断条件 2.执行语句 3. ...
- 总结 : 十分钟快速理解Java容器
首先看一下Java容器的概念 容器可以管理对象的生命周期.对象与对象之间的依赖关系,您可以使用一个配置文件(通常是XML),在上面定义好对象的名称.如何产生(Prototype 方式或Singleto ...
- [译]Javascript中的闭包(closures)
本文翻译youtube上的up主kudvenkat的javascript tutorial播放单 源地址在此: https://www.youtube.com/watch?v=PMsVM7rjupU& ...
- 深入理解javascript原型和闭包(16)——完结
之前一共用15篇文章,把javascript的原型和闭包. 首先,javascript本来就"不容易学".不是说它有多难,而是学习它的人,往往都是在学会了其他语言之后,又学java ...
最新文章
- mingw编译ffmpeg 错误:Unknown option --enable-memalign-hack
- 美国农业生产与农产品国际贸易 对话国际农民丰收节贸易会
- Linux .bin安装文件制作
- Dubbo 和 HSF 在阿里巴巴的实践:携手走向下一代云原生微服务
- bInitiallyDisabled实现原理
- MySQL5.6免安装配置与“系统找不到指定的文件”错误
- dojo还有人用吗_我的Dojo中有一个Mojo(如何编写Maven插件)
- Java中序列化的好处及意义
- [python 学习] requests 库的使用
- 什么是 Python?我介绍我几年前学习Python的方法和经验
- MOQL--面向流的统计过滤技术
- 计算机设置桌面文件夹,win10电脑怎么更改桌面文件夹路径
- Kafka的Topic删不掉
- 自棱镜事件,隐私保护搜索引擎DuckDuckGo流量增长600%
- 相控阵天线(十一):阵列天线有源驻波分析
- sqldevelop 连接数据库,数据库直接写ip
- Unity优化工具有哪些,特别推荐LuaProfiler
- 淘晶驰串口屏入门(四)进度条、滑块、定时器、单选框、复选框、二维码
- mingw版本下qt与HTML,QT5.10开发(2) 在Windows 10下使用MinGW编译 静态Qt 5.10 release版 详细过程...
- 举个栗子~Tableau 技巧(216):服务器视图中的文字乱码怎么办?