淺析JavaScript中變量提升的原理與使用
前端的小伙伴大概都知道,js
中的var
變量存在變量提升,在es6
以后隨著let
變量的出現(xiàn),變量提升問題得以解決。那么變量提升的原理是什么?es6
又是怎么解決變量提升問題的?下面我們來共同探尋答案:
我們首先來了解幾個概念,執(zhí)行上下文、變量環(huán)境、詞法環(huán)境。(本文不涉及閉包、this
指向等問題)
執(zhí)行上下文
當一段js
代碼被執(zhí)行時,js
引擎會先對其進行編譯,并創(chuàng)建執(zhí)行上下文。執(zhí)行上下文分為三種:全局執(zhí)行上下文、函數(shù)執(zhí)行上下文、eval
執(zhí)行上下文
- 全局執(zhí)行上下文 在
js
執(zhí)行全局代碼時,js
引擎會創(chuàng)建一個全局的執(zhí)行上下文,全局執(zhí)行上下文在頁面的生命周期內(nèi)只有一份。即每個js
文件,只有一個全局上下文。 - 函數(shù)執(zhí)行上下文 當執(zhí)行一個
js
函數(shù)時,js
引擎會創(chuàng)建一個函數(shù)執(zhí)行上下文,當函數(shù)執(zhí)行結(jié)束之后,函數(shù)的執(zhí)行上下文會被銷毀。一個函數(shù)被多次調(diào)用,會創(chuàng)建多個執(zhí)行上下文。 eval
執(zhí)行上下文 使用eval
函數(shù)執(zhí)行一段js
代碼時,會創(chuàng)建一個eval
的執(zhí)行上下文。
當js
文件執(zhí)行時,首先會創(chuàng)建全局執(zhí)行上下文,并壓入調(diào)用棧,當調(diào)用js
函數(shù)時,會創(chuàng)建函數(shù)執(zhí)行上下文,并壓入調(diào)用棧。當函數(shù)執(zhí)行完之后,函數(shù)執(zhí)行上下文便會從棧中移出。如以下代碼的執(zhí)行:
var a = "123" function func1() { var b = "123" console.log(b) func2() } funcgion func2() { const c = "456" console.log(c) } func1()
執(zhí)行上下文中其實還包含了另外兩個對象,一個變量環(huán)境對象和一個詞法環(huán)境對象。那么接下來我們來看一下什么是變量環(huán)境和詞法環(huán)境
變量環(huán)境
變量環(huán)境存在于執(zhí)行上下文中,其本質(zhì)是一個對象,變量環(huán)境中存儲的是此作用域內(nèi)定義的變量、函數(shù)信息等信息。如全局執(zhí)行上下文中的變量環(huán)境存儲的是全局的變量和函數(shù)信息。函數(shù)執(zhí)行上下文中的變量環(huán)境則存放的是函數(shù)的參數(shù)、局部變量等信息。
其實,js
的代碼在執(zhí)行前還有一個編譯的過程,在編譯過程中,var
變量和function
函數(shù)部分會被js
引擎放入到變量環(huán)境中,并且變量會被默認設(shè)置為undefined
。在執(zhí)行階段,js
引擎會在變量環(huán)境中查找聲明的變量和函數(shù)。這就是我們所說的“變量提升”,這也是為什么函數(shù)可以在函數(shù)的實現(xiàn)之前調(diào)用。
例:
console.log(a) var a = "123" function func1() { console.log(a) } func1()
以上代碼的執(zhí)行順序是:
- js引擎先進行編譯,并把
a
變量和func1
放入到變量環(huán)境中,并把a
變量設(shè)置為undefined
- 進入執(zhí)行階段,執(zhí)行第一行代碼
console.log(a)
,此時從變量環(huán)境中取出a
的值為undefined
,所以打印結(jié)果為undefined
。 - 執(zhí)行第二行代碼
var a = "123"
,將變量環(huán)境中的a
變量賦值為字符串123
。 - 執(zhí)行最后一行代碼
func1()
,js引擎從變量環(huán)境中找出對應(yīng)的func1
,并執(zhí)行里面的代碼console.log(a)
,打印結(jié)果為123
所以以上代碼輸出結(jié)果為
undefined
123
雖然在a
聲明之前打印a
變量,但是卻并沒有報錯。
詞法環(huán)境
在ES6
之前,js
中只支持全局作用域和函數(shù)作用域,并不支持塊級作用域。ES6
之后,js
引入了let
和const
關(guān)鍵字,從而解決了變量提升問題并使js支持了塊級作用域。
其實說let
和const
沒有變量提升并不準確,當js
代碼被編譯時,let
和const
變量代碼會被存放在詞法環(huán)境中。此時let
和const
變量已經(jīng)被提升了,但是只是創(chuàng)建被提升,初始化和賦值并沒有被提升,如果在賦值之前去讀寫該變量,便會報錯,這就是我們所說的“暫時性死區(qū)”。
那實現(xiàn)塊級作用域的原理是什么呢?其實在詞法環(huán)境中,維護了一個作用域棧,棧底是函數(shù)的最外層變量(let
和const
聲明的變量),進入一個作用域塊后,就會把該作用域中的變量入棧;當作用域中的代碼執(zhí)行完成之后,該作用域的信息就會從棧頂彈出。我們舉個以下例子來說明
例:
function fun() { let a = 1 { let a = 2 let b = 3 console.log(a) console.log(b) } console.log(a) console.log(b) } fun()
如圖:
- 當
fun
函數(shù)被編譯時,外層的a
變量首先被創(chuàng)建,并存放至詞法環(huán)境作用域棧中,此時函數(shù)內(nèi)部的塊級作用域中的變量不會被創(chuàng)建。 - 當函數(shù)執(zhí)行至作用域塊時,
let a
和let b
也被創(chuàng)建并入棧存放至棧頂。并將a
賦值為2
,將賦值為3
。 - 當執(zhí)行至
console.log(a)
和console.log(b)
時,js引擎首先從棧頂找到a
和b
的值并打印出2
和3
。 - 當作用域塊執(zhí)行完成之后,作用域塊中的變量信息從棧中彈出。
- 接著執(zhí)行
console.log(a)
找到的是棧底的a
變量,并打印出1
。接著執(zhí)行console.log(b)
,由于在詞法環(huán)境和變量環(huán)境中都找不到b
變量,所以便會報錯b is not defined
。
如果同一個函數(shù)中不同作用域存在相同的變量(如上面例子的a),那么變量的查找順序是怎樣的呢?
- 首先在詞法環(huán)境作用域棧的棧頂?shù)淖兞啃畔⒅虚_始查找
- 如果找到該變量,則直接返回該變量在此作用域塊中的值,如果沒有找到則從棧頂往下依次查找。
- 如果從詞法環(huán)境中的棧頂?shù)綏5锥紱]有找到,則從變量環(huán)境中查找。
總結(jié)
講到這里,我想應(yīng)該可以回答一下文章開始所提的兩個問題:
變量提升的原理是什么?在js代碼編譯階段,var
變量和function
函數(shù)會被js引擎放入到變量環(huán)境中,并且var
變量會被默認設(shè)置為undefined
。需要注意的是,var
變量只有創(chuàng)建和初始化被提升,賦值并沒有被提升;而function
的創(chuàng)建、初始化和賦值均會被提升。所以在變量的聲明之前訪問,該變量的值是undefined
,而函數(shù)則可以在聲明之前正常調(diào)用。
let
、const
是怎么解決變量提升問題的? 在js代碼編譯階段,let
和const
變量會被js引擎放入到詞法環(huán)境中。與var
一樣,let
和const
變量也被提升了。但只是創(chuàng)建被提升,變量的初始化和賦值并未被提升,如果在賦值之前讀寫該變量,就會形成暫時性死區(qū)。
到此這篇關(guān)于淺析JavaScript中變量提升的原理與使用的文章就介紹到這了,更多相關(guān)JavaScript變量提升內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript 快捷鍵設(shè)置實現(xiàn)代碼
屏蔽Alt+F4等快捷鍵 IE Javascript快捷鍵操作2009-03-03JavaScript使用高階生成器進行過濾以生成素數(shù)
生成器大家都知道是怎么一回事,但是高階生成器又是什么東西呢,下面小編就來為大家簡單介紹一下如何使用高階生成器進行過濾以生成素數(shù)吧2024-02-02使用 js 簡單的實現(xiàn) bind、call 、aplly代碼實例
這篇文章主要介紹了使用 js 簡單的實現(xiàn) bind、call 、aplly代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-09-09JavaScript設(shè)置失效時間清除本地存儲數(shù)據(jù)的幾種方法
這篇文章介紹了如何使用localStorage和sessionStorage設(shè)置失效時間來清除本地存儲的數(shù)據(jù),并提供了一種自動清除過期數(shù)據(jù)的方法,需要的朋友可以參考下2025-02-02