摘要:闭包引起的内存泄漏总结从理论的角度将由于作用域链的特性中所有函数都是闭包但是从应用的角度来说只有当函数以返回值返回或者当函数以参数形式使用或者当函数中自由变量在函数外被引用时才能成为明确意义上的闭包。
文章同步到github
js的闭包概念几乎是任何面试官都会问的问题,最近把闭包这块的概念梳理了一下,记录成以下文章。
什么是闭包我先列出一些官方及经典书籍等书中给出的概念,这些概念虽然表达的不一样,但是都在对闭包做了最正确的定义和翻译,也帮助大家更好的理解闭包,这阅读起来可能比较模糊,大家往后看,本文通过对多个经典书籍中的例子讲解,相信会让大家能很好的理解js中的闭包。文章开始,我会先铺垫一下闭包的概念和为什么要引入闭包的概念,然后结合例子来说明讲解,并讲解如何使用闭包。
百度百科中的定义:闭包包含自由(未绑定到特定对象)变量;这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)。“闭包” 一词来源于以下两者的结合:要执行的代码块(由于自由变量被包含在代码块中,这些自由变量以及它们引用的对象没有被释放)和为自由变量提供绑定的计算环境(作用域) -- 百度百科
《javaScript权威指南》中的概念:函数对象可以通过作用域链互相关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学中成为闭包
《javaScript高级教程》中概念:闭包是指有权访问另一个函数作用域中的变量的函数。
MDN中的概念 个人总结的闭包概念:闭包就是子函数可以有权访问父函数的变量、父函数的父函数的变量、一直到全局变量。归根结底,就是利用js得词法(静态)作用域,即作用域链在函数创建的时候就确定了。
子函数如果不被销毁,整条作用域链上的变量仍然保存在内存中。
为什么引入闭包的概念我引入《深入理解JavaScript系列:闭包(Closures)》文章中的例子来说明,也可以直接去看那篇文章,我结合其他书籍反复读了很多遍此文章才理解清楚。如下:
function testFn() { var localVar = 10; // 自由变量 function innerFn(innerParam) { alert(innerParam + localVar); } return innerFn; } var someFn = testFn(); someFn(20); // 30
一般来说,在函数执行完毕之后,局部变量对象即被销毁,所以innerFn是不可能以返回值形式返回的,innerFn函数作为局部变量应该被销毁才对。
这是当函数以返回值时的问题,另外再看一个当函数以参数形式使用时的问题,还是直接引用《深入理解JavaScript系列》中的例子,也方便大家有兴趣可以直接去阅读那篇文章
var z = 10; function foo() { alert(z); } foo(); // 10 – 使用静态和动态作用域的时候 (function () { var z = 20; foo(); // 10 – 使用静态作用域, 20 – 使用动态作用域 })(); // 将foo作为参数的时候是一样的 (function (funArg) { var z = 30; funArg(); // 10 – 静态作用域, 30 – 动态作用域 })(foo);
当函数foo在不同的函数中调用,z该取哪个上下文中的值呢,这就又是一个问题,所以就引入了闭包的概念,也可以理解为定义了一种规则。
理解闭包 函数以返回值返回看一个《javsScript权威指南》中的一个例子,我稍微做一下修改如下:
var scope = "global scope"; function checkScope() { var scope = "local scope"; return function() { console.log(scope); } } var result = checkScope(); result(); // local scope checkScope变量对象中的scope,非全局变量scope
分析:
即使匿名函数是在checkScope函数外调用,也没有使用全局变量scope,即是利用了js的静态作用域,当匿名函数初始化时,就创建了自己的作用域链(作用域链的概念这里不做解释,可以参考我的另一篇文章js中的执行栈、执行环境(上下文)、作用域、作用域链、活动对象、变量对象的概念总结,其实当把作用域链理解好了之后,闭包也就理解了), 此匿名函数的作用域链包括checkScope的活动对象和全局变量对象, 当checkScope函数执行完毕后,checkScope的活动对象并不会被销毁,因为匿名函数的作用域链还在引用checkScope的活动对象,也就是checkScope的执行环境被销毁,但是其活动对象没有被销毁,留存在堆内存中,直到匿名函数销毁后,checkScope的活动对象才会销毁,解除对匿名函数的引用将其设置为null即可,垃圾回收将会将其清除,另外当外部对checkScope的自由变量存在引用的时候,其活动对象也不会被销毁
result = null; //解除对匿名函数的引用
注释:
自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量
补充:
引用一下《javsScript权威指南》中的补充,帮助大家进一步理解
当函数以参数形式使用时一般用于利用闭包特性解决实际问题,比如浏览器中内置的方法等,下面我直接引用《深入理解JavaScript系列:闭包(Closures)》中关于闭包实战部分的例子如下:
sort在sort的内置方法中,函数以参数形式传入回调函数,在sort的实现中调用:
[1, 2, 3].sort(function (a, b) { ... // 排序条件 });map
和sort的实现一样
[1, 2, 3].map(function (element) { return element * 2; }); // [2, 4, 6]另外利用自执行匿名函数创建的闭包
var foo = {}; // 初始化 (function (object) { var x = 10; object.getX = function() { return x; }; })(foo); alert(foo.getX()); // 获得闭包 "x" – 10利用闭包实现私有属性的存取
先来看一个例子
var fnBox = []; function foo() { for(var i = 0; i < 3; i++) { fnBox[i] = function() { return i; } } } foo(); var fn0 = fnBox[0]; var fn1 = fnBox[1]; var fn2 = fnBox[2]; console.log(fn0()); // 3 console.log(fn1()); // 3 console.log(fn2()); // 3
用伪代码来说明如下:
fn0.[[scope]]= { // 其他变量对象,一直到全局变量对象 父级上下文中的活动对象AO: [data: [...], i: 3] } fn1.[[scope]]= { // 其他变量对象,一直到全局变量对象 父级上下文中的活动对象AO: [data: [...], i: 3] } fn2.[[scope]]= { // 其他变量对象,一直到全局变量对象 父级上下文中的活动对象AO: [data: [...], i: 3], }
分析:
这是因为fn0、fn1、fn2的作用域链共享foo的活动对象, 而且js没有块级作用域,当函数foo执行完毕的时候foo的活动对象中i的值已经变为3,当fn0、fn1、fn2执行的时候,其最顶层的作用域没有i变量,就沿着作用域链查找foo的活动对象中的i,所以i都为3。
但是这种结果往往不是我们想要的,这时就可以利用认为创建一个闭包来解决这个问题,如下:
var fnBox = []; function foo() { for(var i = 0; i < 3; i++) { fnBox[i] = (function(num) { return function() { return num; } })(i); } } foo(); var fn0 = fnBox[0]; var fn1 = fnBox[1]; var fn2 = fnBox[2]; console.log(fn0()); // 0 console.log(fn1()); // 1 console.log(fn2()); // 2
用伪代码来说明如下:
fn0.[[scope]]= { // 其他变量对象,一直到全局变量对象 父级上下文中的活动对象AO: [data: [...], i: 3], fn0本身的活动对象AO: {num: 0} } fn1.[[scope]]= { // 其他变量对象,一直到全局变量对象 父级上下文中的活动对象AO: [data: [...], i: 3], fn1本身的活动对象AO: {num: 1} } fn2.[[scope]]= { // 其他变量对象,一直到全局变量对象 父级上下文中的活动对象AO: [data: [...], i: 3], fn2本身的活动对象AO: {num: 2} }
分析:
当使用自执行匿名函数创建闭包, 传入i的值赋值给num,由于作用域链是在函数初始化时创建的,所以当每次循环时,函数fn10、fn1、fn2的作用域链中保存了当次循环是num的值, 当fn10、fn1、fn2调用时,是按照本身的作用域链进行查找。
闭包引起的内存泄漏 总结从理论的角度将,由于js作用域链的特性,js中所有函数都是闭包,但是从应用的角度来说,只有当函数以返回值返回、或者当函数以参数形式使用、或者当函数中自由变量在函数外被引用时,才能成为明确意义上的闭包。
最后,我想表达的式,本篇大量引用和罗列了经典的犀牛书《javaScript权威指南》、红宝书《javaScript高级教程》、以及《深入理解JavaScript系列:闭包(Closures)》系列文章中的概念和例子,不为能形成自己的独特见解,只为了能把闭包清晰的讲解出来。笔者是个小菜鸟,能力实在有限,也在学习中,希望大家多多指点,如发现错误,请多多指正。也希望看过此文的朋友能对闭包多一些理解,那我写这篇文章也就值得了。下次面试时就可以告诉面试官什么是闭包了。谢谢。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/89142.html
摘要:当在中调用匿名函数时,它们用的都是同一个闭包,而且在这个闭包中使用了和的当前值的值为因为循环已经结束,的值为。最好将闭包当作是一个函数的入口创建的,而局部变量是被添加进这个闭包的。 闭包不是魔法 这篇文章使用一些简单的代码例子来解释JavaScript闭包的概念,即使新手也可以轻松参透闭包的含义。 其实只要理解了核心概念,闭包并不是那么的难于理解。但是,网上充斥了太多学术性的文章,对于...
摘要:一言以蔽之,闭包,你就得掌握。当函数记住并访问所在的词法作用域,闭包就产生了。所以闭包才会得以实现。从技术上讲,这就是闭包。执行后,他的内部作用域并不会消失,函数依然保持有作用域的闭包。 网上总结闭包的文章已经烂大街了,不敢说笔者这篇文章多么多么xxx,只是个人理解总结。各位看官瞅瞅就好,大神还希望多多指正。此篇文章总结与《JavaScript忍者秘籍》 《你不知道的JavaScri...
摘要:权威指南第版中闭包的定义函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中成为闭包。循环中的闭包使用闭包时一种常见的错误情况是循环中的闭包,很多初学者都遇到了这个问题。 闭包简介 闭包是JavaScript的重要特性,那么什么是闭包? 《JavaScript高级程序设计(第3版)》中闭包的定义: 闭包就是指有权访问另一个函数中的变...
摘要:注意由于闭包会额外的附带函数的作用域内部匿名函数携带外部函数的作用域,因此,闭包会比其它函数多占用些内存空间,过度的使用可能会导致内存占用的增加。 作用域和作用域链是javascript中非常重要的特性,对于他们的理解直接关系到对于整个javascript体系的理解,而闭包又是对作用域的延伸,也是在实际开发中经常使用的一个特性,实际上,不仅仅是javascript,在很多语言中都...
摘要:也许最好的理解是闭包总是在进入某个函数的时候被创建,而局部变量是被加入到这个闭包中。在函数内部的函数的内部声明函数是可以的可以获得不止一个层级的闭包。 前言 总括 :这篇文章使用有效的javascript代码向程序员们解释了闭包,大牛和功能型程序员请自行忽略。 译者 :文章写在2006年,可直到翻译的21小时之前作者还在完善这篇文章,在Stackoverflow的How do Java...
阅读 635·2023-04-25 18:59
阅读 1187·2021-09-22 16:00
阅读 1847·2021-09-22 15:42
阅读 3576·2021-09-22 15:27
阅读 1230·2019-08-30 15:54
阅读 1068·2019-08-30 11:16
阅读 2420·2019-08-29 16:24
阅读 782·2019-08-29 12:14