回想一下,我们平时写的一些具名函数(也就是区别于匿名函数的函数),如下面一个例子:
1 function sayHello(){ 2 console.log("hello,我是林丽君"); 3 } 4 sayHello();
分析一下上面的调用方式,sayHello是我们的函数名,当我们想要它执行的时候,我们就在这个函数名的后面添加一对括号(),像这样sayHello(),我们的函数就会被执行了。还不懂?没关系,我们再来分析一个例子:
1 var sayHello=function(){ 2 console.log("hello,我是林丽君"); 3 } 4 sayHello();//hello,我是林丽君
在上面的例子中,我们定义了一个匿名函数,这个匿名函数干的事就是在控制台输出“hello,我是林丽君”,然后把这个匿名函数赋给变量sayHello,那么我们调用的时候就是在这个变量名的后面直接加上一对括号(),这样我们的函数就会被执行,在上面的例子中,如果我们不加后面那对括号,我们调用这个sayHello会输出什么呢?我们来用代码测试一下:
1 function sayHello(){ 2 console.log("hello,我是林丽君"); 3 } 4 console.log(sayHello);
在控制台我们会看到:
它会输出整个函数的定义,所以我们在这个函数的后面加上一个(),其实是在调用这个函数,所以自执行函数的另外一种更明确的叫法应该是“立即调用的函数表达式”,它并不是自己能够自动执行,而是我们使用者自己在显式的调用它。注意在这里我们要提醒各位读者,你可不能贪图方便将我们的代码写成这样:
1 function(){ 2 console.log("hello,我是林丽君"); 3 }();
这段代码会导致语法错误,因为JavaScript将function关键字当做一个函数声明的开始,而函数声明的后面不能跟圆括号,但是函数表达式的后面可以跟圆括号,想要将我们的函数声明转变成函数表达式,我们只需要将我们的函数声明加上一对圆括号,像这样:
1 (function(){ 2 console.log("hello,我是林丽君"); 3 })();
如果我们的函数是需要传参的话,像下面这样,我们直接在后面的圆括号里面把我们的参数传进去就可以了
1 (function sayLove(i,you){ 2 console.log(i+" love "+you); 3 })("林丽君","张俊祺"); 4 //这个时候会输出 林丽君 love 张俊祺
看到这里你一定很疑惑我们为什么要这样写,你经常看到别人说这样写是为了创建作用域,或许你只是人云亦云,并不是真的理解这个话的意思,下面我们来分析它的作用:
在JavaScript里面,我们并没有块级作用域的概念,这个话怎么理解呢?下面来看一段代码:
1 function countNum(num){ 2 for(var i=0;i<num;i++){ 3 } 4 //这里我们调用一下i 5 console.log(i); 6 //这个时候我们控制台会输出i,一开始变量i的值是0,在java和C++等语言中,变量i在循环之后会被销毁,但是在JavaScript里面,这个i可以在函数的内部的任何地方被调用 7 }
如果你在函数里面重复声明了这个i,JavaScript是不是提示你重复定义了这个i,在java语言中,会直接报错,因为一个变量不能再一个函数中被重复定义多次,所以匿名函数可以用来模仿块级作用域来避免这些问题,像这样:
function countNum(num){ (for(var i=0;i<num;i++){})(); console.log(i); //这里我们再调用一下i,这个时候会报错 }
所以这种技术通常用在全局作用域中被用在函数的外部,从而限制向全局作用域添加过多的变量和函数,同时可以避免命名冲突
这种技术我们比较关注的的点就是我们的this对象,首先我们必须明白这一点,我们的this是基于函数的执行环境的,在全局函数中,我们的this是window对象,当函数被当做某个对象的方法调用的时候,this是指向那个对象,不过,匿名函数的执行环境具有全局性,因此其this对象通常指向window,当然根据我们编写闭包的方式,这个分界线并不总是那么清楚。