亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

JavaScript中函數作為另一個函數的參數的時候它存在于哪個作用域

JavaScript中函數作為另一個函數的參數的時候它存在于哪個作用域

BIG陽 2018-10-19 10:15:06
一直對函數作為參數被傳遞進另外一個函數理解的不是很清除。先看下這段代碼吧:function test(fn){    var bar = 1;    fn();}var bar = 99;test(function foo(){    console.log(bar);});console.log(foo);先說下結果為99和foo is not defined。在《你不知道的JavaScript》一書中有這么一句話:無論函數在哪里被調用,也無論它如何被調用,它的詞法作用域都只由函數被聲明時所處的位置決定。所以我的問題是這樣的:上面代碼中test函數的參數foo函數是函數表達式對吧?也算是函數聲明吧?代碼中的foo函數到底存在于哪個作用域里面呢?我最開始以為console.log(bar)的結果是99,說明了這個foo函數存在于全局作用域內而不是test函數作用域內(如果在test函數的作用域內結果就該為1了吧),可是最后的console.log(foo)的結果又是foo is not defined。。困惑了很久。。望解答~
查看完整描述

1 回答

?
心有法竹

TA貢獻1866條經驗 獲得超5個贊

JS中對于函數的聲明語法在標準中就有兩種不同的語法樣式,其一稱為函數聲明樣式,另一個是函數表達式樣式,在標準中也寫得很簡略,像下面這樣,上面的是函數聲明樣式,下面則是函數表達式opt記號代表的是可選的,也就是可有可無的意思:

FunctionDeclaration :function Identifier ( FormalParameterList opt ){ FunctionBody }

FunctionExpression :function Identifier opt ( FormalParameterList opt ){ FunctionBody }

所以在函數表達式這種樣式,函數的識別名是可以不需要有的,但它因為是個表達式,可以賦給另一個變量當值來看待,所以才會有像var f = function(){ return 23 }的這種寫法,真正在函數調用時是用前面的變量識別名稱f,而不是額外的那個可有可無的函數識別名稱。如果充分區分出函數聲明函數表達式的兩種不同樣式,在文中最好以函式創建這字詞來說明函數聲明或函數表達式的這兩種語句較佳。

那要分辨何者是函數聲明又何者是函式表達式?最簡單的方式就是看語句的開頭,在單一個語句中以function作為該語句開頭的函數,應該就是函數聲明,函式表達式不會以function開頭。所以下面的幾例都是函式表達式而不是函式聲明樣式:

var a = function() {    return 3;
} 
var a = function bar() {    return 3;
}
 
(function sayHello() {
    alert("hello!");
})();

而為何會有兩種不同的函數語法樣式,主要是這兩種樣式的用處不同,這有很多不同的應用情況。

最明顯的例子是函數聲明有特殊的提升(hoisting)特性,在同一作用域中的函數聲明會先被提升到此作用域的最上面,所以函數聲明可以寫在代碼文檔的后面,但可以在代碼文檔的上面位置使用。此外,函數聲明只能在函數中塊級或整個應用的全局區中使用,它沒辦法在其他的塊級中使用,例如像if、for等的花括號({})中。而在某些情況下,當需要把函數整體塊級作為一種值,用來當其他函數的傳參或返回時,例如回調函數的語法結構,就是要使用函式表達式的樣式才可以達到。當然,基本上這兩者看起來好像都是長得一樣,實際上在執行階段的運作并不相同。

帶有名稱的函數表達式,有個專用語稱之為"具名函數表達式"(Named function expressions,NFE),這個函數的識別名,它的作用域到底是在什么地方,答案是在函數的主體(FunctionBody)內部。原因當然它只是個原本就可有可無的"代理"函數名,真正的這函數識別名稱是被賦值的那個變量識別名。

正常情況下,你只能在函數表述式中的主體中使用這個"代理函數名",這也是符合標準的規定,如下面的例子:

var f = function foo(){  return typeof foo; 
};typeof foo; // "undefined"f(); // "function"

