JS 變數提升 函數提升
1 function log(str) { 2 // 本篇文章所有的列印都將調用此方法 3 console.log(str); 4 }
函數聲明和變數聲明總是會被解釋器悄悄地被“提升”到方法體的最頂部
變數聲明、命名、提升
在JS中, 變數有4種基本方式進入作用域:
- 語言內置: 所有的作用域里都有this和arguments;(需要註意的是arguments在全局作用域是不可見的)
- 形式參數: 函數的形式參數會作為函數體作用域的一部分;
- 函數聲明: 像這種形式: function foo() {};
- 變數聲明: 像這樣: var foo;
變數提升
function test1() { a = 5; log(a); log(window.a); var a = 10; log(a); } test1();
依次會輸出 5 、undefined 、10 因為在解析時候是等價於
1 var a; 2 a=5; 3 log(a); 4 log(window.a); 5 a=10; 6 log(a);
接著看另外一個例子:
1 function test2() { 2 var a = 1; 3 var b = 2; 4 var c = 3; 5 } 6 /*test2中的語句,是這樣被執行的 這個時候就把變數提升了 7
8 function test2(){ 9 var a,b,c; 10 var a = 1; 11 var b = 2; 12 var c = 3; 13 } 14 */
只有函數級作用域,if語句不會有:test3():
function test3(){
var a = 1;
log(a); // 1
if (true) {
var a = 2;
log(a); //2
}
log(a); // 2
}
函數的提升
我們寫JS的時候,通常會有兩種寫法:
- 函數表達式 var fn=function fn(){}
- 函數聲明方式 function fn(){}
我們需要重點註意的是,只有函數聲明形式才能被提升。
變數賦值並沒有被提升,只是聲明被提升了。但是,函數的聲明有點不一樣,函數體也會一同被提升
1 function test3() { 2 fn(); 3 function fn() { 4 log("我來自fn"); 5 } 6 } 7 test3(); 8 function test4() { 9 fn(); // fn is not a function 10 var fn = function fn() { 11 alert("我來自 fn test4"); 12 } 13 } 14 test4();
函數表達式需要註意的
- 在function內部,fn完全等於fn1
- 在function外面,fn1則是 not defined
1 function test5() { 2 var fn = function fn1() { 3 log(fn === fn1); // true 4 log(fn == fn1); // true 5 } 6 fn(); 7 log(fn === fn1); // fn1 is not defined 8 log(fn == fn1); // fn1 is not defined 9 } 10 test5();
!相容
// b();
// var a = function b() {alert('this is b')};
// 則ie下是可以執行b的. 說明不同瀏覽器在處理函數表達式細節上是有差別的.
補充一點函數表達式
定義裡面的指定的函數名是不是被提升的
1 function text7() { 2 a(); // TypeError "a is not a function" 3 b(); 4 c(); // TypeError "c is not a function" 5 d(); // ReferenceError "d is not defined" 6 7 var a = function() {}; // a指向匿名函數 8 function b() {}; // 函數聲明 9 var c = function d() {}; // 命名函數,只有c被提升,d不會被提升。 10 11 a(); 13 b(); 14 c(); 15 d(); // ReferenceError "d is not defined" 16 } 17 text7();
大家先看下麵一段代碼test6,思考一下會列印什麼?
1 function text6() { 2 var a = 1; 3 function b() { 4 a = 10; 5 return; 6 function a() {} 7 } 8 b(); 9 log(a); // ? 10 } 11 text6();
||
||
||
|| 輸出在下麵
||
||
||
||
||
||
what? 什麼鬼?為什麼是1?
這裡需要註意的是,在function b()中,
var = a // function 類型的
a=10; // 重新把10複製給a, 此時的a是function b()中的內部變數
return;
function a() {} // 不會被執行
所以,外面輸出的a 依舊是最開始定義的全局變數
函數的聲明比變數的聲明的優先順序要高
1 function text6() { 2 function a() {} 3 var a; 4 log(a); //列印出a的函數體 5 6 var b; 7 function b() {} 8 log(b); //列印出b的函數體 9 10 // !註意看,一旦變數被賦值後,將會輸出變數 11 var c = 12 12 function c() {} 13 log(c); //12 14 15 function d() {} 16 var d = 12 17 log(d); //12 18 } 19 text6();
變數解析的順序
一般情況下,會按照最開始說的四種方式依次解析
- 語言內置:
- 形式參數:
- 函數聲明:
- 變數聲明:
也有例外:
- 內置的名稱arguments表現得很奇怪,看起來應該是聲明在形參之後,但是卻在聲明之前。這是說,如果形參裡面有arguments,它會比內置的那個優先順序高。所以儘可能不要在形參裡面使用arguments;
- 在任何地方定義this變數都會出語法錯誤
- 如果多個形式參數擁有相同的名稱,最後的那個優先順序高,即便是實際運行的時候它的值是undefined;
CAO!這麼多坑,以後腫麽寫代碼?
用var定義變數。對於一個名稱,在一個作用域裡面永遠只有一次var聲明。這樣就不會遇到作用域和變數提升問題。
ECMAScript參考文檔關於作用域和變數提升的部分:
如果變數在函數體類聲明,則它是函數作用域。否則,它是全局作用域(作為global的屬性)。變數將會在執行進入作用域的時候被創建。塊(比如if(){})不會定義新的作用域,只有函數聲明和全局性質的代碼(單個JS文件)才會創造新的作用域。變數在創建的時候會被初始化為undefined。如果變數聲明語句裡面帶有賦值操作,則賦值操作只有被執行到的時候才會發生,而不是創建的時候。
最後,
由於時間倉促,demo有很多不足之處,多諒解。