前端面试:聊聊闭包 一盏茶的功夫让你彻底掌握闭包!

前言

在JavaScript中,闭包是一个非常重要的概念,对于编写高质量的JavaScript代码和理解某些设计模式非常关键。很多人认为闭包很难,不过我相信你看完了我的文章会有收获,或许对闭包有一个新的认识

在讲闭包之前,大家可以先看看我之前的文章,有关于调用栈的讲解,配合本篇文章食用效果更佳哦~

文章快捷入口:调用栈

首先我们先来看一道题

js 复制代码
var arr = []

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

for (var j = 0; j < arr.length; j++){
    arr[j]()
}

我们先来思考一下,最后会打印什么呢?让我们输出一下

可以看到,输出了10个10。这里为什么会输出10个10呢?当函数arr[j]调用时,要输出i的值,所以在arr[j]的执行上下文中寻找i,但是并没有找到i,接下来再它的外层作用域中寻找i,在这里也就是全局作用域,找到了i,在全局区中i的值通过for循环已经变成了10,所以当console.log(i)时,打印的是10个10。

那如果我们想要打印 0,1,2,3,4,5,6,7,8,9,我们将这段代码改一下,应该怎么改呢?这个问题我们先留在这,在我们学习了闭包之后,就可以轻松解决。

闭包

在JS中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量的, 当内部函数被返回到外部函数之外时,即使外部函数执行结束了, 但是内部函数引用了外部函数的变量,那么这些变量依旧会被保存在内层中, 我们把这些变量的集合称为闭包

这个概念是不是有些难以理解呢?不用担心,我们接下来拿一道题来解释一下

js 复制代码
function foo() {
    var myName = '菌菌'
    let test1 = 1
    let test2 = 2
    var innerBar = {
        getName: function() {
            console.log(test1);
            return myName  
        },
        setName: function (newName) {
            myName = newName
        }
    }
    return innerBar
    
}
var bar = foo()
bar.setName('来颗奇趣蛋')
console.log(bar.getName());

我们先来看一下输出结果

我们画调用栈的图来分析一下这道题

首先,全局执行上下文入栈

调用foo()函数,foo()执行上下文入栈

foo()函数执行完成,出栈

接下来,我们执行bar.setName('来颗奇趣蛋')

接下来我们发现,有一个赋值语句,myName = newName ,可是,由于foo()已经出栈,bar.setName的执行上下文和全局区的执行上下文都没有myName 这个变量,并且当bar.setName('来颗奇趣蛋') 执行完成出栈时,调用bar.getName()函数,它的执行上下文入栈,打印输出console.log(test1) 时,我们发现也找不到test01这个变量,那么最后为什么会输出1 和 '来颗奇趣蛋'呢?

其实,虽然foo()的执行上下文出栈了,但还是留下了一个'小书包',这里面存放了myName和test01,,如图

这里小伙伴们可能就要问了,那么多变量都不留,为什么就留这两个呢?这里我们来解释一下,因为setName()和getName()定义在了foo()里面,setName()和getName()是可以访问foo()里面的变量的,但可是setName()和getName()并不是在foo()里面调用的,而是在全局区调用的。

foo()创建了这两个函数,但并没有调用这两个函数,而是通过return返回一个对象。通俗一点来讲,当这两个函数在外部调用时,foo()并不能控制这两个函数,而这两个函数调用时,使用了foo()函数里的test1变量myName变量,foo()函数不知道这两个函数什么时候会被调用,而当这两个函数调用时,它们会向foo()函数讨要test1myName,所以foo()虽然出栈了,但是会把两个函数调用时需要使用的变量留下,当两个函数调用时,就可以直接来取。而图中黄色的框所包裹的变量,我们就称之为闭包

看完了这个例子,我们是不是可以理解了开头那段概念。当内部函数(setName和getName被返回到外部函数(foo)之外时,这里也就是全局区,虽然foo()执行完毕了,但是之后两个函数执行时,引用了foo()内的变量,这些变量依旧会保存在内层之中,而这些被保存的变量的集合 ,我们就称之为闭包(closure)

接下来我们再看一个例子来掌握一下:

js 复制代码
function a() {
    function b() {
        var bbb = 234
        console.log(aaa);   //输出123
    }
    var aaa = 123
    return b
}

var c = a()
c()

直接上图理解:

内部函数b()被返回到了外部函数a()之外,这里也是全局区,内部函数调用时,引用了a()中变量aaa,尽管a()已经出栈,但这些变量依旧会被保存下来,所以输出123.

我们也可以使用闭包的方法,让开头那个例子打印出0,1,2,3,4,5,6,7,8,9

js 复制代码
var arr = []

for (var i = 0; i < 10; i++){
    
    (function a() {
            var j = i
            arr[i] = function () {
                console.log(j);
            }
        }) ()
    }
    
    for (var j = 0; j < arr.length; j++){
        arr[j]()
    }

总结

在JS中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量的, 当内部函数被返回到外部函数之外时,即使外部函数执行结束了, 但是内部函数引用了外部函数的变量,那么这些变量依旧会被保存在内层中, 我们把这些变量的集合称为闭包

今天的内容就到这啦,如果你觉得小编写的还不错的话,或者对你有所启发,请给小编一个辛苦的赞吧

相关推荐
Tiffany_Ho4 分钟前
【TypeScript】知识点梳理(三)
前端·typescript
安冬的码畜日常1 小时前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js
太阳花ˉ1 小时前
html+css+js实现step进度条效果
javascript·css·html
小白学习日记2 小时前
【复习】HTML常用标签<table>
前端·html
john_hjy2 小时前
11. 异步编程
运维·服务器·javascript
风清扬_jd2 小时前
Chromium 中JavaScript Fetch API接口c++代码实现(二)
javascript·c++·chrome
丁总学Java2 小时前
微信小程序-npm支持-如何使用npm包
前端·微信小程序·npm·node.js
yanlele3 小时前
前瞻 - 盘点 ES2025 已经定稿的语法规范
前端·javascript·代码规范
It'sMyGo3 小时前
Javascript数组研究09_Array.prototype[Symbol.unscopables]
开发语言·javascript·原型模式