我所了解的闭包

大家好,我是IT修真院成都分院第07期学员,一枚正直善良的web程序员。

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.更多讨论

闭包中的this指向

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容

  • 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。 一、变量...
    zock阅读 1,075评论 2 6
  • ● 闭包基础 ● 闭包作用 ● 闭包经典例子 ● 闭包应用 ● 闭包缺点 ● 参考资料 1、闭包基础 作用域和作...
    lzyuan阅读 929评论 0 0
  • 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。 一、变量...
    zouCode阅读 1,271评论 0 13
  • 字符串 1.字符串当中需要注意的问题: 单引号和双引号均可以用来标记String,只要引号前后的类型规格一致就可以...
    我从不曾离去阅读 276评论 2 2
  • 如何发现手帐这坑 一次机缘下,我无意中下载了简书这个app,刚开始被我冷落了好几天,还差点被我删了,后来点开后看...
    cryolite阅读 343评论 0 2