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