03 javascript 闭包

84 阅读2分钟

闭包:有权访问另一个函数作用域中变量的函数。

闭包分析

function outer(p) {
    var a = 1
    return function inner() {
        console.log('p:' + p, 'a:' + a)
    }
}

var fn = outer(2)

fn()
  • 开始执行程序,提升变量:outer 函数和 fn 变量,创建全局上下文对象 GO
  • 执行 outer 函数,传入参数 2,将其执行上下文压入 call stack
  • 创建函数 outer 变量对象 VO,VO包含 arguments、参数、被提升的变量声明、this,并把 VO 放到作用域链 [[scope]] 的顶层
  • 执行 a = 1,赋值变量 a(变量a的查找遵循作用域链查找规则: VO -> GO 的顺序)
  • 返回函数 inner
  • 函数 outer 执行完成后其执行上下文将从 call Stack中弹出,本来其中创建的 VO 也应被随之销毁,但是其内部函数 inner [[scope]] 引用了 VO,所以 VO 此时不会被销毁
  • 将返回的函数 inner 赋值给变量 fn
  • 执行函数 fn,其[[scope]]如图:

image.png

闭包的应用

for循环变量引用

for (var i = 0; i < 5; i++) {
    setTimeout(() => {
        console.log(i)
    })
}
// 将打印 5 个 5

// 使用闭包修改后
for (var i = 0; i < 5; i++) {
    (function(num) {
        setTimeout(() => {
            console.log(num)
        })
    })(i)
}

代码模块化 因为js并没有块级作用域(es2015之前),只有 全局作用域 和 函数作用域,可以利用函数闭包的特性隐藏某些变量,实现模块化

var counter = (function() {
    var privateCount = 1
    function changeCount(val) {
        privateCount += val
    }
    return {
        increase() {
            changeCount(1)
        },
        decrease() {
            changeCount(-1)
        },
        getCount() {
            return privateCount
        }
    }
})()

缓存

var sum = (function() {
    var cache = {}
    return function (a, b) {
        // 如果函数中是一些比较耗时的操作,且操作是幂等的,就可以进行缓存
        if (cache[`${a}+${b}`] !== undefined) {
            return cache[`${a}+${b}`]
        }
        const total = a + b
        cache[`${a}+${b}`] = total
        return total
    }
})()

单例类

var User = (function () {
    var instance = null;

    return function inner(name) {
        if (instance !== null) return instance;
        this.name = name;
        instance = this;
    };
})();

闭包可能带来的问题

因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响, 其会根据闭包数量的多少而在内存中创建更多的变量对象, 最终可能会导致内存溢出 等情况!

不合适的闭包使用可能带来内存泄漏,但是这不是闭包的问题。