学习JavaScript闭包

介绍

在之前的文章中连续介绍了作用域的知识,有了这些知识储备,我们就来学习本节的内容作用域闭包。回忆工作这几年,大量使用JavaScript或多或少也在运用闭包,现在我们试着从理论角度来讨论下闭包。

什么是闭包?

遇到这种问题,第一时间看mdn文档。官方文档如下解释:

闭包是由函数以及创建该函数的词法环境组合而成。

这个描述过于抽象不利于理解先放一边,我们先来看一段代码:

function foo() { 
    var a = 1;
    function bar() { 
       console.log( a ); // 1
    }
    bar(); 
}
foo();

复制代码

这是一段嵌套函数代码,foo函数中声名了一个bar函数。根据我们之前学习作用域相关的知识。词法作用域是由代码声明的位置决定的,foo函数其中有两个标识符abarbar函数可以访问在其外部声明的变量也就是a

再来看另一段代码:

function foo() { 
    var a = 1;
    function bar() { 
        console.log( a );
    }
    return bar; 
}
var baz = foo();
baz(); // 1

复制代码

这段代码直观的展示了闭包的效果。我们将bar函数本身当作一个函数对象返回。在运行foo函数后,其返回值(也就是bar函数的引用)赋值给变量baz并调用baz(),实际上就是通过不同的标识符引用调用了内部函数bar

foo()执行后,通常会期待foo()的整个内部作用域都被销毁,因为我们知道引擎有垃圾回收器用来释放不再使用的内存空间。由于看上去foo()的内容不会再被使用,所以很自然地会考虑对其进行回收。

而闭包的“神奇”之处正是可以阻止这件事情的发生。事实上内部作用域依然存在,因此没有被回收。谁在使用这个内部作用域?原来是bar()本身在使用。

bar()所声明的位置,它拥有涵盖foo()内部作用域的闭包,使得该作用域能够一直存活,以供bar() 在之后任何时间进行引用。

bar() 依然持有对该作用域的引用,而这个引用就叫作闭包。

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用 域之外执行。

循环与闭包

for循环是最常见的例子:

for (var i=1; i<=5; i++) { 
setTimeout( function timer() {
    console.log(i);
    }, i*1000 );
}
复制代码

我们对这段代码的预期结果分别输出数字1-5,每秒一次,每次一个。但这段代码在运行时会以每秒一次的频率输出五次6。延迟函数的回调会在循环结束时才执行。这显然不是我们想要的结果。

我们试图假设循环中的每个迭代在运行时都会给自己“捕获”一个i的副本。但是根据作用域的工作原理,实际情况是尽管循环中的五个函数是在各个迭代中分别定义的,但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个i

我们需要更多的闭包作用域,特别是在循环的过程中每个迭代都需要一个闭包作用域,例如:

for (var i=1; i<=5; i++) { 
    (function(j) {
        setTimeout( function timer() { 
        console.log( j );
        }, j*1000 );
    })( i );
}
复制代码

在迭代内使用IIFE会为每个迭代都生成一个新的作用域,使得延迟函数的回调可以将新的作用域封闭在每个迭代内部,每个迭代中都会含有一个具有正确值的变量供我们访问。

ES6中let声明,可以用来劫持块作用域,并且在这个块作用域中声明一个变量,例如:

for (let i=1; i<=5; i++) { 
    setTimeout( function timer() {
        console.log(i);
    }, i*1000 );
}
复制代码

模块与闭包

我们利用闭包的强大功能,可以实现一个JavaScript模块,例如:

function module() {
    var something = "module";
    var another = [1, 2, 3];
    function doSomething() { 
        console.log( something );
    }
    function doAnother() {
        console.log( another.join( " ! " ) );
    }
    return {
        doSomething: doSomething, 
        doAnother: doAnother
    }; 
}
var foo = module(); 
foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3
复制代码

首先,module只是一个函数,必须要通过调用它来创建一个模块实例。如果不执行外部函数,内部作用域和闭包都无法被创建。

然后module函数返回一个对象。我们保持内部数据变量是隐藏且私有的状态。可以将这个对象类型的返回值看作本质上是模块的公共API。

这个对象类型的返回值最终被赋值给外部的变量foo,然后就可以通过它来访问API中的属性方法,比如foo.doSomething()

可以对这个模式进行简单的 改进来实现单例模式:

var foo = (function module() {
    var something = "module";
    var another = [1, 2, 3];
    function doSomething() { 
        console.log( something );
    }
    function doAnother() {
        console.log( another.join( " ! " ) );
    }
    return {
        doSomething: doSomething, 
        doAnother: doAnother
    }; 
})();

foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3
复制代码

小结

闭包本质也是作用域的产物,闭包的规律也是作用域的规律。本章也是简单的介绍了一下闭包,更多更深入的内容还是来源我们项目中的代码。

参考

转载于:https://juejin.im/post/5cc1b6d5f265da03761e8ff0

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JavaScript中的闭包是指一个函数能够访问并使用其外部作用域中的变量,即使在该函数被调用之后,该外部作用域的上下文已经销毁。简单来说,闭包是指一个函数保留了对其词法作用域的引用,使得函数可以访问和操作外部作用域的变量。 在JavaScript中,闭包可以通过函数嵌套来创建。当一个函数在内部定义了另一个函数,并且内部函数引用了外部函数的变量,那么内部函数就形成了闭包闭包函数可以访问和修改外部函数的变量,即使在外部函数执行完毕后,这些变量依然存在于闭包函数的作用域中。 以下是一个闭包函数的例子: ```javascript function outerFunction() { var outerVariable = 'Hello'; function innerFunction() { console.log(outerVariable); } return innerFunction; } var closure = outerFunction(); closure(); // 输出 'Hello' ``` 在这个例子中,内部函数`innerFunction`形成了闭包,它可以访问和使用外部函数`outerFunction`中的变量`outerVariable`,即使`outerFunction`执行完毕后,闭包仍然可以访问和操作`outerVariable`的值。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [JavaScript中的闭包](https://blog.csdn.net/qq_44482048/article/details/128714553)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [轻松学习Javascript闭包函数](https://download.csdn.net/download/weixin_38628429/13018893)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [JavaScript闭包](https://blog.csdn.net/qq_57586976/article/details/127678306)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值