VueBloghyhero6

几道例题来理解闭包(还是笔记)

2021-03-04 / 2021-03-04 / 340次浏览

实战例题1:求下面代码的输出

const foo = (function() {
    var v = 0
    return () => {
        return v++
    }
}())
for (let i = 0; i < 10; i++) {
    foo()
}
console.log(foo())

答案:10
解析:
foo 是一个立即执行函数,我们尝试打印foo:

const foo = (function() {
    var v = 0
    return () => {
        return v++
    }
}())

console.log(foo)

输出:

() => {
    return v++
}

在循环执行时,执行 foo() , 这样引用自由变量10次, v 自增 10次,最后执行 foo 时,得到10. (自由变量是指没有在相关函数作用域中声明,但是使用了变量。)

实战例题2: 求下面代码的输出

const foo = () => {
    var arr = []
    var i
    
    for (i = 0; i < 10; i++) {
        arr[i] = function () {
            console.log(i)
        }
    }

    return arr[0]
}

foo()()

答案:10,这时自由变量为 i,分析类似例题 1:foo() 执行返回的是 arr[0], arr[0] 此时是函数:

function () {
    console.log(i)
}

变量 i 值为 10.

这个我到浏览器中输出然后理解了下,发现大致思路是对的。
实战例题3:求下面代码的输出

var fn = null
const foo = () => {
    var a = 2
    function innerFoo() { 
        console.log(a)
    }
    fn = innerFoo    
}

const bar = () => {
    fn()
}

foo()
bar()

输出结果是2, 首先这个函数 foo 先行执行, 内部函数innerFoo 赋值给了全局变量 fn,由于innerFoo 调用了a,所以变量 a 得以保存, bar() 函数开始调用, fn() 调用,接着输出就是2.不知道我解释清楚了没有。

给出一波官方大佬的解释。

正常来讲,根据调用栈的知识,foo 函数执行完毕之后,其执行环境生命周期会结束,所占内存被垃圾收集器释放,上下文消失。但是通过 innerFoo 函数赋值给 fn,fn 是全局变量,这就导致了 foo 的变量对象 a 也被保留了下来。所以函数 fn 在函数 bar 内部执行时,依然可以访问这个被保留下来的变量对象,输出结果为 2。

例题4:求下面代码的输出
我们将上面的例子稍作修改:

var fn = null
const foo = () => {
    var a = 2
    function innerFoo() { 
        console.log(c)            
        console.log(a)
    }
    fn = innerFoo
}

const bar = () => {
    var c = 100
    fn()    
}

foo()
bar()

执行结果是:报错。
例题 4 分析
在 bar 中 执行 fn()时,fn() 已经被复制为 innerFoo, 变量 c 并不在其作用域链上,c 只是 bar 函数的内部变量。因此报错 ReferenceError: c is not defined。
图示分析:

思考例题 5:如何利用闭包实现单例模式
单例模式,是一种常用的软件设计模式。GoF 在 《设计模式:可复用面向对象软件的基础》一书中给出了如下定义:

Ensure a class only has one instance, and provide a global point of access to it.
保证一个类只有一个实例,并提供一个访问它的全局访问点。

使用闭包我们可以保持对实例的引用,不被垃圾回收机制回收,因此:

function Person() {
    this.name = 'lucas'
}
const getSingleInstance = (function(){
     var singleInstance
    return function() {
         if (singleInstance) {
            return singleInstance
         } 
        return singleInstance = new Person()
    }
})()
const instance1 = new getSingleInstance()
const instance2 = new getSingleInstance()

单例模式的引用,这段代码貌似好强的样子。
事实上,我们有 instance1 === instance2。 因为借助闭包变量 singleInstance, instance1 和 instance2 是同一引用的(singleInstance),这正式单例模式的体现。

最后放上大佬的总结

本课我们通过介绍理论知识加例题实战的方式梳理了 JavaScript 中闭包、内存、执行上下文、作用域、作用域链等概念。

这些内容说基础,确实不算很难;说复杂,它绝对又能衍生出很多知识点。这些知识点不是 JavaScript 所特有的,但是在前端开发中又极具自身语言风格。它绝不只是纯理论概念,只有解决真实的开发问题才有实际意义。

一个合格的高级前端工程师需要做的并不是如数家珍地背诵“闭包和 GC 原理”,而是根据面临的场景,凭借扎实的基础,能够通过查阅资料,提升应用性能,分析内存事故和突破瓶颈。