首頁  >  文章  >  web前端  >  深入理解Javascript作用域與變數提升_javascript技巧

深入理解Javascript作用域與變數提升_javascript技巧

WBOY
WBOY原創
2016-05-16 17:10:181057瀏覽

下面的程式是什麼結果?

複製程式碼 程式碼如下:

var foo = 1;


var foo = 1;
function bar(bar() {
 if (!foo) {
  var foo = 10;
 }
 alert(foo);
}
bar();


結果是10; 那下面這個呢?
複製程式碼


程式碼如下:


var a = 1;
function b> {
 a = 10;
 return;
 function a() {}
}
b();alert(a);

alert(a);


結果是1. 嚇你一跳吧?發生了什麼事情?這可能是陌生的,危險的,迷惑的,同樣事實上也是非常有用和印象深刻的javascript語言特性。對於這種表現行為,我不知道有沒有一個標準的稱呼,但是我喜歡這個術語:「Hoisting (變數提升)」。這篇文章將對這個機製做一個拋磚引玉式的講解,但是,首先讓我們對javascript的作用域有一些必要的理解。 Javascript的作用域 對Javascript初學者來說,一個最困惑的地方就是作用域;事實上,不光是初學者。我就見過一些有經驗的javascript程式設計師,但他們對scope理解不深。 javascript作用域之所以迷惑,是因為它程式語法本身長的像C家族的語言,像下面的C程式:



複製程式碼


程式碼如下:


#include
int main() {
 int x = 1;
 printf("%d, ", x ); // 1
 if (1) {
  int x = 2;
  printf("%d, ", x); // 2 } printf("%dn ", x); // 1}輸出結果是1 2 1,這是因為C家族的語言有區塊作用域,當程式控制走進一個區塊,例如if塊,只作用於該塊的變數可以被聲明,而不會影響塊外面的作用域。但是在Javascript裡面,這樣不行。看看下面的程式碼:



複製程式碼


程式碼如下:

var x = 1;
console.log(x); // 1if (true) {

 var x = 2;
 console.log(x); // 2

}console. log(x); // 2結果會是1 2 2。因為javascript是函數作用域。這是和c家族語言最大的不同。該程式裡面的if並不會建立新的作用域。 對於許多C,c ,java程式設計師來說,這不是他們期望和歡迎的。幸運的是,基於javascript函數的靈活性,這裡有可變通的地方。如果你必須建立暫時的作用域,可以像下面這樣:



複製程式碼


程式碼如下:


function foo() {
 var x = 1;
 if (x) {
  (function () {
   var x = 2; }());

 }

 // x is still 1.

}

這個方法很靈活,可以用在任何你想創造臨時的作用域的地方。不光是塊內。但是,我強烈推薦你花點時間理解javascript的作用域。它很有用,是我最喜歡的javascript特性之一。如果你了解作用域,那麼變數提升就對你顯得更有意義。

變數聲明,命名,和提升

在javascript,變數有4種基本方式進入作用域:

•1 語言內建:所有的作用域裡都有this和arguments;(譯者註:經過測試arguments在全域作用域是不可見的)


•2 形式參數:函數的形式參數會作為函數體作用域的一部份;

•3 函數宣告:像這種形式:function foo(){}; •4 變數宣告:像這樣:var foo; 函數宣告和變數宣告總是會被解釋者悄悄地被「提升」到方法體的最頂端。這個意思是,像是下面的程式碼:


複製程式碼


程式碼如下:
function foo( ) { bar(); var x = 1;}
實際上會被解釋成:
複製程式碼 程式碼如下:


程式碼如下:


function foo () {
 var x;
 bar();
 x = 1;
}複製程式碼


程式碼如下:


function foo() {
 if (false) {
  var x = 1;
 }
 return;
 var y = 1;
}
} var x, y;
 if (false) {
  x = 1;
 }
 return;
 y = 1;
 return;
 y = 1;>}
}



}


請注意,變數賦值並沒有被提升,只是聲明被提升了。但是,函數的宣告有點不一樣,函數體也會一同被提升。但請注意,函數的宣告有兩種方式:




複製程式碼

程式碼如下:

