重学JavaScript(四)深入理解闭包
前言
在很久很久以前,在古乾断,一个叫做嘉瓦斯克瑞普特的地方,出了一条恶龙,它口吐紫光,毁天灭地,伸出的触手能让最强壮士兵消失。在这个地方自古以来流传着这样一个预言,当作用域之剑显世之时,就是巨龙毁灭之日。
切入正题,在学习了作用域之后我们可以进一步了解闭包这个概念,在刚刚接触JavaScript时,就一直有听说,闭包很重要但是很难理解。写这篇文章的初衷便是让大家深入理解闭包的概念,因此接下来我会慢慢讲述何为闭包,闭包又有什么作用,希望大家看完之后有所收获,话不多说,进入今天的屠龙之旅。
闭包的概念
在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。
👆上面的概念取自于维基百科,上述概念中其实蕴含了两种概念,我个人倾向于第一种,即引用了自由变量的函数为闭包
闭包的定义在js学习中一直是非常模糊的,可以说每个人对于闭包的理解是不同的,我认为闭包的是作用域的产物,姑且认为闭包是指一个可以访问另一个函数作用域中变量的函数。
function foo (){ function bar (){ console.log(aaa) } var aaa =123; return bar } var demo = foo() demo() // 123 复制代码
在上述的例子,输出的是123,但是根据我们对作用域的理解,aaa属于函数内部作用域中的变量,而demo的运行环境处于全局作用域,按理说外部环境并不能访问内部函数的变量。很多小伙伴都应该明白,这是运用了闭包,那么接下来我们需要了解为什么会产生闭包而闭包又是何时产生的。
进一步了解闭包
根据前面作用域的学习,我们可以了解上述例子的执行过程,这里我简单将其列出:
1.foo函数定义,形成foo函数的作用域链,作用域链的第一位为全局执行上下文
2.foo函数执行,作用域的第一位,改为foo函数的局部执行上下文,第二位则是之前的全局执行上下文。在foo函数执行的同时,我们迎来了bar函数的定义。形成了bar函数的作用域链,由于bar在函数foo内被定义,它可以访问foo的作用域,使以bar的作用域链上天生就有foo的作用域链,所以下面我将bar的作用域链指向foo的作用域链。同时bar函数被return赋值给了demo这个全局变量。
3.bar函数执行,bar函数作用域链第一位改为自身局部执行上下文,执行 console.log(aaa) 按照作用域链查找,输出123
foo函数执行完毕之后,js自带的垃圾回收机制,会将其标记,并在下次回收时,释放foo函数所占用的内存空间,但是好巧不巧,你需要释放的作用域链,已经作为bar的作用域链的一部分被带到外部了,那指定不能让你销毁呀。
这时闭包就产生了,在我的理解中,bar被foo返回时就产生了闭包,此时内部函数和外部函数同处于一个作用域下,bar的作用域链上绑定了foo的作用域链,导致foo函数作用域链不能释放。不知道大家有没有理解呢?
闭包会产生内存泄漏吗?
不再用到的内存,没有及时释放,就叫做内存泄漏。更进一步的说当一个变量我们已经无法通过js来引用了,但是垃圾回收器却认为这个对象还在被引用,因此在回收的时候不会释放它。导致了分配的这块内存永远也无法被释放出来。
那么闭包会产生内存泄漏吗?这是经常被讨论的问题,我个人认为不会,因为我理解中的闭包时在内部函数被返回到外部时,闭包产生了。而内存泄漏出现在bar函数被执行时,因此我认为aaa变量不被释放,并不是由闭包导致的,你可以通过将demo变量使用完后,指向新的地址,来手动释放这一部分内存。由于闭包并没有准确的定义,上述仅是我个人理解,当然还有很多人认为闭包是foo函数并且导致了内存泄漏,欢迎大家在下面讨论。
闭包的运用
私有化变量
原生 JavaScript 并没有支持私有变量,但我们可以使用闭包来模拟私有变量或者方法。私有变量不仅仅有利于限制对代码的访问,还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共部分。
(function (ROOT) { var jQuery = function(selector) { return new jQuery.fn.init(selector) } jQuery.fn = jQuery.prototype = { constructor: jQuery, init: function(selector) { var elem, selector; elem = document.querySelector(selector); this[0] = elem; return this; } } .... ROOT.jQuery = ROOT.$ = jQuery; })(window); 复制代码
jQuery就是私有化变量的典型应用
循环中的闭包
在 ES6 引入块级作用域之前,在循环中有一个常见的创建问题。我们可以运用闭包解决下面这个异步问题,这里就贴一下代码。
for (var i = 1; i <= 5; i++) { setTimeout( function timer() { console.log(i); }, 1000 ); } for (var i = 1; i <= 5; i++) { (function(i){ setTimeout( function () { console.log(i); }, 1000 ); })(i); } for (let i = 1; i <= 5; i++) { setTimeout( function timer() { console.log(i); }, 1000 ); } 复制代码
写在最后
经过这篇文章的梳理,自己对闭包对理解又有些加深,希望这篇文章能给读者带来新的思考,解决了闭包这头恶龙后,我们将继续Javascript的探索之旅,下一站我们将了解什么是原型,去体会JavaScript的魅力。
作者:TimCope
链接:https://juejin.im/post/5de4cdd16fb9a071b3011ccc
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
- javascript深入理解闭包
- 深入理解javascript原型和闭包(2)——函数和对象的关系
- 深入理解javascript原型和闭包(2)——函数和对象的关系
- javascript深入理解js闭包
- 深入理解javascript原型和闭包(17)——补this
- javascript深入理解js闭包
- 深入理解javascript原型和闭包(3)——prototype原型
- 深入理解javascript原型和闭包(7)——原型的灵活性
- javascript深入理解js闭包
- javascript深入理解js闭包
- 深入理解JavaScript系列(16) 闭包(Closures)
- 深入理解javascript原型和闭包(10)——this
- 深入理解javascript原型和闭包(2)——函数和对象的关系
- 深入理解javascript原型和闭包(10)——this
- 深入理解javascript原型和闭包(3)——prototype原型
- 深入理解javascript原型和闭包系列 深入理解javascript原型和闭包(8)——简述【执行上下文】上
- javascript深入理解js闭包
- 深入理解javascript原型和闭包(7)——原型的灵活性
- javascript深入理解js闭包
- 深入理解javascript原型和闭包(1)——一切都是对象