什么是闭包

BitAurora
• 阅读 1162

这里是修真院前端小课堂,每篇分享文从

【背景介绍】【知识剖析】【常见问题】【解决方案】【编码实战】【扩展思考】【更多讨论】【参考文献】

八个方面深度解析前端知识/技能,本篇分享的是:

【 什么是闭包】

今天给大家介绍一下什么是闭包。

1.背景介绍
闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。

JS函数的执行依赖于变量作用域, 函数对象的内部状态包含函数自身的逻辑,还必须引用当前的作用域链。 函数对象可以相互关联起来,函数体内部的变量可以保存在函数作用域内, 具有这种特性的函数称为闭包。

2.知识剖析
作用域和作用域链

在说到闭包的同时,我们不得不说一下作用域和作用域链,js的执行过程分为两个阶段,第一阶段是代码编译阶段,第二阶段是代码执行阶段。在编译阶段,我们的代码将会被翻译成可执行的代码、并且在此阶段我们的作用域规则将会被确定。在第二阶段,主要任务是执行可执行代码,重点注意下,我们的执行上下文将会在此时生成

执行上下文

执行上下文分为两个阶段:创建阶段:在进入函数的时候创建执行上下文,这个创建的操作中,包括了生成变量对象,建立作用域链、确定this指向。

执行阶段:包括变量赋值、函数引用、执行其他代码。经历过这两个阶段之后,它就会等待被js的垃圾回收机制回收

它在创建执行上下文的时候,先生成了变量对象用于保存我们的变量,在这个变量对象中,以function名称为属性名的变量将会储存一个指向function的指针,如果后面存在着同名的函数,那么前面的将会被覆盖,而以var形式赋值的变量将会以这个变量名为属性名储存但是值是undefined,并且,如果具有同名的变量名,会直接跳过,不会被覆盖

它在执行阶段,这个时候才会为var的进行赋值操作。并且执行引用的函数。

作用域链

作用域链是由当前环境和上层环境的一系变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问

在上面的例子中,全局,函数test,函数innerTest的执行上下文先后创建。我们设定他们的变量对象分别为VO(global),VO(test), VO(innerTest)。而innerTest的作用域链,则同时包含了这三个变量对象,所以innerTest的执行上下文可如下表示。

是的,你没有看错,我们可以直接用一个数组来表示作用域链,数组的第一项scopeChain[0]为作用域链的最前端,而数组的最后一项,为作用域链的最末端,所有的最末端都为全局变量对象。

很多人会误解为当前作用域与上层作用域为包含关系,但其实并不是。以最前端为起点,最末端为终点的单方向通道我认为是更加贴切的形容。如图。

所以作用域链是由一系列变量对象组成的,我们可以在这个单向通道中查询变量对象的标识符,这样就能够访问到上一层作用域中的变量了

垃圾回收机制

在js中存在着特殊的内存回收机制,就是当一个值在内存中失去引用时,垃圾回收机制会根据它的方法找到它,将它回收,释放内存,我们上面说过,在该函数的执行上下文声明周期结束以后,那么该段函数的执行上下文就失去了引用,其所占用的内存空间就会很快的被垃圾回收机制收回,闭包的存在会阻止这一过程

所以现在,我们可以总结一下闭包的作用

闭包可以用在许多地方。它的最大用处有两个:

1.可以访问该函数作用域以外的变量对象

2.让这些变量的值始终保存在内存中

读取函数内部的变量的例子:

/使用闭包读取函数内部的变量/

function f1(){

n = 999;

function f2(){

alert(n);

}

return f2;

}

var result = f1();

result(); //999

在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是Javascript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗

变量的值始终保存在内存的例子:

/使用闭包让函数内部的变量储存在内存中/

function f1(){

var n = 999;

nAdd = function(){

n+=1;

};

function f2(){

alert(n);

}

return f2;

}

var result = f1();

result();//999

nAdd();

result();//1000

在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。因为f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。

3.常见问题
window.onload = function(){

var el = document.getElementById("id");

el.onclick = function(){

alert(el.id);

}

}

这段代码为什么会造成内存泄露?

4.解决方案
内存泄漏的原因:执行这段代码的时候,将匿名函数对象赋值给el的onclick属性;然后匿名函数内部又引用了el对象,存在循环引用,所以不能被垃圾回收机制回收;

修改后:

window.onload = function(){

var el = document.getElementById("id");

var id = el.id; //解除循环引用

el.onclick = function(){

alert(id);

}

el = null; // 将闭包引用的外部函数中活动对象清除

}

5.编码实战
点击按钮会弹出相应的数字0、1、2、3、4。

