你不知道的闭包原理【三个栗子彻底理解】
想要理解闭包之前,就必须理解函数的创建过程、活动变量AO、作用域链。我曾写过相关的文章
网上相关对闭包的定义:
- MDN:函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在 JavaScript 中,每当函数被创建,就会在函数生成时生成闭包。
- 你不知道的JavaScript:是指有权访问另外一个函数作用域中的变量的函数。创建闭包的常见方式就是在一个函数内部创建另外一个函数。
- Javascript核心技术开发解密:闭包是一种特殊对象,由两部分组成:执行上下文A + 该执行上下文创建的函数B
我对这些定义的理解:
- 《MDN》的解释更加接近原理,
- 《你不知道的Javascript》的解释更多讲的是现象,
- 《Javascript核心技术开发解密》的解释更能说明闭包的真实存在:闭包是一种特殊对象。
---------------------------------人工分割线------------------------------
下面我们通过几个栗子来一步步讲解闭包的原理:
栗子一:
function makeFunc() { var name = "Mozilla"; function displayName() { console.log(name); } return displayName; } var myFunc = makeFunc(); myFunc();
打印结果:Mozilla
- 混淆点1当执行到var myFunc = makeFunc();makeFunc函数在执行完之后里面的name不是应该被垃圾回收机制给处理掉了吗? 答案:其实通过垃圾回收机制大概也知道name属性不可能被回收,因为还有myFunc函数持有name的引用。
- 混淆点2:与普通函数有什么不同? 答案:如果没产生闭包,那么函数中的临时变量都被回收了。
- 混淆点3:name属性如果不回收,那么存放在哪里? 答案:这个问题后面会讲到 【myFunc在google浏览器的内部属性,生成了一个闭包对象makeFunc】
先来思考一个问题:我们知道当执行了makeFunc函数后会产生对应的变量对象{变量对象保存了函数中的临时变量},那么上图中的闭包对象makeFunc是否就等于makeFunc函数所产生的变量对象?
带着这个问题看下一个栗子--
栗子二:
function makeFunc() { var name = "Mozilla"; var age = 12; function displayName() { console.log(name); } return displayName; } var myFunc = makeFunc(); myFunc();
思考: 此时的闭包对象makeFunc是否带有age属性? 答案:没有age属性 说明:闭包对象不等于makeFunc的变量对象。闭包对象仅保存跨域的属性。
延申到另一个常见问题:如何清除闭包? 我们知道闭包对象存在于myFunc函数内,所以一句:myFunc = null。使得闭包对象没有引用持有那么等待他的就是垃圾回收。
再延申到另一个常见问题:MDN:在 JavaScript 中,每当函数被创建,就会在函数生成时生成闭包。那么岂不是内存很快就泄露了? 实际上你的闭包大多数都是没有引用持有,很快就会被回收掉的。并且JS对闭包也有相关的优化处理。
然而:这个时候,我们是否明白这个闭包对象与作用域链的关系是什么?
栗子三:
var a = 20; function test () { var b = a + 10; function innerTest () { debugger var c = 10; return b + c; } innerTest(); } test();
??? 当执行到debugger时,此时innerTest函数的作用链是什么呢?闭包对象是否产生? :::此时innerTest函数的作用链: 闭包对象已经产生,并且闭包对象作为作用域链中的对象。
你是否记得很多书上都说作用域链是一条又每个函数的VO对象组成的链条。但这里看到的却不是VO对象,而是闭包对象。
我的看法是:如果单纯从函数的作用域来看:作用域链是一条又每个函数的VO对象组成的链条。这个说法很正确,这是真正能够以此帮助我们判断访问作用域边界的依据。但是在程序实际的运行中,经过词法编译的阶段,JS引擎已经通过代码把各个实际上闭包产生的变量已经提炼出来。而不是直接就把VO对象放在作用域链。这也有利于提高访问速度。
总结:
function makeFunc() { var name = "Mozilla"; function displayName() { console.log(name); } return displayName; } var myFunc = makeFunc(); myFunc();
执行过程有关闭包的变化: 当调用makeFunc()函数进入函数创建阶段时发现displayName函数含有name的跨函数变量,所以在对displayName函数进行提升的时候就已经给displayName函数初始化了闭包对象【makeFunc闭包】。所以当执行myFunc()函数的时候,从当前myFunc()的VO对象找不到的话就会从作用域链中的上一级【makeFunc闭包】对象中找。 来个图清晰一点: --- 以上便是对闭包最新的理解。不对的望多多指出。
- js的闭包原理理解
- 深刻理解js执行原理和闭包
- 简单理解闭包的原理
- 两个函数彻底理解Lua中的闭包
- Android 阅读源码,让你彻底理解AsyncTask运行原理
- 彻底理解MapReduce shuffle过程原理
- 彻底理解MapReduce shuffle过程原理
- 闭包原理的理解
- 深入理解JavaScript内部原理(6): 闭包
- 理解js闭包原理
- 彻底理解js中的闭包
- 我对EJB3调用原理的彻底理解
- 彻底理解handler的实现原理
- js 闭包原理理解
- JavaScript系列_彻底理解闭包,如何彻底理解闭包,javascript中的闭包如何理解
- 深入理解函数内部原理(5)——闭包
- 对于闭包的理解和prototype的应用原理
- 深入理解Lua的闭包一:概念、应用和实现原理
- 彻底理解JavaScript中的闭包
- 【JS基础】从零开始带你理解JavaScript闭包--我是如何彻底搞明白闭包的