1 回答

TA貢獻1866條經(jīng)驗 獲得超5個贊
JS中對于函數(shù)的聲明語法在標(biāo)準(zhǔn)中就有兩種不同的語法樣式,其一稱為函數(shù)聲明
樣式,另一個是函數(shù)表達式
樣式,在標(biāo)準(zhǔn)中也寫得很簡略,像下面這樣,上面的是函數(shù)聲明
樣式,下面則是函數(shù)表達式
,opt
記號代表的是可選的,也就是可有可無的意思:
FunctionDeclaration :function Identifier ( FormalParameterList opt ){ FunctionBody } FunctionExpression :function Identifier opt ( FormalParameterList opt ){ FunctionBody }
所以在函數(shù)表達式
這種樣式,函數(shù)的識別名是可以不需要有的,但它因為是個表達式
,可以賦給另一個變量當(dāng)值來看待,所以才會有像var f = function(){ return 23 }
的這種寫法,真正在函數(shù)調(diào)用時是用前面的變量識別名稱f,而不是額外的那個可有可無的函數(shù)識別名稱。如果充分區(qū)分出函數(shù)聲明
與函數(shù)表達式
的兩種不同樣式,在文中最好以函式創(chuàng)建
這字詞來說明函數(shù)聲明或函數(shù)表達式的這兩種語句較佳。
那要分辨何者是函數(shù)聲明
又何者是函式表達式
?最簡單的方式就是看語句的開頭,在單一個語句中以function
作為該語句開頭的函數(shù),應(yīng)該就是函數(shù)聲明
,函式表達式
不會以function開頭。所以下面的幾例都是函式表達式
而不是函式聲明
樣式:
var a = function() { return 3; } var a = function bar() { return 3; } (function sayHello() { alert("hello!"); })();
而為何會有兩種不同的函數(shù)語法樣式,主要是這兩種樣式的用處不同,這有很多不同的應(yīng)用情況。
最明顯的例子是函數(shù)聲明
有特殊的提升(hoisting)特性,在同一作用域中的函數(shù)聲明
會先被提升到此作用域的最上面,所以函數(shù)聲明
可以寫在代碼文檔的后面,但可以在代碼文檔的上面位置使用。此外,函數(shù)聲明
只能在函數(shù)中塊級或整個應(yīng)用的全局區(qū)中使用,它沒辦法在其他的塊級中使用,例如像if、for等的花括號({})中。而在某些情況下,當(dāng)需要把函數(shù)整體塊級作為一種值,用來當(dāng)其他函數(shù)的傳參或返回時,例如回調(diào)函數(shù)的語法結(jié)構(gòu),就是要使用函式表達式
的樣式才可以達到。當(dāng)然,基本上這兩者看起來好像都是長得一樣,實際上在執(zhí)行階段的運作并不相同。
帶有名稱的函數(shù)表達式,有個專用語稱之為"具名函數(shù)表達式"(Named function expressions,NFE),這個函數(shù)的識別名,它的作用域到底是在什么地方,答案是在函數(shù)的主體(FunctionBody)內(nèi)部。原因當(dāng)然它只是個原本就可有可無的"代理"函數(shù)名,真正的這函數(shù)識別名稱是被賦值的那個變量識別名。
正常情況下,你只能在函數(shù)表述式中的主體中使用這個"代理函數(shù)名",這也是符合標(biāo)準(zhǔn)的規(guī)定,如下面的例子:
var f = function foo(){ return typeof foo; };typeof foo; // "undefined"f(); // "function"
那么又為何要使用這個"代理函數(shù)名",不是可有可無的嗎?
因為這個名稱在調(diào)試時,可以明確地在呼叫堆疊中看到,如果是不加這名稱,也就是"匿名函數(shù)表達式"在調(diào)試時是看不準(zhǔn)是呼叫什么的。這使得調(diào)試時多了一些便利,所以它會被用在這個情況下。
比較特別的情況是在IE8以前的版本中,它里面的JS引擎并不是現(xiàn)在的標(biāo)準(zhǔn)ECMAScript規(guī)范,而是JScript 5.8。IE8并沒有設(shè)計這個封閉作用域,來界定出函數(shù)表達式的作用域,而且,在IE8中認(rèn)為這種"具名函數(shù)表達式",相等于函數(shù)聲明
。
以上的資料主要參考Named function expressions demystified與Function Declarations vs. Function Expressions
后面針對題目本身提供一些解答:
上面代碼中test函數(shù)的參數(shù)foo函數(shù)是函數(shù)表達式對吧?也算是函數(shù)聲明吧?
是函數(shù)表達式
而不是函數(shù)聲明,這是依照ECMAScript標(biāo)準(zhǔn)的說明。只是它有帶函數(shù)名,是上面說的"具名函數(shù)表達式"(NFE)。
代碼中的foo函數(shù)到底存在于哪個作用域里面呢?
在函數(shù)表達式
的函數(shù)定義塊級作用域里,也就是函數(shù)的主體(FunctionBody)內(nèi)部,上面有例子。但這有例外情況,在IE8以前的IE瀏覽器,這代碼應(yīng)該是會如同函數(shù)聲明樣式一樣的執(zhí)行結(jié)果。
另一個問題忘了說明,補充一下。
全域中有一個bar=99變量,在test函數(shù)中的區(qū)塊中也有聲明一個bar=1,那么如果在傳參中是以函數(shù)類型傳入test函數(shù),有存取bar如console.log(bar)
,為何答案是99而不是1?
主要是因為傳參的作用域是在全局作用域,并非函數(shù)的作用域之中。也就是說像下面的代碼:
test(function foo(){ console.log(bar);});
相等于下面的代碼:
var f = function foo(){ console.log(bar); }test(f);
也就是說在本例子中,test的傳參在傳入后,然后被調(diào)用,里面要作什么事,已經(jīng)可以決定了結(jié)果,也就是相當(dāng)于:
var bar = 99;var f = function foo(){ console.log(99); // bar在全局中已賦值為99} test(f);
上面說的概念雖然會有點怪異,在這個例子你可以把f這個函數(shù),當(dāng)成只是稍晚些打印到控制臺的一種變量而已。
因為很特別的是,這個f函數(shù),它并沒有傳參,也沒有返回,單純只是打印bar變量,這個bar變量如果要能有值,只能從函數(shù)所能存取得到的作用域而來,而且是以函數(shù)聲明的主體上下文作用域為主。
例子中的傳參的作用域是位于全局作用域,所以只能存取得到全局的那個var bar = 99
變量。
會讓人誤解的是這個test函數(shù)中的寫法:
function test(fn){ var bar = 1; fn(); }
有可能會直覺得認(rèn)定,應(yīng)該是相當(dāng)于下面的寫法:
function test(function foo(){ console.log(bar); }){ var bar = 1; foo(); }
所以它的結(jié)果應(yīng)該是:
console.log(1)
但結(jié)果不是上面那樣,實際上這相當(dāng)于下面的例子,因為f函數(shù)不需要傳參,所以相等于直接在test函數(shù)中調(diào)用而已:
var f = function foo(){ console.log(99); // bar在全局中已賦值為99}function test(){ var bar = 1; f(); }
除非f函數(shù)的內(nèi)容,是定義在test函數(shù)的區(qū)塊之中時,才有可能得到1的打印結(jié)果,像下面這樣:
function test(){ var bar = 1; var f = function foo(){ console.log(bar); // bar相當(dāng)于1 } f(); }
JS中的作用域,是屬于詞法上的作用域,也就是"靜態(tài)"的作用域,作用域中的變量綁定,在執(zhí)行前就已經(jīng)決定好了,按定義是在編譯期間。在JavaScript: The Definitive Guide這本書中對作用域的一句說明如下:
函數(shù)在調(diào)用(執(zhí)行)時,使用它們被定義(聲明)時被影響的作用域鏈
這與題中所引用的 你不知道的JavaScript 書中的說明是一樣的:
無論函數(shù)在哪里被調(diào)用,也無論它如何被調(diào)用,它的詞法作用域都只由函數(shù)被聲明時所處的位置決定。
添加回答
舉報