function test() {

 foo(); // TypeError "foo is not a function"

 bar(); // "this will run!"
 var foo = function () { // 變數指向函數表達式


這個例子裡面,只有函數式的宣告才會連同函數體一起被提升。 foo的宣告會被提升,但是它所指向的函數體只會在執行的時候才被賦值。

上面的東西涵蓋了提升的一些基本知識,它們看起來也沒有那麼迷惑。但是,在一些特殊場景,還是有一定的複雜度的。

變數解析順序
最需要牢記在心的是變數解析順序。記得我前面給的4種被命名進入作用域的方式嗎?變數解析的順序就是我列出來的順序。



複製程式碼


程式碼如下:


<script><BR>function(){function<BR>}<BR>var a;</script>

alert(a);//印出a的函數體



<script></script>

var a;

function a(){ 

}
alert(a);//印出a的函數體

//但是要注意區分和下面兩個寫法的差別:
<script>var a=1;<P>function a(){ }<P>alert(a);//印出1 </script>

<script><P>function a(){ }<P> <BR>var a=1;<div class="codetitle"> <span>alert(a);//印出1<a style="CURSOR: pointer" data="50725" class="copybut" id="copybut50725" onclick="doCopy('code50725')"></script>這裡有3個例外: 1 內建的名稱arguments表現得很奇怪,他看起來應該是聲明在函數形式參數之後,但是卻在函數宣告之前。這是說,如果形參裡面有arguments,它會比內建的那個有優先權。這是很不好的特性,所以要杜絕在形參裡面使用arguments;

2 在任何地方定義this變數都會出語法錯誤,這是個好特性;

3 如果多個形式參數擁有相同的名稱,最後的那個具有優先權,即便實際運行的時候它的值是undefined;
命名函數


你可以給一個函數一個名字。如果這樣的話,它就不是一個函數聲明,同時,函數體定義裡面的指定的函數名( 如果有的話,如下面的spam, 譯者註)將不會被提升, 而是被忽略。這裡一些程式碼幫助你理解:



複製程式碼

程式碼如下:foo(); / TypeError "foo is not a function"bar(); // validbaz(); // TypeError "baz is not a function"spam(); // ReferenceError "spam is notspam is not defined" var foo = function () {}; // foo指向匿名函數function bar() {}; // 函數宣告var baz = function spam() {}; // 命名函數,只有baz被提升,spam不會被提升。 foo(); // validbar(); // validbaz(); // validspam(); // ReferenceError "spam is not defined"
怎麼寫程式

現在你了解作用域和變數提升,那麼這對javascript編碼又意味著什麼?最重要的一點是,總是用var定義你的變數。而且我強烈推薦,對於一個名稱,在一個作用域裡面永遠只有一次var聲明。如果你這麼做,你就不會遇到作用域和變數提升問題。

語言規範怎麼說

我發現ECMAScript參考文件總是很有用。以下是我找到的關於作用域和變數提升的部分:

如果變數在函數體類別聲明,則它是函數作用域。否則,它是全域作用域(作為global的屬性)。變數將會在執行進入作用域的時候被建立。區塊不會定義新的作用域,只有函數宣告和程式(譯者以為,就是全域性質的程式碼執行)才會創造新的作用域。變數在創建的時候會被初始化為undefined。如果變數宣告語句裡面有賦值運算,則賦值運算只會執行到的時候才會發生,而不是在創建的時候。

我期待這篇文章會對那些對javascript比較迷惑的程式設計師帶來一絲光明。我自己也盡最大的可能去避免帶來更多的迷惑。如果我說錯了什麼,或忽略了什麼,請告知。

譯者補充

有位朋友提醒了我發現了IE下全域作用域下命名函數的提升問題:

我翻譯文章的時候是這麼測試的:

複製程式碼 程式碼如下:
<script><div class="codebody" id="code24426">functiont(){<BR>spam();<BR>var baz = function spam() {alert('this is spam')};<BR>}<BR>t(); <BR></script>


這種寫法, 即非全局作用域下的命名函數的提升,在ie和ff下表現是一致的. 我改成:

複製程式碼 程式碼如下:
<script><div class="codebody" id="code76764">spam();<BR><BR><script><BR>spam();<BR>var baz = function spam() {alert('this is spam')};<BR></script>
則ie下是可以執行spam的,ff下不可以. 說明不同瀏覽器在處理這個細節上是有差別的.


這個問題也引導我思考了另2個問題,1:對於全局作用於範圍的變量,var與不var是有區別的. 沒有var的寫法,其變量不會被提升。例如下面兩個程序,第二個會報錯:

程式碼如下:


<script><BR>alert(a);<BR>var a=1;<BR></script>

複製程式碼


程式碼如下:

<script><BR>alert(a);<BR>a=1;<div class="codetitle"></script>2: eval中創建的局部變數是不會被提升的(它也沒辦法做到).

複製程式碼


代碼如下:


<script><BR>var a = 1;<BR>function t(){<BR> alert(a);<BR> eval('var a = 2');</script>
 alert(a);}t();alert(a);
陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn