js 變數聲明 函數聲明 變數賦值的實現機制疑惑?
//先聲明變數再聲明函數
//先聲明函數再聲明變數
var a ;
function a(){
console.log(1);
}
console.log(a);//a為函數
function b(){
console.log(1);
}
var b ;
console.log(b);//b為函數
//這裡可以看出函數的聲明高於變數的聲明
//賦值變數//賦值函數
a = 1;
console.log(a);//a為1
變數標識符與函數標識符指向的是同一個符號表的標識符?
a = function a(){
console.log(a);
}
console.log(a);//a為函數
如果是的話聲明的時候函數聲明標識符優先存儲到符號表中是怎樣的過程?
Javascript里有個神奇的東西叫Hoisting (抱歉不知道中文是怎麼翻譯的). 通俗點講,在運行一個JS文件的時候 以下兩項將被最先執行:
1. function declarations (函數聲明):function foo() {
return true;
}
2. variable declarations (變數聲明):
var foo;
所以,請看下面的代碼:
var boo = "test";
var foo = function() {
console.log("foo");
}
function doo() {
console.log("doo");
}
goo = 1;
var goo;
真正的執行順序如下:
var boo;
var foo;
var goo;
function doo() {
console.log("doo");
}
boo = "test"
goo = "1"
可以看出,即使我們在代碼里:
goo = 1;
var goo;
但其實在運行的時候變數的聲明被提到了前面:
var goo;
...
goo = 1;
還有一個有趣的例子:
console.log(foo);
foo = 1;
運行時候會報錯:
ReferenceError: foo is not defined
但是如果寫成這樣:
console.log(foo);
foo = 1;
var foo;
運行結果為「undefined」。此時的undefined是foo的值,程序本身並沒有錯誤,因為「var foo」這一句被提到了最前面,所以foo這個變數是存在的,但是console.log在對foo賦值之前執行了,真正的順序為:
var foo;
console.log(foo);
foo = 1;
JS引擎解析有一定順序。當執行調用的函數時,在這個函數執行環境中,首先建立arguments,然後是函數的參數,緊接著是函數聲明,然後是變數聲明。最後賦值。但是有一個非常重要的一點。如果在變數聲明的解析過程中,發現了與函數聲明同名的變數,那麼解析器會選擇跳過!這是因為,執行環境建立具體分為幾個階段,在調用階段時,變數聲明的解析過程中,所有變數的值是設定為未定義的,而先解析的函數聲明的名字若與後解析的變數聲明的名字重複的話。會發生覆蓋,導致函數聲明的值變成:undefined。這個跳過也是一個非常重要的機制。在重名發生時,這個機制導致,這個名字會被判給函數而非變數。
本題涉及到「JavaScript Hoisting」(中文:聲明提升)。幾位答主已有解釋,我在這裡再補充完善一些(主要關於 Hoist 的優先順序)。
在JavaScript中,一個變數名進入作用域的方式有4種:
- Language-defined:所有的作用域默認都會給出 this 和 arguments 兩個變數名;
- Formal parameters(形參):函數有形參,形參會添加到函數的作用域中;
- Function declarations(函數聲明):如 function foo() {};
- Variable declarations(變數聲明):如 var foo。
函數聲明和變數聲明總是會被移動(即hoist)到它們所在的作用域的頂部(這對你是透明的)。至於Language-defined和形參,顯然,它們已經在頂部了。
而變數的解析順序(優先順序),與變數進入作用域的4種方式的順序一致。下面用一段代碼解釋變數提升及優先順序:function testOrder(arg) {
console.log(arg); // arg是形參,不會被重新定義
console.log(a); // 因為函數聲明比變數聲明優先順序高,所以這裡a是函數
var arg = "hello"; // var arg;變數聲明被忽略, arg = "hello"被執行
var a = 10; // var a;被忽視; a = 10被執行,a變成number
function a() {
console.log("fun");
} // 被提升到作用域頂部
console.log(a); // 輸出10
console.log(arg); // 輸出hello
};
testOrder("hi");
/* 輸出:
hi
function a() {
console.log("fun");
}
10
hello
*/
希望答案對透徹理解變數提升有所幫助。
搞懂 hoisting 的機制其實就能迎刃而解了:只提升聲明部分。1.我是變數聲明,我會被提升在作用域頂端!
var a;
2.我是變數定義,我的聲明部分會被提升,賦值部分不會被提升!
var b = "test";
3.我是函數定義,或者叫我函數表達式。其實我就是變數定義,只不過恰好被賦值的類型是函數,所以也只提升變數名,不提升函數值!
var c = function() {
console.log("test");
}
4.我是函數聲明,所以我全部被提升了,包括函數名和函數體。另外,我的優先順序比變數聲明要高,名字和我相同的變數聲明會被忽略!
function d() {
console.log("test");
}
變數的問題,莫過於聲明和賦值兩個步驟,而這兩個步驟是分開的。
函數聲明被提升時,聲明和賦值兩個步驟都會被提升,而普通變數卻只能提升聲明步驟,而不能提升賦值步驟。
變數被提升過後,先對提升上來的所有對象統一執行一遍聲明步驟,然後再對變數執行一次賦值步驟。而執行賦值步驟時,會優先執行函數變數的賦值步驟,再執行普通變數的賦值步驟。
當你明白這三點後,一切都豁然開朗了。
首先來看一個DEMO:
(function(){
function a(){};
var a;
alert(typeof a); //function
})();
先提升兩個a,然後執行函數的賦值步驟,a沒有被賦值,故結果為function
再看一個:
(function(){
alert(typeof a);//function
function a(){};
var a = 1;
})();
先提升兩個a,再執行函數的賦值步驟,因為在alert語句執行以前,還未執行a = 1的賦值步驟,函數不會被覆蓋,故為function
來個最有說服力的:
(function(){
var a = 1;
function a(){};
alert(typeof a); //number
})();
在alert語句執行之前,a = 1步驟和函數賦值步驟均已執行,而且函數還在a = 1賦值語句之後,但是仍然輸出number,就是因為函數的賦值步驟會先於a = 1的賦值步驟,函數被覆蓋,故輸出number。
一切關乎變數提升的代碼,用這三點沒有解釋不清楚的。而至於你說的「為什麼聲明函數不能被外部調用」,這是作用域的問題,跟變數提升,其實並無直接關係。
以上。SF的也是我。
歡迎拍磚...培訓班裡貌似管這個叫 預解釋
最近在寫一個腳本語言解釋器,js應該是先把函數和變數給提取出去了,尋找變數時先在函數域中尋找,找到就不管變數啦~
推薦閱讀:
※穩妥構造函數模式和工廠模式創建對象有什麼區別?
※Vue.js中如何動態的載入、卸載組件?
※Google Polymer是前端組件化的未來!那對於現在當下,又該採用什麼技術實現組件化呢?AngularJs可以勝任嗎?
※如何評價 TypeScript?
※如何在 React 中運用 CSS?
TAG:JavaScript | V8 |