JavaScript變量作用域和閉包的基礎知識

d3fw 9年前發布 | 11K 次閱讀 JavaScript開發 JavaScript

在這篇文章中,我會試圖講解JavaScript變量的作用域和聲明提升,以及許多隱隱藏的陷阱。為了確保我們不會碰到不可預見的問題,我們必須真正理解這些概念。


基本定義


作用范圍是個“木桶”,里面裝著變量。變量可以是局部或者全局性的,但在子范圍中定義的變量是可以訪問父范圍的,這一點可能會造成一些困擾。


在JavaScript中使用"var"關鍵字聲明變量。一旦在父范圍宣聲明,就會作為各自子范圍的一部分。即在本地范圍內有效,但本地定義的變量不可在全局范圍內訪問。


讓我們來看一個例子。執行下面的代碼,你會發現,你能打印出全局范圍定義的變量,而全局范圍無法訪問局部范圍定義的變量。



var agloballydefinedvariable = 'Global';

function someFunction() {

  var alocallydefinedvariable = 'Local';

  console.log(agloballydefinedvariable); // Global

}

console.log(alocallydefinedvariable);

// Uncaught ReferenceError: alocallydefinedvariable is not defined
</pre>

作用域鏈(Scope Chain)


如果你忘記使用“var”的關鍵字來定義局部變量,事情可能會變得非常糟糕。為什么會這樣呢?因為JavaScript會首先在父作用域內搜索一個未定義的變量,然后再到全局范圍進行搜索。在下面的例子中,JavaScript知道變量“a”是someFunction()的一個局部變量,在 anotherFunction()中它會尋找它父作用域內的變量。


var a = 1;

function someFunction() {

  var a = 2;

  function anotherFunction() {

    console.log(a); // 2

  }

}
</pre>


更復雜的情況是,在下面的例子中,一個變量沒有在函數中進行作用域的限定。


在someFunction()中調用了一個沒有在函數范圍內定義的變量 a=2; 這個分配將覆蓋全局變量的值。

后續引用將指向全局變量的值。



var a = 1;

function someFunction() {

  a = 2;

  function anotherFunction() {

    console.log(a); // 2

  }

  anotherFunction();

}

someFunction();

console.log(a); //2
</pre>


聲明提升(Hoisting)


Hoisting會將在函數或全局范圍內的變量“提升”到頂部聲明的過程。請記住,只有量聲明被提升了,初始化或值分配等等沒有變化,在下面的代碼的情況下,第一個輸出將不確定...但它不會拋出任何錯誤。


console.log(a); //undefined

var a = 1;

console.log(a); //1
</pre>


Window范圍


在基于瀏覽器的JavaScript中,定義為全局范圍內的一部分變量實際上是所謂的“Window”對象的屬性。這里的Window是指“容器”。換句話說,當你想從一個局部范圍修改全局定義的變量,你也可以通過修改Window對象的相應的屬性來做到這一點。


var myVariable = 'Global Scope';

function myFunction() {

  window.myVariable = 'Something Else';

}

myFunction();

console.log(myVariable); // Something Else
</pre>


可能的陷阱


如果在函數內部分配一個以前沒有被定義的變量的值,它會自動成為全局范圍的一部分。


function myFunction() {

  myVariable = 'JavaScript';

}

myFunction();

console.log(myVariable); //JavaScript
</pre>


如果你不小心忘記定義了一個局部變量,你的整個腳本可能會運行混亂。


var city="LA";

var team="Lakers";

function showTeam () {

    console.log (city + " " + team);

}

function showCity () {

    city = "Moscow";

    console.log (city);

}

showTeam(); // LA Lakers

showCity(); // Moscow

/*

因為上面的 showCity 中定義的變量 "city" 沒有使用 "var" 聲明,全局范圍內的變量被覆蓋了。因此會導致下面的問題 :)

*/

showTeam(); // Moscow Lakers
</pre>


內部函數依然會存儲局部變量即使它的外部函數已經執行完畢


這聽起來可能有點怪異,看一個例子,就會更容易理解。解釋這一點的最好辦法是使用一個簡單的“Hello World”的例子。


function greet(who) {

    var iterations = 0;

    return function () {

        console.log(++iterations);

        return 'Hello ' + who + '!';

    };

}

var greeting = greet('World');

console.log(typeof greeting); //function

console.log(typeof greeting()); //string & iterations=1

console.log(greeting()); //Hello World! & iterations=2

console.log(greeting("Universe")); //Hello World! & iterations=3

//輸出不是 Hello Universe. world 被閉包封閉保存了起來
</pre>


注*  在計算機科學中,閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即使已經離開了創造它的環境也不例外。所以,有另一種說法認為閉包是由函數和與其相關的引用環境組合而成的實體。閉包在運行時可以有多個實例,不同的引用環境和相同的函數組合可以產生不同的實例。

閉包的概念出現于60年代,最早實現閉包的程序語言是Scheme。之后,閉包被廣泛使用于函數式編程語言如ML語言和LISP。很多命令式程序語言也開始支持閉包。  引自:Wiki

</div>


正如你上面看到的那樣,greet() 返回一個被稱為“閉包”的內部函數。閉包除了會儲存他們自己本地作用域內部的封閉起來的函數和變量外,還會存儲外部引用的參數。參看我們的具體例子,參數 who 和 iterations 就是被閉包封閉起來的局部變量。


這意味著,greeting已成為一個包含who和iterations在內的函數(直接返回的匿名函數)。- 它不會再次執行greet,它只會執行閉包而且返回結果永遠是 "Hello World!"。

原文地址: codepunker.com

來自:http://ourjs.com/detail/553edd20329934463f000002

 本文由用戶 d3fw 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!