深入探究js中的隱式變數聲明

  • 2019 年 10 月 3 日
  • 筆記

前兩天遇到的問題,經過很多網友的深刻討論,終於有一個相對可以解釋的通的邏輯了,然後我仔細研究了一下相關的點,順帶研究了一下js中的隱式變數。

  • 以下文章中提到的隱式變數都是指沒有用var,let,const等關鍵字定義的變數。
  • 以下文章中提到的var變數都是指用var聲明定義的變數。

一遇到隱式變數,我們去百度一下,都會看見這樣一句話,隱式變數是全局變數,在函數中用隱式變數也是全局變數,但是在函數中用var變數是局部變數,那我們來具體看下隱式變數到底與var變數有什麼區別,下面我們來通過一些程式碼來探究隱式變數與var變數的區別。

1.隱式變數與var變數的區別

程式碼1:

var變數:

console.log(a);//undefined  var a = 100;

程式碼2:

隱式變數:

console.log(a);//Uncaught ReferenceError: a is not defined  a = 100;

 看上面的程式碼我們發現var變數a是存在變數聲明提升的,也就是程式碼1相當於下面的程式碼:

var a;  console.log(a);  a = 100;

這與我們以前了解的那個var變數是一樣的,變數聲明提升還是那個變數聲明提升,並不會改變,這裡如果想詳細了解變數提升的,推薦這篇文章大家可以看一下。然後我們繼續看程式碼2,隱式變數在前面列印變數a時會報錯,那這裡我們是不是可以猜測,隱式變數是不是不存在變數提升,當然以前好像也沒有人說過隱式變數存在聲明提升,可能我一廂情願的認為隱式變數是全局變數就存在聲明提升。然後我們繼續看在函數中,程式碼塊中聲明的隱式變數是不是跟上面全局定義的隱式變數一樣,不存在聲明提升。

程式碼3:

函數中的var變數:

console.log(a);//Uncaught ReferenceError: a is not defined  function b() {      console.log(a);//undefined      var a = 100;  }  b(); 

程式碼4:

函數中的隱式變數:

console.log(a);//Uncaught ReferenceError: a is not defined  function b() {  	console.log(a);//Uncaught ReferenceError: a is not defined  	a = 100;  }  b();

我們來看上面的程式碼3和程式碼4,我們發現程式碼3,在第一行就報錯,因為var在函數內部定義的變數屬於局部變數,全局是訪問不到的,然後把第一行注釋掉再運行,發現第三行列印出的a是undefined,證明var變數在函數中進行了變數聲明提升。我們來看程式碼4,在函數內定義了一個隱式變數,然後第一行就會報錯,a是未定義,然後把第一行注釋掉再運行,發現第三行也還是報錯a未定義,這裡是不是就說明隱式變數並不會有變數聲明提升這種操作,換一句話說,就是隱式變數類似於函數一樣,聲明和定義是一起的,只有執行了這行程式碼,才能引用訪問這個變數,這裡推薦一篇關於變數生命周期的文章,可以細讀一下,然後我們再看一下程式碼塊中定義的var變數和隱式變數:

程式碼5:

程式碼塊中的var變數:

console.log(a);//undefined  {  	console.log(a);//undefined  	var a = 100;  }

程式碼6:

程式碼塊中的隱式變數:

console.log(a);//Uncaught ReferenceError: a is not defined  {  	console.log(a);//Uncaught ReferenceError: a is not defined  	a = 100;  }

然後我們看程式碼塊中的var變數,是存在聲明提升的,然後隱式變數是不存在聲明提升的,所以上面訪問都會報錯。

  • 結論:根據上面的一系列的探究我們基本可以確定一個結論,那就是隱式變數是不存在變數聲明提升的。

2.程式碼塊中的函數聲明提升

    上面探究完隱式變數我們再稍微看下程式碼塊中的函數聲明提升:

程式碼7:

console.log(a);//undefined  {  	function a(){};  	console.log(a);//ƒ a(){}  }  console.log(a);//ƒ a(){}

