网站首页 » 前端开发 » JavaScript » JavaScript 闭包(Closure)
上一篇:
下一篇:

JavaScript 闭包(Closure)

闭包是什么?为什么要用到闭包?这些问题想必你已经听过不少了,特别是如果你面试前端开发的时候,这是你怎么也躲不过的话题。现在我们来看看闭包到底是什么东西。

什么是闭包?

闭包就是有权访问另一个函数作用域内数据资源(变量、函数)的函数。至于闭包有什么用,下面我们会说到。现在我们要了解闭包,我们得先来明白有关于闭包的一些名词。

相关名词

作用域:就是可访问数据资源(变量、函数)的一个范围,即作用域控制着变量与函数的可见性和生命周期。

作用域链:JavaScript 中所有作用域的集合。你可以把它想象成一串鞭炮,中间那条引线就是这个链,两边的鞭炮就是一个一个的作用域。

执行环境:其实执行环境就跟作用域差不多,只不过它里面多存放了当前执行函数所需的数据资源(变量、函数等),它在函数执行时产生,每一个执行环境都对应着一个变量对象。

变量对象:一个包含了作用域内所有数据资源(变量、函数等)的对象。

活动对象:变量对象的一种,所谓的活动对象就是当前正在运行的变量对象,相当于正在燃烧的那些鞭炮,只不过在 JavaScript 中只有一个点燃了的鞭炮(函数正在运行时所需要的变量对象)。

内存回收机制:用于回收无用的资源来释放内存空间的一个东西。

有关作用域和作用域链的知识可以看这篇文章《JavaScript 作用域和作用域链

闭包详解

闭包的一个作用就是可以让你访问到函数内的数据资源,也就是说让函数内的数据资源让外部可以访问到。我们来看看下面这个小例子:

<script>
function a(){
    var localAttr = "我是一个局部变量"
        return function(){
            return localAttr;
        }
}

var runFn = a();
console.log(runFn());
</script>

通过在函数里面创建函数并返就可以让外部使用函数内的局部变量。所以我们不能看出闭包的一种最常见的表示形式,在一个函数内创建一个函数并返回它。或者我们可以这样理解它的这个作用:闭包把一个局部变量变成了一个全局变量,这里说的全局只是说你可以在任何地方使用这个局部变量,但这个变量并不是挂在window 对象下的属性。

现在我们来看一个更有意思的例子:

<script>
function a(){
    var arr = [];
    for(var i=0;i<5;i++){
        arr[i] = function(){
            return i;
        }
    }
    return arr;
}
// 把函数a的执行结果(返回的arr)赋值给b;
var b = a();

// 手动逐一输出:
console.log(b[0]()); // 5
console.log(b[1]()); // 5
console.log(b[2]()); // 5
console.log(b[3]()); // 5
console.log(b[4]()); // 5
</script>

执行函数后,数组里输出的结果全是 5 ,这肯定不是我们想要的,我们写这一段代码的本意是输出的值跟下标一样,也就是说 arr[0]() 输出 0 ,arr[1]() 输出 1 ,arr[2]() 输出 2 ,arr[3]() 输出 3 ,arr[4]() 输出 4。那这是为什么呢?因为当函数里的 for 循环执行完之后 i 的值就已经变成了5 ,为什么是 5 而不是 4 呢?因为只有当 i=5 时才会结束并跳出 for 循环。那要怎么才能达到我们想要的结果呢?没错就是用我们本文介绍的闭包来实现。

<script>
function a(){
    var arr = [];
    for(var i=0;i<5;i++){
        arr[i] = (function(n){
            return function(){
                return n;
            }  
        })(i);
    }
    return arr;
}
// 把函数a的执行结果(返回的arr)赋值给b;
var b = a();
console.log(b[0]()); // 0
console.log(b[1]()); // 1
console.log(b[2]()); // 2
console.log(b[3]()); // 3
console.log(b[4]()); // 4
</script>

为什么这样就可以实现我们想要的效果?现在我们来分析下代码。

改进后的代码,在每一次循环中都会立即执行一个匿名函数把 i 当前的值赋值给匿名函数的局部变量 n (变量是值传递)并返回一个匿名函数(地址)赋值给数组。由于我们在匿名函数里返回了变量 n ,所以在 for 循环结束后,每一个匿名函数执行时对应的变量 n 是不会被垃圾回收机制回收,一直存在内存中。那为什么这里的 n 可以实现不同的值,而在未改进的代码中的 i 却不可以?前面我们也说到了,立即执行一个匿名函数并返回一个匿名函数(地址)赋值给数组,所以每一个匿名函数都会有一个属于自己的局部变量 n 。也许你会说那这些匿名函数不是一样的吗,没错,可你别忘了函数也是一个对象,对象是址传递的,虽然它们长得是一样,但它们在内存中的实际地址是不一样的,这也是为什么可以有多个 n 值存在的原因。

改进后的代码中用到了立即执行函数,至于什么是立即执行函数,这也有一篇文章,你可以前往阅读《立即执行函数(function ( ){……})( )

  • 微信扫一扫,赏我

  • 支付宝扫一扫,赏我

声明

原创文章,不经本站同意,不得以任何形式转载,如有不便,请多多包涵!

本文永久链接:http://yunkus.com/javascript-closure/

发表评论

电子邮件地址不会被公开。 必填项已用*标注

评论 END