缘起
javascript和其他编程语言相比比较随意,所以javascript代码中充满各种奇葩的写法,有时雾里看花,当然,能理解各型各色的写法也是对 javascript语言特性更进一步的深入理解,那么他有几种写法呢?
( function(){…} )()
或者
( function (){…} () )
首先要明白两个知识点
js中函数是引用类型; 函数一般执行方式:函数名+();
下面的例子帮你理解引用类型
var a = function(x,y){ console.log(x + y); }; var b = a; a(1,2); b(1,2); //b,a指向同一个函数对象 //b重新赋值 b = function(x,y){ console.log(x - y); } a(1,2); b(1,2);
函数的几种定义方式
js函数有普通函数、构造函数、匿名函数,定义方式有三种,即函数声明方式(function declaration, abbreviation as FD)、函数表达式方式(function expression, abbreviation as FE)、函数对象方式(对象的方式定义函数,前面参数为函数的参数,最后为函数体。但是需要解析传入的字符串参数,导致两次解析,所以不推荐这种方式来定义函数)
1、函数声明方式
//不会报错,但是javascript引擎只解析函数声明,忽略后面的括号,函数声明不会被调用 function fnName(){ alert('Hello World'); }(); //可以执行 alert(sum(1,2)); function sum(x,y){ return x + y; }
2、函数表达式方法
//这段代码会报错 alert(sum(1,2)); var sum = function (x,y){ return x + y; } //函数表达式后面加括号,当javascript引擎解析到此处时能立即调用函数 var show=function(){ alert('Hello World'); }();
3、函数对象方法
var sum = new Function('value1', 'value2', 'return value1 + value2');
在写递归的时候可以这样写
//如果直接用sum(x-1) + sum(x-2),如果sum被改名,或者重新赋值,产生bug var sum = function fSum(x){ if(x<=2) return 1; else return fSum(x-1) + fSum(x-2); }; alert(sum(5));
4、匿名函数
使用function关键字声明一个函数,但未给函数命名,所以叫匿名函数,匿名函数属于函数表达式,匿名函数有很多作用,赋予一个变量则创建函数,赋予一个事件则成为事件处理程序或创建闭包等等
function () {}
区别
1、FD是在构建函数的Execution Context时会被计算并作为Activation Object的一个属性被引用,因此就出现declaration hoisting的现象。而FE则是在函数的Runtime中才被计算,而且不会作为Activation Object的一个属性被引用,也就是说FD会被解析器通过函数声明提升的过程即function declaration hoisting置于原代码数的顶部,所以即使在函数前调用该函数也可以正常使用
2、而函数表达式方式除了不能在声明前调用外,与函数声明方式一样
3、函数对象方法可以直观地理解“函数是对象,函数名是指针”这个概念,但是它会造成解析器两次解析,一次是普通的ECMAScript代码,一次是解析传入 Function构造函数里的字符串,会影响js引擎性能
4、函数表达式后面可以加括号立即调用该函数,函数声明不可以
立即执行函数表达式
我们在使用JavaScript的时候经常会看见类似如下的函数调用方式
(function(){ console.log("test"); })();
或者
(function(){ console.log("test"); }());
比如jQuery
(function( window, undefined ) { // code here }) ( window );
这种写法有两种称呼
「自执行匿名函数」(self-executing anonymous function), 「立即执行函数表达式」(Immediately-Invoked Function Expression,以下简称IIFE)
还有一些奇葩的定义方式
// 如果本身就是expression,那么根本不需要做任何处理 var i = function(){ return 10; }(); true && function(){ /* code */ }(); 0, function(){ /* code */ }(); // 如果你不在乎返回值,可以这么做 !function(){ /* code */ }(); ~function(){ /* code */ }(); -function(){ /* code */ }(); +function(){ /* code */ }(); // 还有更奇葩的方式,但是不知道性能如何,来自 new function(){ /* code */ } new function(){ /* code */ }()
为什么要用立即执行函数表达式
1、模拟块作用域
众所周知,JavaScript没有C或Java中的块作用域(block),只有函数作用域,在同时调用多个库的情况下,很容易造成对象或者变量的覆盖,比如
liba.js
var num = 1; // code....
libb.js
var num = 2; // code....
如果在页面中同时引用liba.js
和liba.js
两个库,必然导致num
变量被覆盖,为了解决这个问题,可以通过IIFE来解决:
liba.js
(function(){ var num = 1; // code.... })();
libb.js
(function(){ var num = 2; // code.... })();
2、解决闭包冲突
闭包(closure)是JavaScript的一个语言特性,简单来说就是在函数内部所定义的函数可以持有外层函数的执行环境,即使在外层函数已经执行完毕的情况下,在这里就不详细介绍了,感兴趣的可以自行Google。我们这里只举一个由闭包引起的最常见的问题
var f1 = function() { var res = []; var fun = null; for(var i = 0; i < 10; i++) { fun = function() { console.log(i);};//产生闭包 res.push(fun); } return res; } // 会输出10个10,而不是预期的0 1 2 3 4 5 6 7 8 9 var res = f1(); for(var i = 0; i < res.length; i++) { res[i](); }
修改成:
var f1 = function() { var res = []; for(var i = 0; i < 10; i++) { // 添加一个IIFE (function(index) { fun = function() {console.log(index);}; res.push(fun); })(i); } return res; } // 输出结果为0 1 2 3 4 5 6 7 8 9 var res = f1(); for(var i = 0; i < res.length; i++) { res[i](); }
可以参考http://segmentfault.com/q/1010000003490094
3、模拟单例
在JavaScript的OOP中,我们可以通过IIFE来实现,如下
var counter = (function(){ var i = 0; return { get: function(){ return i; }, set: function( val ){ i = val; }, increment: function() { return ++i; } }; }()); counter.get(); // 0 counter.set( 3 ); counter.increment(); // 4 counter.increment(); // 5
参考:
http://benalman.com/news/2010/11/immediately-invoked-function-expression/#iife
http://blog.coolaj86.com/articles/how-and-why-auto-executing-function.html
http://stackoverflow.com/questions/592396/what-is-the-purpose-of-a-self-executing-function-in-javascript
http://www.cnblogs.com/TomXu/archive/2011/12/31/2289423.html
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function