深入探究js中的隱式變數宣告
前兩天遇到的問題,經過很多網友的深刻討論,終於有一個相對可以解釋的通的邏輯了,然後我仔細研究了一下相關的點,順帶研究了一下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同步到了全域性。到這裡我們也就瞭解為什麼兩個程式碼只是換了一下函式的位置列印結果過就完全不同了,然後看文章的時候如果發現有什麼錯誤或寫的不好的地方,還請指正,我會立即做出修