深入学习JS系列-闭包

107 阅读4分钟

「这是我参与2022首次更文挑战的第20天,活动详情查看:2022首次更文挑战」。

打算重新按照红宝书过一遍JavaScript了,那就一个一个更新吧。打算做成一个非常非常认真的一个系列,感兴趣的也可以关注一波。

一、开篇

上一篇介绍了这个系列的第一个知识点立即执行函数,这一篇将介绍闭包,很多人都分不清立即执行函数和闭包的区别,我也是其中之一,那就接着立即执行函数来说一说闭包。

二、作用域

在学习闭包之前,我们需要先对JavaScript的作用域有一个全面的了解。 我们都知道变量的作用域分为两种:全局作用域局部作用域。 我们在上一篇立即执行函数中介绍过JavaScript的作用域分为局部和全局。

  • JavaScript可以在函数内部可以读取全局变量
var name = "小铃铛"
function fn() {
    console.log(name)
}
fn()
//输出:小铃铛
  • JavaScript外部无法读取函数内部的变量
function fn() {
    var age = 11
}
console.log(age)

image.png

  • JavaScript在函数内部声明变量的时候一定注意使用var/let/const声明,否则声明的则是全局变量。
function fn() {
    scroll = "scroll"
}
fn()
console.log(scroll)
//输出:scroll

三、闭包

如果我们需要获取函数内部的局部变量,上面也说过了,正常情况下是获取不到的,我们需要采取另一种方式。在函数内部在定义一个函数。

function f1() {
    var a = 1
    function f2() {
        console.log(a)
    }
}
//输出:2

在上面的代码中,我们可以看见函数f2定义在函数f1中,这时f1内部的所有局部变量,对f2都是可见的。Butf2内部的变量,对f1就是不可见的。

这就是JavaScript独有的“链式作用域”结构,子对象会一级一级的向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

这样,我们通过这种链式结构,将f2的作为返回值,我们就可以在f1外部读取到f2内部的变量了。

function f1() {
    var a = 1
    function f2() {
        console.log(a)
    }
    return f2
}
var reslut = f1()
reslut()

//输出:1

1.定义

函数里面嵌套函数,我们就称内部的函数为闭包。

比如上面f2函数,我的理解就是,闭包能够读取其他函数内部的变量的函数。

2.用途

我们日常使用闭包有两大用途:一个是上面的例子,读取函数内部的变量;另一个就是,让这些变量的值始终保持在内存中。

先看一个例子

function f1() {
    var name = "小铃铛"
    addFn = function(){name+="的打怪之路"}
    function f2(){
        console.log(name)
    }
    return f2
}
var result = f1()
result()
addFn()
result()

image.png

在这段代码中,result就相当于闭包函数f2,第一次打印的值是"小铃铛",第二次打印的值是"小铃铛的打怪之路"。这就证明了,函数f1中的变量name一直保存在内存中,并没有在f1调用后删除。

原因是什么呢?函数f1是函数f2的父函数,f2被赋值给一个全局变量,这导致f2始终在内存中,f2的存在又依赖于f1,因此f1也始终在内存中,不会在调用之后被垃圾回收机制回收。

这段代码中值得注意的地方就是addFn这个函数,它不是使用var声明的,所以它是一个全局变量;addFn也是一个匿名函数,本身也算是一个闭包,可以在函数外部对函数内部进行变量进行操作。

3.注意事项

闭包有以上这么多好处,但是它同时也会导致其他一些问题的出现。

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

关于闭包的介绍就到这里吧~~~~~