JavaScript闭包和匿名函数的关系详解

摘要:本文讲的是关于JavaScript闭包匿名函数两者之间的关系,从匿名函数概念到立即执行函数,最后到闭包。下面一起来看看文章分析,希望你会喜欢。

前面讲了一篇在for循环中加setTimeout输出内容,我们用到了一个闭包,但同时也可以说是匿名函数,到底匿名函数和闭包有没有关系呢?【答案是它们之间没有关系】

匿名函数

匿名函数,顾名思义,就是没有名字的函数,与之对应的就是有名字的函数,也叫具名函数。

//匿名函数
function (){
    console.log('匿名函数');
}
//具名函数
function myFn(){
    console.log('具名函数');
}
//变量a就是匿名函数的名字
var a = function(){
    console.log('a就是匿名函数的名字');
}

如果我们直接在控制台中运行匿名函数,会发现报错,无法执行。匿名函数是无法执行的,一般用到匿名函数的时候都是立即执行,也叫自执行匿名函数或者自调用匿名函数,一般人都叫立即执行函数。

立即执行函数

比较常见的立即执行函数如下:

;(function(){
    console.log('caibaojian.com');
})()

;(function(){
    console.log('caibaojian.com');
}());

上面这两种都是典型的立即执行函数写法,两者的区分就是一个执行在匿名函数括号外面,另外一个发起执行的括号在匿名函数里面。比较常见的是第一种写法,括号在匿名函数的括号外面。看下图:

步骤分解:

  1. 首先声明一个匿名函数 function(){alert('我是匿名函数')}。
  2. 然后在匿名函数后面接一对括号 (),调用这个匿名函数。

那为什么还要用一个括号包起来呢?其实是为了兼容JS的语法,如果我们不加括号,直接写成

function (){alert('我是匿名函数')}()

浏览器会报语法错误,想要通过浏览器的语法检查,必须加点小东西,比如下面几种,更多请看:深入javascript:立即调用的函数表达式

(function(){alert('我是匿名函数')} ()) // 用括号把整个表达式包起来
(function(){alert('我是匿名函数')}) () //用括号把函数包起来
!function(){alert('我是匿名函数')}() // 求反,我们不在意值是多少,只想通过语法检查。
+function(){alert('我是匿名函数')}()
-function(){alert('我是匿名函数')}()
~function(){alert('我是匿名函数')}()
void function(){alert('我是匿名函数')}()
new function(){alert('我是匿名函数')}()

实际上,立即执行函数的作用只有一个:创建一个独立的作用域,在这个作用域里面,外面访问不到,避免变量污染。比如我们前面的一篇文章,setTimeout的第三个参数里面讲到的一道题目。

for(var i=0;i<6;i++){
    setTimeout(function(){
        console.log(i); //为什么输出的总是 6,而不是0,1,2,3,4,5
    },i*1000);
}

我们发现上面这个定时器总是输出6,因为setTimeout里面的执行函数是异步的,执行的时候,i的值是贯穿整个作用域的,而不是单独一个给每个定期器分配了一个i,for运行完的值是6,此时输出就总是6了。

那怎么解决呢?用立即执行函数给每个定时器创造一个独立作用域即可。

for(var i=0;i<6;i++){
    (function(j){
        setTimeout(function(){
            console.log(j);
        },j*1000);
    })(i);  
}

在for循环执行时,立即执行函数就已经有了结果了。而每个立即执行函数里面的j值就是独立的一个,不会受后面影响。所以会分别执行5次定时器。

//第一个立即执行函数
(function(0){
    setTimeout(function(){
        console.log(0);
    })
})(0);
//第二个立即执行函数
(function(1){
    setTimeout(function(){
        console.log(1);
    })
})(1);
//……
//第六个立即执行函数
(function(5){
    setTimeout(function(){
        console.log(5);
    })
})(5);

i 的值从 0 变化到 5,对应 6 个立即执行函数,这 6 个立即执行函数里面的 j 「分别」是 0、1、2、3、4、5。

上面说了这么多关于匿名函数和立即执行函数的,相信你对这两个概念已经很清楚,那么闭包跟匿名函数有关系吗?

闭包

js闭包是指有权访问另一个函数作用域中的变量的函数,个人认为js闭包最大的用处就是防止对全局作用域的污染。闭包最神奇的地方就是能在一个函数外访问函数中的局部变量,把这些变量用闭包的形式放在函数中便能避免污染。更多介绍:浅谈JavaScript中的闭包

我们可以分离出上面的第一个立即执行函数

function box(i){
    setTimeout(function(){
        console.log(i);
    },i*1000);
}
box(1);

//或者这样
function box(i){
    function inner(){
        console.log(i);
    }
    return inner;
}
var outer = box(1);
outer();

结论

很明显这是一个闭包,然后我们再看看我们最前面的匿名函数代码和立即执行函数代码,可以看出匿名函数和闭包两者并没有关系。闭包既可以在匿名函数也可以在具名函数中使用。

这个for循环中的闭包怎么理解以及自执行匿名函数的作用:

这个for循环产生的闭包其实是定时器的回调函数,这些回调函数的执行环境是window,类似刚才例子中的引用inner的全局outer的执行环境,匿名函数则相当于刚才例子中的box函数。

Stackoverflow网站上的一个提问跟我们今天分析的类似。有一个回答挺好。

闭包机制适用于所有JavaScript函数,无论是否匿名。
我认为这两个概念之间的混淆来自于使用术语“闭包”,其中作者已经说过“下面的代码创建一个闭包”,然后给出了一个恰好使用匿名函数的例子。 在这种情况下,闭包机制通常是使特定代码段按预期工作的重要因素,而使用匿名函数而不是命名函数恰好是编码它的便捷方式。 阅读这些例子并且第一次看到“闭包”的人然后误解了这个术语,并继续在他们自己的Stack Overflow或博客文章中错误地使用它,因此混乱传播。

一开始我以为匿名函数跟闭包有关系,那是因为恰好这个定时器使用了闭包和匿名函数,让我们误认为两者之间有关系,其实还有很多种方法可以解决这个问题,比如我们之前说到的setTimeout的第三个参数,同样可以得到跟使用立即执行函数同样的效果。

所以说匿名函数和闭包之间没有什么关系,只不过很多时候在用到匿名函数解决问题的时候恰好形成了一个闭包,就导致很多人分不清楚匿名函数和闭包的关系。

原文:JavaScript闭包和匿名函数的关系详解 ,未经许可,禁止转载。
来源:前端开发博客 (http://caibaojian.com/javascript-closure-fn.html)