研究js的塊級做用域中的變量聲明和函數聲明

昨天晚上在沸點看到一個小哥發了個沸點,代碼很簡潔,可是但是弄暈了我,評論區也是很熱鬧,我沒事就研究了下,本身理解了下,感受差很少能夠解釋通了。先來看那是什麼樣的代碼吧javascript

就是這個幾行代碼,反正我看了是不太明白爲何這樣子,另外若是是強類型語言的程序員看了估計會說這個編譯都不會經過吧,類型都不匹配怎麼賦值啊,hahahtml

如下全部代碼都會討論關於塊級做用域的知識,不懂得能夠先惡補阮一峯老師的文章 ES6 的塊級做用域java

另外我所提到的默認變量都指的是沒有用var ,let,const關鍵詞聲明的變量程序員

本文代碼測試均在谷歌瀏覽器測試,支持es6語法,其他瀏覽器執行結果可能會有所不一樣,不表明全部瀏覽器的結果es6

塊級做用域內的默認變量

咱們來研究下塊級做用域內的默認變量面試

運行結果是index.html:14 Uncaught ReferenceError: b is not defined 對於我來講有點震驚,b不是默認至關於var了嗎?事實並不是如此,咱們根據事實能夠得出結論,塊級做用域內的變量聲明不會提高到全局做用域的頂層,咱們查看window也發現window並無這個屬性。再看下面圖typescript

報錯:index.html:16 Uncaught ReferenceError: b is not defined瀏覽器

運行成功,輸出 50,經過斷點調試我發現,一旦 b =50執行後window上就有b這個屬性了

無報錯,第四行和第六行都輸出了50,這說明塊內定義的默認變量和塊外訪問的都是一個,也就是說塊內定義的默認變量只有等到定義默認變量的那行代碼執行後纔會往window上掛載屬性,這個行爲跟var聲明變量有點區別,咱們來看下若是在塊內用var來聲明b是什麼結果

輸出是

能夠看到在塊內用var聲明變量後行爲符合咱們的通常認知,b被提高到全局,一開始是undefined,直到執行賦值代碼後纔有值,var仍是那個var,歷來沒變過,哈哈函數

小結:

  • 在塊級做用域內部聲明的默認變量(不適用let,var,const修飾),只有等到執行過你定義那個變量的那行代碼後才能夠訪問,纔給window賦值這個屬性,在那行代碼以前訪問會報錯
  • 塊內的 默認變量依舊是全局變量
  • 在塊內的默認變量沒執行以前不能夠訪問這個變量

塊級做用域內的函數聲明

在塊級做用域內部的函數聲明非常詭異,直接看代碼測試

console.log(a);
        {
           function a(){}
        }
     
複製代碼

輸出 undefined 還記得塊內的默認變量嗎?它若是這樣子寫會報錯的,可是函數聲明就不會,由於正如阮一峯老師那篇文章所說

在塊內的函數聲明會提高到全局做用域頂部,而且相似var了一個同名的變量,那的確很相似var,默認值是undefined, 接着看

console.log(`${window.a},${a}`);
        {
            console.log(`${window.a},${a}`);
           function a(){}
        }
        console.log(`${window.a},${a}`);
複製代碼

輸出是

細心觀察會發現,第一個console符合預期,第二個console彷佛爲啥window.a是undefiend,而a是有值呢? 再來回想下阮一峯老師的文章提升,函數聲明會提高到塊級做用域的頂部,那此時執行第二個console的時候在塊內的頂部其實已經有了聲明,因此此時a有值,而此時window.a沒有值我猜想,由於window.a是提早在外面var a的那個a,而這個彷佛也跟塊內的默認變量有點類似就是塊外的這個a只有等塊內定義a的那行代碼執行了纔會賦值, 這個賦值行爲跟塊內直接訪問a是不一樣的,直接訪問a至關於訪問塊內提高到頂部的函數聲明,執行的時候就有值了。

小結

  • 塊內的函數聲明會提高到塊內的頂部,同時也會在全局做用域用var聲明一個同名的變量,初始值爲undefined
  • 這個塊外的全局同名變量的賦值時機是執行完塊內那行函數聲明語句後才賦值
  • 塊內的函數聲明會提高到塊內頂部,區別提高到塊外,它並不會用var去聲明一個同名的變量

塊內同時有同名的默認變量和函數聲明

