一個有意思的js塊作用域問題


1.問題

首先把問題放出來,昨天看了一個掘友發的一個問題,然后跟我同事一起研究了一下,沒找出來是為什么,然后我回來一直在想為什么,然后各種找資料研究,從各個方面找為什么,比如js上下文,作用域,js垃圾回收,堆棧調用情況等等。

2.js斷點調試找答案

首先如果不看上面的圖,以你現在知道的js知識,你覺得打印出來應該是什么。第二張圖其實打印出來的結果在意料之中,原因就是函數聲明提升,沒問題,但是第一張圖為什么呢?這里可以發散一下思維,比如說是不是在塊作用域中,變量和函數之間存在某種互相覆蓋的問題啊,或者說先在塊中聲明的會被掛載到全局的window對象下面,后面聲明的就掛載不上去了,並且不會覆蓋,然后可以把代碼稍微改改,驗證一下你的思想,很有意思。然后下面我們斷點調試看下:

首先進花括號一步都沒走的時候,但是a和b已經掛載到全局變量的window對象下面了,這就說明代碼塊中隱式聲明的變量是全局變量(這句話不對,此時掛載到window對象下面的,其實是函數掛的,並不是隱式變量是全局變量的原因掛上去的參考文章這篇文章,基本是這個問題的答案了,解釋了為什么,鏈接: https://juejin.im/post/5d90ae9ef265da5b646480a0),代碼相當於這樣:

此時我們再看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/

在第一篇文章中,看見里面有真么一段話:

然后找到這么一句話,我就點鏈接進去看:

發現es6規范里面真的有對代碼塊作用域的一些規定,但是我也沒太看懂,反正能確定跟這有關系。然后我又點擊了另一個行為方式的鏈接進去看:

然后在這個回答里找到這么一個回答,這個回答說代碼塊作用域中定義的函數類似於var定義的變量,(前面阮一峰博客里也是這么說的),然而第二個綁定僅在塊內部可見,也就是說,第二個綁定在外面是訪問不到的,那用這段話來解釋我們代碼的話就是先不管代碼塊中的函數聲明提升,然后從上面往下運行,看見第一個就綁定到全局的window對象上,第二個就只在函數作用域內可見,那這樣的話我如果在代碼塊內部打印,那結果應該是誰在后面定義我們就打印誰啊,而打印的結果卻是證明了函數提升存在的。所以這個好像也解釋不通。然后從這個回答里面我又找到一個這個鏈接:

然后的然后我就不知道該怎么去看這個問題了,但是我相信應該接近答案了,或許答案就藏在上面es6規范B3.3里面的某個點。當然也可以從調用棧,js垃圾回收,js上下文,js引擎執行解析過程,函數與變量聲明創建原理等等各個方面去分析,這應該是一個值得去分析思考的問題,同時也是很有意思的一個問題,相信你是能學到一些東西的,下面有一些參考鏈接,如果感興趣可以研究一下,在平常寫代碼的時候可能永遠也不會遇到,很有意思的問題。
參考資料:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM