深入剖析 JavaScript 闭包

简介: 深入探讨JavaScript闭包,了解其定义、特性、优缺点及作用。闭包是函数与其引用环境的组合,允许内层函数访问外层作用域,常驻内存可能导致内存泄露。优点包括创建私有变量,缺点则涉及内存使用。闭包在变量搜索中遵循从内到外的规则,并影响变量的作用域和生存周期。理解闭包有助于优化代码并避免性能问题。

🌞 深入剖析 JavaScript 闭包


💎导读目录

什么是闭包 闭包的特性 闭包的优缺点

闭包的作用 闭包的注意点

💎什么是闭包?

一个函数和对其周围状态的引用捆绑在一起,这样的组合就是闭包.

通俗的说: 一个内层函数可以访问外层函数的作用域 就叫 闭包

在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。

闭包的形成与变量的作用域以及变量的生存周期密切相关。

💎闭包的特性

函数嵌套函数

函数内部可以引用外部的参数和变量 参数和变量不会被垃圾回收机制回收

💎闭包的优缺点

优点:

可以设计私有的方法和变量

缺点

常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。

一般函数执行完毕后,局部活动对象就被销毁,内存中仅仅保存全局作用域。

💎关于 变量

变量的作用域

变量的作用域: 变量的有效范围。

在实际开发中,我们经常遇到的是 函数中声明的变量作用域。

var a = '闭包'; function getValue(){     var a = '函数局部作用域'     console.log(a) } getValue()  //函数局部作用域

复制

当在全局声明了一个同名变量,在函数内部也声明了一个同名变量,函数优先访问函数作用域中的变量。

函数作用域

函数作用域: 在函数内部可以访问到函数外部变量,而在函数外部的变量不可以访问函数内部的变量。

为什么呢?

因为当在函数中搜索一个变量的时候,如果函数内部没有这个变量的声明,那么它会随着代码的执行环境创建的作用域往外层逐层搜索,直到搜索到全局变量为止。

变量的搜索是从内到外搜索的。

