js中的闭包的理解
在正常情况下,如果定义了一个函数,就会产生一个函数作用域,在函数体中的局部变量会在这个函数作用域中使用。一旦函数执行完成,函数所占空间就会被回收,存在于函数体中的局部变量同样会被回收,回收后将不能被访问到。那么如果我们期望在函数执行完成后,函数中的局部变量仍然可以被访问到,这能不能实现呢?答案是可以的,使用本节将要讲到的闭包,就可以实现这个目标。在学习闭包之前,我们需要掌握一个概念——执行上下文环境。
执行上下文环境
JavaScript每段代码的执行都会存在于一个执行上下文环境中,而任何一个执行上下文环境都会存在于整体的执行上下文环境中。根据栈先进后出的特点,全局环境产生的执行上下文环境会最先压入栈中,存在于栈底。当新的函数进行调用时,会产生的新的执行上下文环境,也会压入栈中。当函数调用完成后,这个上下文环境及其中的数据都会被销毁,并弹出栈,从而进入之前的执行上下文环境中。需要注意的是,处于活跃状态的执行上下文环境只能同时有一个,即图3-1所示的深色背景的部分。
我们再通过以下这段代码来看看执行上下文环境的变化过程。
1 var a = 10; // 1.进入全局执行上下文环境
2 var fn = function (x) {
3 var c = 10;
4 console.log(c + x);
5 };
6 var bar = function (y) {
7 var b = 5;
8 fn(y + b); // 3.进入fn()函数执行上下文环境
9 };
10 bar(20); // 2.进入bar()函数执行上下文环境
从第1行代码开始,进入全局执行上下文环境,此时执行上下文环境中只存在全局执行上下文环境
执行上下文环境
| |
| |
| |
| 全局执行上下文环境 | <---- 活跃状态的上下文环境
____________________
当代码执行到第10行时,调用bar()函数,进入bar()函数执行上下文环境中
执行上下文环境
| |
| |
| bar()函数执行上下文环境 | <---- 活跃状态的上下文环境
| 全局执行上下文环境 |
______________________
执行到第10行后,进入bar()函数中,执行到第8行时,调用fn()函数,进入fn()函数执行上下文环境中。
执行上下文环境
| |
| fn函数执行上下文环境 | <---- 活跃状态的上下文环境
| bar函数执行上下文环境 |
| 全局执行上下文环境 |
______________________
进入fn()函数中,执行完第5行代码后,fn()函数执行上下文环境将会被销毁,从而弹出栈。
执行上下文环境
| |
| |
| bar函数执行上下文环境 | <---- 活跃状态的上下文环境
| 全局执行上下文环境 |
______________________
fn()函数执行上下文环境被销毁后,回到bar()函数执行上下文环境中,执行完第9行代码后,bar()函数执行上下文环境也将被销毁,从而弹出栈。
执行上下文环境
| |
| |
| |
| 全局执行上下文环境 | <---- 活跃状态的上下文环境
____________________
最后全局上下文环境执行完毕,栈被清空,流程执行结束。像上面这种代码执行完毕,执行上下文环境就会被销毁的场景,是一种比较理想的情况。有另外一种情况,虽然代码执行完毕,但执行上下文环境却被无法干净地销毁,这就是我们要讲到的闭包。
闭包的概念
对于闭包的概念,官方有一个通用的解释:一个拥有许多变量和绑定了这些变量执行上下文环境的表达式,通常是一个函数。闭包有两个很明显的特点。· 函数拥有的外部变量的引用,在函数返回时,该变量仍然处于活跃状态。· 闭包作为一个函数返回时,其执行上下文环境不会被销毁,仍处于执行上下文环境中。在JavaScript中存在一种内部函数,即函数声明和函数表达式可以位于另一个函数的函数体内,在内部函数中可以访问外部函数声明的变量,当这个内部函数在包含它们的外部函数之外被调用时,就会形成闭包。
我们来看看下面这段代码。
1 function fn() {
2 var max = 10;
3 return function bar(x)
4 if (x > max) {
5 console.log(x);
6 }
7 };
8 }
9 var f1 = fn();
10 f1(11); // 11
代码开始执行后,生成全局上下文环境,并将其压入栈中。
执行上下文环境
| |
| |
| |
| 全局执行上下文环境 | <---- 活跃状态的上下文环境
____________________
代码执行到第9行时,进入fn()函数中,生成fn()函数执行上下文环境,并将其压入栈中
执行上下文环境
| |
| |
| fn函数执行上下文环境 | <---- 活跃状态的上下文环境
| 全局执行上下文环境 |
______________________
fn()函数返回一个bar()函数,并将其赋给变量f1。当代码执行到第10行时,调用f1()函数,注意此时是一个关键的节点,因为f1()函数中包含了对max变量的引用,而max变量是存在于外部函数fn()中的,此时fn()函数执行上下文环境并不会被直接销毁,依然存在于执行上下文环境中。
执行上下文环境
| |
| bar函数执行上下文环境 | <---- 活跃状态的上下文环境
| fn函数执行上下文环境 |
| 全局执行上下文环境 |
___________________________
等到第10行代码执行结束后,bar()函数执行完毕,bar()函数执行上下文环境才会被销毁,同时因为max变量引用会被释放,fn()函数执行上下文环境也一同被销毁。最后全局上下文环境执行完毕,栈被清空,流程执行结束。从分析就可以看出闭包所存在的最大的一个问题就是消耗内存,如果闭包使用越来越多,内存消耗将越来越大。
备注:摘自于书籍:《Javasript 重难点实例精讲》