JavaScript函數的多種定義方法
緣起
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://www.cnblogs.com/TomXu/archive/2011/12/31/2289423.html
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function
- 上一篇 ?PHP Array 數組詳細介紹
- 下一篇 ?獲取HTML模版字符串