那么又為何要使用這個"代理函數名",不是可有可無的嗎?

因為這個名稱在調試時,可以明確地在呼叫堆疊中看到,如果是不加這名稱,也就是"匿名函數表達式"在調試時是看不準是呼叫什么的。這使得調試時多了一些便利,所以它會被用在這個情況下。

比較特別的情況是在IE8以前的版本中,它里面的JS引擎并不是現在的標準ECMAScript規范,而是JScript 5.8。IE8并沒有設計這個封閉作用域,來界定出函數表達式的作用域,而且,在IE8中認為這種"具名函數表達式",相等于函數聲明

以上的資料主要參考Named function expressions demystifiedFunction Declarations vs. Function Expressions


后面針對題目本身提供一些解答:

上面代碼中test函數的參數foo函數是函數表達式對吧?也算是函數聲明吧?

函數表達式而不是函數聲明,這是依照ECMAScript標準的說明。只是它有帶函數名,是上面說的"具名函數表達式"(NFE)。

代碼中的foo函數到底存在于哪個作用域里面呢?

函數表達式的函數定義塊級作用域里,也就是函數的主體(FunctionBody)內部,上面有例子。但這有例外情況,在IE8以前的IE瀏覽器,這代碼應該是會如同函數聲明樣式一樣的執行結果。


另一個問題忘了說明,補充一下。

全域中有一個bar=99變量,在test函數中的區塊中也有聲明一個bar=1,那么如果在傳參中是以函數類型傳入test函數,有存取bar如console.log(bar),為何答案是99而不是1?

主要是因為傳參的作用域是在全局作用域,并非函數的作用域之中。也就是說像下面的代碼:

test(function foo(){
    console.log(bar);});

相等于下面的代碼:

var f = function foo(){
    console.log(bar);
}test(f);

也就是說在本例子中,test的傳參在傳入后,然后被調用,里面要作什么事,已經可以決定了結果,也就是相當于:

var bar = 99;var f = function foo(){    console.log(99); // bar在全局中已賦值為99}

test(f);

上面說的概念雖然會有點怪異,在這個例子你可以把f這個函數,當成只是稍晚些打印到控制臺的一種變量而已。

因為很特別的是,這個f函數,它并沒有傳參,也沒有返回,單純只是打印bar變量,這個bar變量如果要能有值,只能從函數所能存取得到的作用域而來,而且是以函數聲明的主體上下文作用域為主。

例子中的傳參的作用域是位于全局作用域,所以只能存取得到全局的那個var bar = 99變量。

會讓人誤解的是這個test函數中的寫法:

function test(fn){    var bar = 1;
    fn();
}

有可能會直覺得認定,應該是相當于下面的寫法:

function test(function foo(){
    console.log(bar); 
}){    var bar = 1;
    foo();
}

所以它的結果應該是:

console.log(1)

但結果不是上面那樣,實際上這相當于下面的例子,因為f函數不需要傳參,所以相等于直接在test函數中調用而已:

var f = function foo(){    console.log(99); // bar在全局中已賦值為99}function test(){    var bar = 1;
    f();
}

除非f函數的內容,是定義在test函數的區塊之中時,才有可能得到1的打印結果,像下面這樣:

function test(){    var bar = 1;    var f = function foo(){      console.log(bar); // bar相當于1
    }
    f();
}

JS中的作用域,是屬于詞法上的作用域,也就是"靜態"的作用域,作用域中的變量綁定,在執行前就已經決定好了,按定義是在編譯期間。在JavaScript: The Definitive Guide這本書中對作用域的一句說明如下:

函數在調用(執行)時,使用它們被定義(聲明)時被影響的作用域鏈

這與題中所引用的 你不知道的JavaScript 書中的說明是一樣的:

無論函數在哪里被調用,也無論它如何被調用,它的詞法作用域都只由函數被聲明時所處的位置決定。


查看完整回答
反對 回復 2018-11-15
  • 1 回答
  • 0 關注
  • 631 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號