function getData() {     var str = "闭包练习";     var fun = function(){         var innerStr = '内部变量'     }     console.log(innerStr)       //innerStr is not defined 函数外层是访问不到 函数内层变量的 } getData()

复制

变量的生存周期

对于 全局变量,它的生存周期是永久的的,除非主动销毁变量。

而对于 函数局部变量 ,当函数执行完毕,局部变量也就销毁了。

栗子 1

<!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <meta http-equiv="X-UA-Compatible" content="IE=edge">     <meta name="viewport" content="width=device-width, initial-scale=1.0">     <title>Document</title> </head> <body>     <div>1</div>     <div>2</div>     <div>3</div>     <div>4</div>     <script>         var nodes = document.getElementsByTagName('div')         for (var i = 0; i < nodes.length; i++) {                 nodes[i].onclick = function () {                     alert(i)                 }         }     </script> </body> </html>

复制

给每个 div 增加点击事件,当点击 div 时,弹出它对应的索引值。

现在无论点击哪个 div ,它 弹出的 都是 4 。

为什么呢?

因为 div 点击事件 是被 异步触发的,当事件被触发的时候,循环已经执行完,此时的 i 的 变量值 为 4。

如何解决 点击每个div 弹出对应的i 值呢 ?

可以借用 闭包, 把每次循环的 i 保存起来,当执行点击事件时,它会从内到外 搜索变量的作用域,它会优先搜索到 闭包环境环境的 i

# 闭包解决办法    <script>         var nodes = document.getElementsByTagName('div')         for (var i = 0; i < nodes.length; i++) {             (function(i) {                 nodes[i].onclick = function () {                     alert(i)                 }             })(i)         }  </script>

复制

栗子 2

var num = 1; function getValue(){     var num = 0;     return function(){         num++         console.log(num)     } } var s = getValue() s() s() // 1 2

复制

按常理思路来: 函数执行完毕,num = 1 销毁,变为初始值 num = 0 ,变量在函数中作用域从内到外逐层搜索。

前面也说到了,当函数执行完,局部变量也跟着销毁了,那为什么会 输出 2 呢 ?

这里 涉及到 垃圾回收机制引用计数问题 <a href="https://blog.csdn.net/zhouziyu2011/article/details/61201613">[关于垃圾回收] </a>  https://blog.csdn.net/zhouziyu2011/article/details/61201613简述:当声明了一个变量并将一个引用类型值赋给该变量时,则该值的引用次数就是1;如果同一个值又被赋给另一个变量,则该值的引用次数加1;如果包含对该值引用的变量又取得了另外一个值,则该值的引用次数减1。当该值的引用次数变为0时,则可以回收其占用的内存空间。当垃圾回收器下一次运行时,就会释放那些引用次数为0的值所占用的内存。

解答

第一次执行 s() 时,num = 1第二次 执行 s() 时, 由于 引用的时第一次 s () 的变量num=1,num 没有被销毁,固然在 num = 1 的基础上 再 加 1 。

undefined

注意

如果没有使用同样引用的话,那么多次调用,都是同样的值,因为没有记录引用值。

函数在执行完毕,num = 1 被销毁掉了,初始为 0

var num = 1; function getValue(){     var num = 0;     return function(){         num++         console.log(num)     } } getValue()() getValue()() // 0 0

复制

💎闭包的作用

闭包的注意作用为这两项:

可以读取函数内部的变量可以变量的值始终保持在内存中

栗子

function f2(){     let num = 0;     addNum = function(){         num++     }     function f3(){         console.log(num)     }     return f3 } var a = f2() a() addNum() a() // 0  1

复制

结果为 0  1

函数在执行完毕,局部变量也跟着销毁, 结果 不应该是  0  0 吗 ?

其实a() 相当于 是 f3() 的闭包函数,它被执行了两次。

第一次 执行 a() 时, 结果为 0 , 很好理解。 第二次   执行的 f2() 函数内部的 addNum 函数,发现没这个匿名函数赋值给一个变量,而且这个变量没加  var / let , 那么它此时的作用域为 全局 ,保存在内存当中。执行addNum 时它访问的 f2() 函数内部的局部变量 num , 此时,addNum 的存在依赖于 f2,因此f2 也在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。 第三此  执行 a() 时, 因为num 已存在内存中,而至值为1

最终输出结果: 0 , 1

💎闭包注意

由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。


相关文章
|
2月前
|
自然语言处理 JavaScript 前端开发
深入理解JavaScript中的闭包:原理与实战
【10月更文挑战第12天】深入理解JavaScript中的闭包:原理与实战
|
1月前
|
JavaScript 前端开发
js 闭包的优点和缺点
【10月更文挑战第27天】JavaScript闭包是一把双刃剑,在合理使用的情况下,它可以带来很多好处,如实现数据封装、记忆功能和模块化等;但如果不注意其缺点,如内存泄漏、变量共享和性能开销等问题,可能会导致代码出现难以调试的错误和性能问题。因此,在使用闭包时,需要谨慎权衡其优缺点,根据具体的应用场景合理地运用闭包。
114 58
|
1月前
|
缓存 JavaScript 前端开发
js 闭包
【10月更文挑战第27天】JavaScript闭包是一种强大的特性,它可以用于实现数据隐藏、记忆和缓存等功能,但在使用时也需要注意内存泄漏和变量共享等问题,以确保代码的质量和性能。
38 7
|
1月前
|
自然语言处理 JavaScript 前端开发
JavaScript闭包:解锁编程潜能,释放你的创造力
【10月更文挑战第25天】本文深入探讨了JavaScript中的闭包,包括其基本概念、创建方法和实践应用。闭包允许函数访问其定义时的作用域链,常用于数据封装、函数柯里化和模块化编程。文章还提供了闭包的最佳实践,帮助读者更好地理解和使用这一强大特性。
21 2
|
28天前
|
存储 缓存 自然语言处理
掌握JavaScript闭包,提升代码质量与性能
掌握JavaScript闭包,提升代码质量与性能
|
28天前
|
自然语言处理 JavaScript 前端开发
深入理解JavaScript中的闭包(Closures)
深入理解JavaScript中的闭包(Closures)
|
28天前
|
存储 自然语言处理 JavaScript
深入理解JavaScript的闭包(Closures)
深入理解JavaScript的闭包(Closures)
23 0
|
2月前
|
设计模式 JavaScript 前端开发
探索JavaScript中的闭包:从基础概念到实际应用
在本文中,我们将深入探讨JavaScript中的一个重要概念——闭包。闭包是一种强大的编程工具,它允许函数记住并访问其所在作用域的变量,即使该函数在其作用域之外被调用。通过详细解析闭包的定义、创建方法以及实际应用场景,本文旨在帮助读者不仅理解闭包的理论概念,还能在实际开发中灵活运用这一技巧。
|
2月前
|
缓存 JavaScript 前端开发
深入了解JavaScript的闭包:概念与应用
【10月更文挑战第8天】深入了解JavaScript的闭包:概念与应用
|
2月前
|
自然语言处理 JavaScript 前端开发
Javascript中的闭包encloure
【10月更文挑战第1天】闭包是 JavaScript 中一种重要的概念,指函数能够访问其定义时的作用域内的变量,即使该函数在其词法作用域之外执行。闭包由函数及其词法环境组成。作用域链和词法作用域是闭包的核心原理。闭包常用于数据隐藏和封装,如模块模式;在异步操作中也广泛应用,如定时器和事件处理。然而,闭包也可能导致内存泄漏和变量共享问题,需谨慎使用。