JavaScript - 闭包

135 阅读2分钟

部分概念内容来源于《JavaScript高级程序设计(第3版)》

1.概念

闭包是指有权访问另一个函数作用域中的变量的函数。【说白了,闭包就是一种特殊的函数】创建闭包的常见方式,就是在一个函数内部创建另一个函数。

一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)。但是闭包的情况又有所不同。

2.实例

这里举一个程序防抖的例子,之所以举这个例子,是因为对程序防抖程序印象比较深刻,因为按之前的理解,一个方法运行结束后,该方法内定义的变量函数就会立即被销毁。防抖程序debounce()运行结束后,里面的变量fn,delay,timer理论上都已经不存在了,如果在外部运行debounce()返回的匿名函数,就会报错,但是实际上,匿名函数在debounce()的外部依然可以使用这三个变量。

/**
 * 防抖程序要考虑的两个问题:
 *  1. 执行环境中this,以及接受的参数问题;
 *  2. 如何判断上一个防抖函数已经执行完成;
 * 
 * 解决方案:
 *  1. 通过获取执行环境的 this 和 arguments
 *      使用apply方法执行原始函数:fn.apply($this, args);
 *  2. 直接清除上一个计时器clearTimeout
        因为如果上一个计时器没有计时结束说明在单位 delay 时间内该函数执行的两次;
        如果已经计时结束执行完,说明未多次执行该函数
    * 
    **/
function debounce(fn, delay) {
    let timer = null; // 定义一个计时器
    
    return function() {
        let $this = this; // 被设置防抖的函数的执行环境的this
        let args = arguments;
        if (timer) {
            clearTimeout(timer);
            timer = null;
        }
        timer = setTimeout(function() {
            fn.apply($this, args);
        }, delay);
    };
}

3.说明

在匿名函数从debounce()中被返回后,它的作用域链被初始化为包含debounce()函数的活动对象和全局变量。这样匿名函数就可以访问在debounce()中定义的所有变量了。更重要的是,debounce()函数在执行完毕后,其活动对象也不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象。换句话说,当debounce()函数返回后,其执行环境的作用域链仍然在引用这个活动对象,但它的活动对象仍然会保留在内存中;直到匿名函数被销毁后,debounce()的活动对象才会被销毁。