function init({

var pAry = document.getElementsByTagName("button");

for( var i=0; i< pAry.length; i++ ) {

(function(arg){

pAry[i].onclick = function() {

alert(arg);

};

})(i);//调用时参数

}

}

思路:加一层闭包,i以局部变量形式传递给内存函数,在js任务4中的杀人游戏选中的身份死亡有用到。

6.扩展思考
闭包还可以有什么作用?

封装变量

什么是闭包

7.参考文献
参考一:阮一峰的网络日志:学习Javascript闭包

参考二:慕课网

参考三:详细图解作用域链与闭包

今天的分享就到这里啦,欢迎大家点赞、转发、留言、拍砖~

8.PPT视频链接
PPT:https://ptteng.github.io/PPT/...

视频: https://pan.baidu.com/s/1jHI4JFw 

点赞
收藏
评论区
推荐文章
Souleigh ✨ Souleigh ✨
4年前
JS - 从执行上下文的角度来理解闭包
今天看到一篇关于闭包的文章,里面有这样一句话“就我而言对于闭包的理解仅止步于一些概念,看到相关代码知道这是个闭包,但闭包能解决哪些问题场景我了解的并不多”,这说的不就是我么,每每在面试中被问及什么是闭包,大部分情况下得到的答复是(至少我以前是)A函数嵌套B函数,B函数使用了A函数的内部变量,且A函数返回B函数,这就是闭包。而往往面试官想要听到的并不是这样的
菜园前端 菜园前端
2年前
一篇文章教会你什么是闭包
原文链接:什么是闭包?闭包的概念并不复杂,但是它的定义比较绕(就像平时经常用到它,却又说不出来是什么)。可以在一个作用域中调用函数的内部函数并访问到该函数中的作用域的成员,这就是闭包。给一个建议,网上闭包的概念可以搜出来一大堆,但是你真的了解它吗?你有去调
Jacquelyn38 Jacquelyn38
4年前
你不可不知的JS面试题(第三期)
1、什么是闭包?如图所示,闭包就是一个定义在函数内部的函数,其作用是将函数内部和函数外部连接起来。大家知道,作用域的问题,就是在函数内部定义的变量称为局部变量,外部取不到值。下面我们通过代码来更加详细地看一下:function A()       let x  1;       return function B()           c
Bill78 Bill78
5年前
Python 中的闭包
闭包定义:如果在一个内部函数里,对在外部作用于(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包Python中的闭包原文出处:田小计划(http://www.cnblogs.com/wilber2013/p/4658894.html)闭包(closure)是函数式编程的重要的语法结构
Stella981 Stella981
4年前
JavaScript易错知识点整理
前言本文是我学习JavaScript过程中收集与整理的一些易错知识点,将分别从变量作用域,类型比较,this指向,函数参数,闭包问题及对象拷贝与赋值这6个方面进行由浅入深的介绍和讲解,其中也涉及了一些ES6的知识点。JavaScript知识点1.变量作用域vara1;functio
Easter79 Easter79
4年前
Swift讲解专题八——闭包
Swift讲解专题八——闭包一、引言      Swift中的闭包是有一定功能的代码块,这十分类似于ObjectiveC中的block语法。Swift中的闭包语法风格十分简洁,其作用和函数的作用相似。二、从一个系统函数看闭包      Swift标准函数库中提供了一个sort排序函数,对
Stella981 Stella981
4年前
GROOVY预览
1.Groovy中的闭包Java的一些不足可以通过使用groovy的闭包很好的解决,通过下面这个例子来看看使用闭包的优势:在Java中遍历一个集合的方法是使用迭代,就像下面这样:defacoll"Groovy","Java","Ruby"for(Iteratoriteracoll.iterator()
可莉 可莉
4年前
19 年学好前端的6点建议
1\.夯实基础要成为一名年薪30W的前端工程师,基础一定要掌握牢固,基础知识一问三不知,岂不是要贻笑大方。css,js基础知识一定要掌握得很熟练,你能使用css实现斑马条纹背景,毛玻璃效果吗?能给图片实现滤镜效果,能实现所有自适应布局效果吗?原型,原型链,闭包是实现设计模式的必备知识,你真的弄懂了吗?闭包导致内存泄漏的原因
Stella981 Stella981
4年前
JavaScript函数——闭包
闭包概念只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁例子functionouter(){varlocalVal30;returnlocalVal;}
Wesley13 Wesley13
4年前
go 学习笔记之10 分钟简要理解 go 语言闭包技术
闭包是主流编程语言中的一种通用技术,常常和函数式编程进行强强联合,本文主要是介绍Go语言中什么是闭包以及怎么理解闭包.如果读者对于Go语言的闭包还不是特别清楚的话,可以参考上一篇文章go学习笔记之仅仅需要一个示例就能讲清楚什么闭包(https://www.oschina.net/ac
Wesley13 Wesley13
4年前
#建模大赛预备#认真学习装饰器
!illustration(https://static.oschina.net/uploads/img/201612/17015549_rIYh.jpg"伪物语")1.什么是闭包装饰器的一个基本原理就是闭包,函数嵌套:deffunc1(x):returnxdeffunc2(y):