闭包原理浅谈

177 阅读2分钟

在工作中很多时候会用到闭包,但是对于闭包的原理还比较懵懂,索性写一篇文章来聊聊闭包

什么是闭包

闭包在《js高程》中的解释是:「可以访问外部作用域的函数」,即,只要某函数定义在另一个函数内,并,它就是闭包,无论它是否使用了外部函数作用域中的变量

举个例子

    function fn() {
        let a = '1';
        let b = '2';
        return function() {
            console.log(a)
        }
    }
    let c = fn;
    c()

以上代码,匿名函数fn返回的匿名函数可以访问外部作用域的变量a,因此fn是一个闭包

闭包有什么作用

当闭包函数被全局变量引用时,由于闭包函数持有作用域外的变量,使该变量不会被浏览器的垃圾回收机制清除

为什么闭包可以做到使变量不被清除?

如果闭包函数被外部变量引用 (该点很关键),那么这个存在于堆内存中的闭包函数对象就一直存在着(因为它被外部引用着,只要外部作用域不退出,就不会被垃圾回收清除)。

那么这个函数对象上的 [[Scope]]属性(即作用域链)自然就不会被清除,那么作用域链引用的所有层级包含函数的活动对象就不会被清除,而函数使用的外部变量都在该属性上,因此也不会被清除。

准确说是闭包所包含的整个作用域链所引用的 变量对象 中的值不会被清除。

这也就意味着,所有返回函数的函数被接收之后,都有占用内存的闭包问题。

我们同时要关注的是,无论闭包的定义如何(是内部函数,还是被引用的外部函数),按照语言解释器的机制,无法被垃圾清除的是函数创建时的作用域链。

顺便谈谈词法作用域

  • 词法作用域即是静态作用域名,其与动态作用域形成对比,

  • 静态作用域: 函数执行时是使用的是函数定义时候的变量作用域;

  • 动态作用域: 函数执行的时候使用的是函数执行时的变量作用域

看一个有趣的例子

    var name = 'Mike'; //第一次定义name 
    function showName() { 
        console.log(name); //输出 Mike 还是 Jay ? 
    } 
    
    function changeName() { 
        var name = 'Jay'; 
        showName()
        } 
        
    changeName(); // 最后结果是输出 Mick

最后结果是输出Mick, 原因是由于,在执行showName时,使用的是定义时的变量作用域,基于作用域链,因此直接找到window对象上的name,即 Mick