1.問題
首先把問題放出來,昨天看了一個掘友發的一個問題,然后跟我同事一起研究了一下,沒找出來是為什么,然后我回來一直在想為什么,然后各種找資料研究,從各個方面找為什么,比如js上下文,作用域,js垃圾回收,堆棧調用情況等等。
2.js斷點調試找答案
首先如果不看上面的圖,以你現在知道的js知識,你覺得打印出來應該是什么。第二張圖其實打印出來的結果在意料之中,原因就是函數聲明提升,沒問題,但是第一張圖為什么呢?這里可以發散一下思維,比如說是不是在塊作用域中,變量和函數之間存在某種互相覆蓋的問題啊,或者說先在塊中聲明的會被掛載到全局的window對象下面,后面聲明的就掛載不上去了,並且不會覆蓋,然后可以把代碼稍微改改,驗證一下你的思想,很有意思。然后下面我們斷點調試看下:
此時我們再看a在塊作用域中就已經是方法了,注意此時我function a(){}
這段代碼還沒走完呢,這就說明函數聲明在js解析(注意是解析不是執行)的時候被提升到了代碼塊頂部,進花括號的那一刻起,函數就已經被聲明了,我們再往下面一步走
此時a不管在塊作用域還是全局作用域中都變成了a函數,那這里是不是可以理解為運行上面一行代碼,然后就給全局變量下的a賦值為函數呢,我們再看下一步
當a=50;
走完之后,也就出了塊作用域,此時我們看到沒有了塊作用域,因為已經出了塊作用域了,然后全局對象window里面a還是函數,並不是50,但是如果你在塊作用域a后面加一行的斷點看的話,此時塊作用域里面的a的值為50,問題就在這里,為什么此時塊作用域里面的a的值跟全局window對象下面的值結果不一樣呢?
然后我們再往下走一步:
然后進第二個塊作用域,發現跟前面進第一個塊作用域一樣,還沒執行第一行,塊作用域里面的b已經是函數了,原因也跟第一個一樣js解析的時候函數聲明提升,然后我們再往下走一步:
這一步走完我們發現塊作用域里面的b已經變成50了,但是全局window對象下面的b還是undefined,這我也不知道為什么,那我也就只能說此時b是定義在塊作用域中的內部變量了,再往下走一步
但是當我走出塊作用域的時候,b竟然在全局對象下變成了50,那就證明我上面說的不對,b不是塊作用域中的內部變量,因為此時執行完方法立馬就出塊作用域了,我們看的不是很清楚,我們在方法下面加一行代碼,方便調試看結果:
確實是當我b函數那一步走完,塊作用域和全局對象window下面的b都變成了50,那我這里我就認為是函數b在js解析的時候就被提升到了塊作用域的最上面,執行到b函數那一步其實在之前就已經執行過了,相當於js執行的時候代碼變成下面這樣:
{ function b() {}; b = 50; }
我們再看這個代碼不正是上面a那一個塊作用域的代碼嗎,所以在塊作用域中js執行的時候上下兩個塊作用域中是一樣的,所以在塊作用域中打印a,b得到的結果都是50,然后下一步:
出了塊作用域,就只有全局對象window了,然后window對象下面的b還是50,所以最后打印出來也是50。走到這一步就所有的步驟都走完了,那么我們再回頭看上面的a為什么塊作用域中的值跟window對象下面的a的值不一樣,通過走完下面一個代碼塊我們發現上面代碼塊跟下面代碼塊只有函數放的位置不一樣,結果就不一樣,那我們就看一下這里函數聲明提升到底是怎么提升的。
3.塊作用域中的函數聲明提升
然后我就找到阮一峰博客里面寫的關於es6塊級作用域的文章:
http://es6.ruanyifeng.com/#docs/let#%E5%9D%97%E7%BA%A7%E4%BD%9C%E7%94%A8%E5%9F%9F
另一篇關於js變量的生命周期的文章:
https://dmitripavlutin.com/variables-lifecycle-and-why-let-is-not-hoisted/
在第一篇文章中,看見里面有真么一段話: