《前端實戰》之變量提高,函數聲明提高及變量做用域詳解

之因此會寫這篇文章,主要源於筆者在重構老項目的時候發現了一個bug,致使某個插件不生效了,在review加search code加斷點調試以後,發現了緣由:一個同名的變量將插件方法給覆蓋了,ohmyGad。javascript

正文

1.變量是如何被覆蓋的

在通常狀況下,js代碼都是自上而下執行的,對於同一個變量,咱們能夠經過以下方式來修改:css

var a = 1;
a = 2;
console.log(a)   // 2
a = function(){};
console.log(a)   // function(){};
複製代碼

2.變量提高

上面的覆蓋過程你們都很好理解,那麼看以下的操做呢?前端

console.log(a);
var a = 1;
console.log(b);
var b = function(){};
複製代碼

這個時候console.log()都會輸出undefined而不會報錯,這是爲何呢?這裏就是變量提高起到的做用。咱們在用var或者函數聲明的方式定義一個變量時,這個變量的定義會提高到方法體的最頂端,即以下所示:vue

var a = undefined;
var b = undefined;
console.log(a)
// ..
console.log(b)
複製代碼

所以咱們得出一條結論:java

函數聲明和變量聲明老是會被解釋器悄悄地被"提高"到方法體的最頂部。node

值得注意的是,咱們使用let,const定義變量的時候,並不會發生提高,由於它存在局部(塊)做用域的概念,會出現暫時性死區,因此在它們以前打印變量將報錯。若是對暫時性死區或者對es6不太瞭解的朋友能夠參考個人另外一篇文章,webpack

快速掌握es6+新特性及es6核心語法盤點;css3

對let和const以及es6的新特性有詳細的介紹。es6

3.更近一步——變量提高的優先級

直接剖出問題:web

var a = 1;
function a(){
    console.log(a)
}
console.log(a)
複製代碼

此時代碼會打印什麼呢?答案是會打印1。這個問題也是我以前面試一些求職者的過程當中錯誤高發區,這裏隱藏着一個概念:函數聲明提高的優先級高於變量聲明的提高。瀏覽器底層的實現過程是這樣的:當js解析器在遇到函數聲明時,會優先將其提高到定義體頂部,其次再是var聲明的變量,這樣就致使函數a被變量a給覆蓋的狀況,因此最終將打印1。

4.函數參數做用域與做用域鏈

做用域就是變量和函數的可訪問範圍,當代碼在一個環境中執行時,會建立變量對象的一個做用域鏈(scope chain),來保證對執行環境有權訪問的變量和函數的順序訪問。做用域第一個對象始終是當前執行代碼所在環境的變量對象。而後會一層層向外查找,直到發現第一個指定的變量爲止。

在瞭解完以上概念以後,咱們來看看下面這個問題:

var a = {name: 'xuxi'};
function b(a){
    a.age = 12;
    a = {num: 1};
    return a
}
var a1 = b(a);
console.log(a, a1)
複製代碼

上面代碼打印的是什麼呢?其實這個是我今天出的面試題,仍是由於一個朋友以前問了我這個問題,我以爲有必要總結一下。雖然今天的候選人沒有答出來,可是我相信在給他解釋完以後他應該不枉此行(說過了,很差意思)。

這塊主要仍是函數內部做用域和引用類型的一個問題。具體過程以下:

(1)咱們根據以前介紹的做用域和做用域鏈的概念能夠知道,在函數體內,變量會就近查找,而函數參數會存在於函數體內部做用域中,因此當咱們把全局變量a看成入參傳遞給函數時,又因爲全局a是引用類型,此時只是引用了它的地址,那麼咱們經過a.age設置屬性時,全局a也會改變。 (2)第二步是將a賦予了一個新的值,此時的a根據就近查找實際上是參數a,本質上是將參數a賦予了一個新的對象,這個時候和全局變量的a沒有任何關係了,此時函數最後會返回一個新的對象。

綜上兩步分析,咱們就會明白爲何打印a時輸出的是{name: 'xuxi', age: 12},打印a1會輸出{num: 1}了。

總結

函數聲明提高,變量做用域以及做用域鏈這塊一直是學習javascript的基礎也是重點,因此但願這篇文章可讓你們更好的掌握它。 若是想了解更多webpack,node,gulp,css3,javascript,nodeJS,canvas等前端知識和實戰,歡迎在公衆號《趣談前端》加入咱們一塊兒學習討論,共同探索前端的邊界。

更多推薦

相關文章
相關標籤/搜索