Zerlinda's Blog

浅谈JavaScript立即执行函数

JavaScript中立即执行函数也叫匿名自调用函数。因为直到ES5为止JS里都只有函数作用域, JavaScript中没有命名空间,没有局部作用域。而且只有function代码块内部可以隔离变量作用域。所以自调用函数目前最大的作用是隔离作用域,防止变量弥散到全局,以免各种js库冲突。

立即执行函数的另一个作用就是由于javascript里没有入口函数(参考C和C++的main()函数)的概念,在现今的模块化方案成熟稳定之前,立即执行函数也常常用来充当主函数的角色。

 ( function(){…} )()和( function (){…} () )是两种javascript立即执行函数的常见写法。要理解立即执行函数,需要先理解一些函数的基本概念。

函数声明、函数表达式

函数声明:使用function关键字声明一个函数,再指定一个函数名,叫函数声明。形如:function fnc () {…};

匿名函数: 使用function关键字声明一个函数,但未给函数命名,所以叫匿名函数。形如:function () {…};

函数表达式使用function关键字声明一个函数,但未给函数命名。最后将匿名函数赋予一个变量,叫函数表达式,这是最常见的函数表达式语法形式。形如:var fnc = function () {…};

首先我们要知道,JavaScript解析过程分为两个阶段,一个是编译阶段,另外一个就是执行阶段。

编译阶段

编译阶段就是我们常说的JavaScript预处理阶段,在这个阶段JavaScript解释器将完成把JavaScript脚本代码转换到字节码。

执行阶段

在编译阶段JavaScript解释器借助执行环境把字节码生成机械码,并顺序执行。

编译阶段完成对var , function声明的变量提升,首先创建一个当前执行环境下的活动对象,然后将用 var 声明的变量设置为活动对象的属性(也就是将其添加到活动对象当中)并将其赋值为undefined,然后将 function 定义的函数也添加到活动对象当中。

所以函数声明和函数表达式不同之处在于,对于函数声明Javascript引擎在编辑阶段会进行‘函数声明提升’(Hoisting),也就是函数声明在执行之前javascript引擎会对其预编译处理进行解析。而函数表达式必须等到Javascirtp引擎执行到它所在行时,才会从上而下一行一行地解析函数表达式。函数声明和函数表达式在预解析的不同表现,其主要的原因就是 var 和 function 两者不同的提升。

举例来说

        alert(a);
	var a = 1;
	function a() {
	    alert(2);
	}
	alert(a);
	var a = 3;
	alert(a);
	a = function a() {
	    alert(4);
	}
	alert(a);

上面的代码在函数声明提升之后与下面的代码是相同的:

        var a;
	function a() {
	    alert(2);
	}
	alert(a);
	a = 1;
	alert(a);
	a = 3;
	alert(a);
	a = function a() {
	    alert(4);
	}
	alert(a);

所以最后的输出依次是function a() { alert(2); } ,1,3,function a() { alert(4); }

再看以下几个例子:

fnc();
function fnc(){
...
}//正常,因为了函数声明提升,函数调用可在函数声明之前
fnc();
var fnc=function(){
    ...
}//报错,变量fnc还未保存对函数的引用,函数调用必须在函数表达式之后。
var fnc=function(){
    ...
}();//函数表达式后面加括号,当javascript引擎解析到此处时能立即调用函数,后()传递参数。
function fnc(){
    ...
}();//报错,javascript引擎执行到此会略过函数声明而执行(),所以会报错。
function(){
    ...
}();//报错

通过上面几个例子,我们对javascript引擎的解析机制有了初步的了解,再看立即执行函数的一些其他写法:

(function(a){
    console.log(a);   //输出123,使用()运算符
})(123);
(function(a){
    console.log(a);   //输出123,使用()运算符
}(123));
!function(a){
    console.log(a);   //输出123,使用!运算符
}(123);
+function(a){
    console.log(a);   //输出123,使用+运算符
}(123);
-function(a){
    console.log(a);   //输出123,使用-运算符
}(123);
var fn=function(a){
    console.log(a);   //输出123,使用=运算符
}(123)

可以看出,在function前面加()、 !、 +、 -等都可以立即执行的效果,那是因为它们都可以将函数声明转换成函数表达式,并且立即执行函数的代码。

加括号是最安全的做法,因为!、 +、 -等运算符还会和函数的返回值进行运算,有时会造成不必要的麻烦。

作用

由于javascript中没用私有作用域的概念,在多人开发的项目上,如果在全局或局部作用域中声明了一些变量,可能会被其他人不小心用同名的变量给覆盖掉。根据javascript函数作用域链的特性,可以使用这种技术可以模仿一个私有作用域,用匿名函数作为一个“容器”,“容器”内部可以访问外部的变量,而外部环境不能访问“容器”内部的变量,所以( function(){…} )()内部定义的变量不会和外部的变量发生冲突,俗称“匿名包裹器”或“命名空间”。

JQuery使用的就是这种方法,将JQuery代码包裹在

	( function (window,undefined){
	…jquery代码…
	} (window)

中,在全局作用域中调用JQuery代码时,可以达到保护JQuery内部变量的作用。

举例说明:

假设需要把当前时间以 "2015/9/22 0:10:24" 的形式赋给某个变量

var currTime = (function(){
    var time = new Date(),
       year = time.getFullYear(),
       month = time.getMonth(),
       date = time.getDate(),
       hour = time.getHours(),
       min = time.getMinutes(),
       sec = time.getSeconds(),
    return year + '/' + month + '/' + date + ' ' + hour + ':' + min + ':' + sec
})()

使用这一大串变量并不会影响到外面的作用域中,所以更优雅。

发表评论

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