之所以會寫這篇文章,主要源於筆者在重構老專案的時候發現了一個bug,導致某個外掛不生效了,在review加search code加斷點除錯之後,發現了原因:一個同名的變數將外掛方法給覆蓋了,ohmyGad。
正文
1.變數是如何被覆蓋的
在一般情況下,js程式碼都是自上而下執行的,對於同一個變數,我們可以通過如下方式來修改:
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或者函式宣告的方式定義一個變數時,這個變數的定義會提升到方法體的最頂端,即如下所示:
var a = undefined;
var b = undefined;
console.log(a)
// ..
console.log(b)
複製程式碼
因此我們得出一條結論:
函式宣告和變數宣告總是會被直譯器悄悄地被"提升"到方法體的最頂部。
值得注意的是,我們使用let,const定義變數的時候,並不會發生提升,因為它存在區域性(塊)作用域的概念,會出現暫時性死區,所以在它們之前列印變數將報錯。如果對暫時性死區或者對es6不太瞭解的朋友可以參考我的另一篇文章,
對let和const以及es6的新特性有詳細的介紹。
3.更近一步——變數提升的優先順序
直接剖出問題:
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等前端知識和實戰,歡迎在公眾號《趣談前端》加入我們一起學習討論,共同探索前端的邊界。
更多推薦
- 《前端實戰總結》如何在不重新整理頁面的情況下改變URL
- 前端元件/庫打包利器rollup使用與配置實戰
- 一張圖教你快速玩轉vue-cli3
- vue高階進階系列——用typescript玩轉vue和vuex
- 快速掌握es6+新特性及es6核心語法盤點
- 基於nodeJS從0到1實現一個CMS全棧專案(上)
- 基於nodeJS從0到1實現一個CMS全棧專案(中)
- 基於nodeJS從0到1實現一個CMS全棧專案(下)
- 基於nodeJS從0到1實現一個CMS全棧專案的服務端啟動細節
- 使用Angular8和百度地圖api開發《旅遊清單》
- 《javascript高階程式設計》核心知識總結
- 用css3實現驚豔面試官的背景即背景動畫(高階附原始碼)
- 5分鐘教你用nodeJS手寫一個mock資料伺服器
- 教你用200行程式碼寫一個偶像拼拼樂H5小遊戲(附原始碼)