我們看上面的程式碼,我們都知道函數是js中的”一等公民”,優先順序是最高的,那在上面這個程式碼中,函數是否提升到了塊作用域的外面呢,如果我說提升了,那你從塊作用域最外面調用你會發現報錯,a不是一個方法:

a();//Uncaught TypeError: a is not a function  {  	function a(){};  }

然後這樣之前的我就得出一個結論,函數聲明提升並沒有將函數提升到最外層,那就要問那上面我們列印的a為undefined,為什麼不是報錯a未定義呢,根據阮一峰老師的這篇文章的描述,我們可以得出一個結論:

 

  • 塊作用域內定義的函數在塊作用域內會進行函數提升,提升到最上面,然後在最外層類似於var聲明一個同名變數,默認值為undefined。

3.程式碼塊中存在同名的隱式變數與函數的情況

    然後我們再回過頭來看程式碼塊中同時存在同名的隱式變數和函數時會怎樣:

首先我們來看上面這個程式碼,此時如果我們再最外層的上面列印a和b你會發現會列印出來是undefined:

程式碼8:

console.log(a,b);//undefined undefined  {  	console.log(a);//ƒ a() {}  	function a() {};  	a = 50;  	console.log(a);//50  }  console.log(a);//ƒ a() {}  {  	console.log(b);//ƒ b() {}  	b = 50;  	function b() {};  	console.log(b);//50  }  console.log(b);//50

我們看上面的程式碼,你會發現最前面列印a和b都是undefined,通過前面對隱式變數和函數聲明的探究我們可以知道此時外面的a和b都是函數聲明定義的,另外我們從側面也可以看出這一點,vscode有一個功能,是可以查看變數是誰定義的,那我們來看一下:

此時我們發現vscode分析出來最上面的a和b都是a和b函數定義的,根據我們上面的探究,既然隱式變數不存在變數聲明提升,那隻能函數定義的,然後通過對函數的探究可以證實這一點,同時配上vscode分析出來的結果,也證實了這一點,首先可以確定,最外層的全局的a和b都是函數定義的。然後我們再繼續往下看,最開始的a和b列印undefined沒問題,

然後我們來看下上面圖片上第16行列印的結果為什麼是a方法,通過我上篇文章一行一行的調試你會發現,此時程式碼塊中的a其實是被限制在塊作用域裡面的,並不是全局的變數,此時其實a = 50這行程式碼不是隱式變數,因為a已經被函數定義過了,那a = 50也就是對之前定義過的變數賦值了,所以此時

a = 50不是隱式變數,然後對之前定義的a賦值,在程式碼塊中列印出來為50,然後出了塊作用域列印出來的結果為方法a,通過程式碼一步步的調試你會發現全局的a,只有在執行a方法的程式碼時才會把塊作用域賦的值同步到全局。

第一步:此時全局的a為undefined,此時a是程式碼塊中函數定義的:

 

 第二步:此時我們發現出來了一個塊作用域,因為程式碼塊中的函數提前了,此時程式碼塊中的a的值為a方法,而全局的a還是undefined,沒什麼問題

 

 第三步:此時我們會發現當函數a這一行程式碼走完之後,塊作用域裡面的a跟全局的a都變成了a方法,也就是說想要讓塊作用域中的a的值同步到全局,必須讓程式碼執行到定義a方法的下一行才可以,要不然程式碼塊中的a的值是不會同步到程式碼塊外面的

 

 第四步:到這一步我們會發現塊作用域中的a變成了50,而全局的a還是a方法,根據上一步得到的結論,此時只要在a = 50這行程式碼後面再執行一次方法a,a = 50就會被同步到全局,此時驗證一下也確實是這樣

 然後出塊作用域你會發現列印a為a方法,此時你就不會好奇為什麼列印結果是a方法了,因為塊作用域中的a並沒有同步到全局,而b列印50,是因為b = 50後面執行了一行b方法,將b同步到了全局。到這裡我們也就了解為什麼兩個程式碼只是換了一下函數的位置列印結果過就完全不同了,然後看文章的時候如果發現有什麼錯誤或寫的不好的地方,還請指正,我會立即做出修改。