在JavaScript中,函數(shù)是個(gè)非常重要的對(duì)象,函數(shù)通常有三種表現(xiàn)形式:函數(shù)聲明,函數(shù)表達(dá)式和函數(shù)構(gòu)造器創(chuàng)建的函數(shù)。 本文中主要看看函數(shù)表達(dá)式及其相關(guān)的知識(shí)點(diǎn)。 函數(shù)表達(dá)式首先,看看函數(shù)表達(dá)式的表現(xiàn)形式,函數(shù)表達(dá)式(Function Expression, FE)有下面四個(gè)特點(diǎn):
下面就通過一些例子來看看函數(shù)表達(dá)式的這四個(gè)特點(diǎn)。 FE特點(diǎn)分析例子一:在下面代碼中,"add"是一個(gè)函數(shù)對(duì)象,"sub"是一個(gè)普通JavaScript變量,但是被賦值了一個(gè)函數(shù)表達(dá)式" function (a, b){ return a - b; } ": function add(a, b){ return a + b; } var sub = function (a, b){ return a - b; } console.log(add(1, 3)); // 4 console.log(sub(5, 1)); // 4 通過這個(gè)例子,可以直觀的看到函數(shù)表達(dá)式的前兩個(gè)特點(diǎn):
例子二:為了解釋函數(shù)表達(dá)式另外兩個(gè)特點(diǎn),繼續(xù)看看下面的例子。 console.log(add(1, 3)); // 4 console.log(sub); // undefined console.log(sub(5, 1)); // Uncaught TypeError: sub is not a function(…) function add(a, b){ return a + b; } var sub = function (a, b){ return a - b; } 在這個(gè)例子中,調(diào)整了代碼的執(zhí)行順序,這次函數(shù)"add"執(zhí)行正常,但是對(duì)函數(shù)表達(dá)式的執(zhí)行失敗了。 對(duì)于這個(gè)例子,可以參考"JavaScript的執(zhí)行上下文"一文中的內(nèi)容,當(dāng)代碼開始執(zhí)行的時(shí)候,可以得到下圖所示的Global VO。 在Global VO中,對(duì)"add"函數(shù)表現(xiàn)為JavaScript的"Hoisting"效果,所以即使在"add"定義之前依然可以使用; 但是對(duì)于"sub"這個(gè)變量,根據(jù)"Execution Context"的初始化過程,"sub"會(huì)被初始化為"undefined",只有執(zhí)行到" var sub = function (a, b){ return a - b; } "語句的時(shí)候,VO中的"sub"才會(huì)被賦值。 通過上面這個(gè)例子,可以看到了函數(shù)表達(dá)式的第四個(gè)特點(diǎn)
例子三:對(duì)上面的例子進(jìn)一步改動(dòng),這次給函數(shù)表達(dá)式加上了一個(gè)名字"_sub",也就是說,這里使用的是一個(gè)命名函數(shù)表達(dá)式。 var sub = function _sub(a, b){ console.log(typeof _sub); return a - b; } console.log(sub(5, 1)); // function // 4 console.log(typeof _sub) // undefined console.log(_sub(5, 1)); // Uncaught ReferenceError: _sub is not defined(…) 根據(jù)這段代碼的運(yùn)行結(jié)果,可以看到"_sub"這個(gè)函數(shù)名,只能在"_sub"這個(gè)函數(shù)內(nèi)部使用;當(dāng)在函數(shù)外部訪問"_sub"的時(shí)候,就是得到"Uncaught ReferenceError: _sub is not defined(…)"錯(cuò)誤。 所以通過這個(gè)可以看到函數(shù)表達(dá)式的第三個(gè)特點(diǎn):
FE的函數(shù)名到了這里,肯定會(huì)有一個(gè)問題,"_sub"不在VO中,那在哪里? 其實(shí)對(duì)于命名函數(shù)表達(dá)式,JavaScript解釋器額外的做了一些事情:
下面是表示這一過程的偽代碼: specialObject = {}; Scope = specialObject + Scope; _sub = new FunctionExpression; _sub.[[Scope]] = Scope; specialObject. _sub = _sub; // {DontDelete}, {ReadOnly} delete Scope[0]; // 從作用域鏈中刪除特殊對(duì)象specialObject 函數(shù)遞歸這一小節(jié)可能有些鉆牛角尖,但是這里想演示遞歸調(diào)用可能出現(xiàn)的問題,以及通過命名函數(shù)表達(dá)式以更安全的方式執(zhí)行遞歸。 下面看一個(gè)求階乘的例子,由于函數(shù)對(duì)象也是可以被改變的,所以可能會(huì)出現(xiàn)下面的情況引起錯(cuò)誤。 function factorial(num){ if (num <= 1){ return 1; } else { return num * factorial(num-1); } } console.log(factorial(5)) // 120 newFunc = factorial factorial = null console.log(newFunc(5)); // Uncaught TypeError: factorial is not a function(…) 這時(shí),可以利用函數(shù)的arguments對(duì)象的callee屬性來解決上面的問題,也就是說在函數(shù)中,總是使用"arguments.callee"來遞歸調(diào)用函數(shù)。 function factorial(num){ if (num <= 1){ return 1; } else { return num * arguments.callee(num-1); } } 但是上面的用法也有些問題,當(dāng)在嚴(yán)格模式的時(shí)候"arguments.callee"就不能正常的工作了。 比較好的解決辦法就是使用命名函數(shù)表達(dá)式,這樣無論"factorial"怎么改變,都不會(huì)影響函數(shù)表達(dá)式" function f(num){…} " 代碼模塊化在JavaScript中,沒有塊作用域,只有函數(shù)作用域,函數(shù)內(nèi)部可以訪問外部的變量和函數(shù),但是函數(shù)內(nèi)部的變量和函數(shù)在函數(shù)外是不能訪問的。 所以,通過函數(shù)(通常直接使用函數(shù)表達(dá)式),可以模塊化JavaScript代碼。 創(chuàng)建模塊為了能夠到達(dá)下面的目的,我們可以通過函數(shù)表達(dá)式來建立模塊。
下面看一個(gè)簡單的例子: var Calc = (function(){ var _a, _b; return{ add: function(){ return _a + _b; }, sub: function(){ return _a - _b; }, set: function(a, b){ _a = a; _b = b; } } }()); Calc.set(10, 4); console.log(Calc.add()); // 14 console.log(Calc.sub()); // 6 代碼中通過匿名函數(shù)表達(dá)式創(chuàng)建了一個(gè)"Calc"模塊,這是一種常用的創(chuàng)建模塊的方式:
除了返回一個(gè)對(duì)象的方式,有的模塊也會(huì)使用另外一種方式,將包含模塊公共接口的對(duì)象作為全局變量的一個(gè)屬性。 這樣在代碼的其他地方,就可以直接通過全局變量的這個(gè)屬性來使用模塊了。 例如下面的例子: (function(){ var _a, _b; var root = this; var _ = { add: function(){ return _a + _b; }, sub: function(){ return _a - _b; }, set: function(a, b){ _a = a; _b = b; } } root._ = _; }.call(this)); _.set(10, 4); console.log(_.add()); // 14 console.log(_.sub()); // 6 立即調(diào)用的函數(shù)表達(dá)式在上面兩個(gè)例子中,都使用了匿名的函數(shù)表達(dá)式,并且都是立即執(zhí)行的。如果去看看JavaScript一些開源庫的代碼,例如JQuery、underscore等等,都會(huì)發(fā)現(xiàn)類似的立即執(zhí)行的匿名函數(shù)代碼。 立即調(diào)用的函數(shù)表達(dá)式通常表現(xiàn)為下面的形式: (function () { /* code */ })(); (function () { /* code */ } ()); 在underscore這個(gè)JavaScript庫中,使用的是下面的方式: (function () { // Establish the root object, `window` in the browser, or `exports` on the server. var root = this; /* code */ } .call(this)); 在這里,underscore模塊直接對(duì)全局變量this進(jìn)行了緩存,方便模塊內(nèi)部使用。 總結(jié)本文簡單介紹了JavaScript中的函數(shù)表達(dá)式,并通過三個(gè)例子解釋了函數(shù)表達(dá)式的四個(gè)特點(diǎn)。
通過函數(shù)表達(dá)式可以方便的建立JavaScript模塊,通過模塊可以實(shí)現(xiàn)下面的效果:
|
|