到了最精彩的地方了

看代碼1

console.log(`${window.a},${a}`);
        {
            a = 50;
            console.log(`${window.a},${a}`);
           function a(){}
          
        }
        console.log(`${window.a},${a}`);
複製代碼

打印結果是

1.按照前兩節的總結來,塊內有函數聲明,此時在全局var了一個同名的變量a,也等於window.a 2.第一行輸出沒毛病,var a嘛沒賦值,默認是undefined 3.執行默認的變量 a =50,注意,此時執行後不要覺得會像第一小節說的那樣會給window掛載屬性a,不會的,由於此時在塊級做用域內部已經由於有了下面的函數聲明,此時塊級做用域頂部有了function a(){}的聲明,你此時執行a = 50只是至關於賦值操做,沒有任何聲明,此時給a賦值的時候會查找做用域鏈有沒有聲明a,恰好函數聲明提高到頂部了一個a,因此就把塊內的a賦值爲50,因此第二行打印winddow.a仍然是undefined,而a屬於塊內,此時被賦值爲50了 4.按照第二節的總結,塊外的那個跟塊內函數聲明同名的變量只有在函數聲明那段代碼執行後纔會賦值, 因此最後一行代碼執行時window.a已經被賦值了而且詭異的是塊外的那個變量的值彷佛跟塊內函數聲明的函數綁定着,當執行function(){}的時候會給外面的那個變量賦值,由於塊內那個函數聲明被a =50覆蓋了,因此當執行完 function a(){}以後塊外的那個變量就被賦值爲50了,而非仍是function(){}

看代碼2

console.log(`1 ${window.a},${a}`);
        {
           console.log(`2 ${window.a},${a}`);
           function a(){}
           a = 50;
           console.log(`3 ${window.a},${a}`);
        }
        console.log(`4 ${window.a},${a}`);
複製代碼

打印:

符合你的預期嗎? 我以爲須要解釋下第四個輸出爲何window.a不是50,爲何沒有被覆蓋呢,按照上面所說的 a= 50的時候好像這個a跟外面的那個同名變量綁定着,其實這裏你注意到function a(){}和 a= 50的順序了嗎? 是先寫的function a(){},再寫的a =50;我猜想只有執行function a(){}這段語句時纔會影響外面的那個同名變量,其餘時候不會影響,一旦執行事後,外面的那個變量的值就定死了,因此包括第三行,第四行輸出的window.a已經不受a =50影響了,我又測試了下

console.log(`1 ${window.a},${a}`);
        {
    
           console.log(`2 ${window.a},${a}`);
           function a(){}
           a = 50;
           function a(){} // 再增長個
           console.log(`3 ${window.a},${a}`);
        }
        console.log(`4 ${window.a},${a}`);
複製代碼

我在 a =50以後又寫了個funaction a(){},正如我說,只有執行function a (){}的時候會觸發改變外部的那個同名的全局變量,沒執行一次就會觸發改變外部的那個變量,而且對外部變量賦的值是跟內部同名的函數名綁定着。

小結

  • 塊內的函數聲明每次執行的時候都會給全局那個同名的變量賦值一次,而且,只有執行那個定義函數聲明的代碼纔會觸發賦值,你寫的函數聲明就至關於setter,每執行一次就給外部的那個同名的變量賦值一次
  • 若是塊內同時有同名的函數聲明和默認的變量聲明,那給默認的變量賦值時其實至關於賦值給那個同名的函數,由於查找塊內的做用域鏈時找到了,就不會往全局聲明瞭

總結

js這門語言真的是很神奇的語言,幾行代碼都能讓我琢磨半天,歸根揭底仍是弱類型致使的坑,你一個number類型的變量怎麼能夠賦值給function類型呢?若是能在編譯時直接報錯,估計就沒這麼多面試神題了,由於這就是不懂類型瞎賦值,可是弱類型也不是一無可取,這也是js牛逼的地方,另外都快0202了趕忙用typescript吧,用了一直爽!不再寫別人沒法重構的代碼了!

我也是初次研究這個,可能理解的有不到位,或錯誤的地方,若是有須要修改的地方請提出來,我不誤人子弟

再次聲明,全部代碼均在谷歌瀏覽器上測試,其他版本和其餘瀏覽器結果可能會有所不一樣!

相關文章
相關標籤/搜索