JavaScript提升機(jī)制Hoisting詳解
前言
剛接觸到JavaScript的時(shí)候,便知道JavaScript是按順序執(zhí)行的,是如瀏覽器的解析DOM樹一樣的流程,解析DOM結(jié)構(gòu)的時(shí)候,如果遇到JS腳本或者外聯(lián)腳本便會(huì)停止解析,繼續(xù)下載腳本之后,執(zhí)行腳本,然后再解析DOM。
然而,卻因此常常碰到問題。
看如下代碼以及輸出:
var name; console.log(name); // undefined name = 'tom'; age = 10; var age; console.log(age); // 10
上面的代碼讓我們產(chǎn)生了疑惑,我們僅僅聲明了name的時(shí)候,打印出來值是undefined,按理說,重新聲明age之后,age的值應(yīng)該也是undefined才對(duì),但是輸出來的卻是10。這究竟是怎么回事兒呢?
我們的通用解釋是,遇到了變量提升。
而這樣的情況,我們?cè)诤瘮?shù)中也會(huì)看到,請(qǐng)看下面代碼:
log(); console.log(name); var name = 'tom'; function log() { console.log('this is log'); }
上面代碼的輸出結(jié)果是什么?
輸出結(jié)果:
this is log undefined
為什么會(huì)產(chǎn)生這樣的情形呢?我們通用的解釋是,函數(shù)聲明提升了。
而針對(duì)這兩種情況,就是我們經(jīng)常遇到的提升機(jī)制,也就是我們常說的Hoisting。
而僅僅只是一句提升機(jī)制來解釋這種現(xiàn)象,還是覺得云里霧里,要是我之前可能也就不明覺厲的哦了一聲,然后就不再理會(huì)這樣的東西了,那么究竟為什么會(huì)出現(xiàn)這樣的情況呢?
JavaScript是如何被編譯的呢
有時(shí)候我們會(huì)想,一段JS代碼是如何執(zhí)行的呢?其實(shí),在JS代碼被執(zhí)行之前,通常都有一個(gè)編譯過程。
這個(gè)編譯過程其實(shí)很復(fù)雜,但總體來說,逃不過編譯過程的步驟,只不過JavaScript是在這個(gè)步驟之中對(duì)代碼做了優(yōu)化處理。
第一、詞法分析
詞法分析主要是將一段程序分解成有意義的代碼塊,便于對(duì)分解的代碼塊做解析。
比如,var age = 10;這一段代碼將會(huì)被分解成 var、age、=、10、;。這是5個(gè)詞法單元。
這些單元分析完畢之后,便會(huì)給解析器調(diào)用,生成相應(yīng)的AST(抽象語法樹)。
第二、解析詞法單元
解析詞法單元,是為了生成AST,那么到底什么是AST呢,我們來看一段代碼以及解析生成的AST。
同樣是var age = 10;這段代碼,被解析器解析成了一段樹形結(jié)構(gòu)的結(jié)構(gòu),這個(gè)結(jié)構(gòu),就是抽象語法樹AST。你可以通過這個(gè)網(wǎng)站來查看生產(chǎn)的AST:AST解析器
而抽象語法樹,又是可以轉(zhuǎn)換成可執(zhí)行代碼。這就涉及到編譯的第三個(gè)階段。
第三、生成可執(zhí)行代碼
生成可執(zhí)行代碼的過程,相當(dāng)于是再把AST轉(zhuǎn)換成瀏覽器可執(zhí)行的代碼,或者是各種語言引擎可執(zhí)行的代碼。
比如我們常見的babel,可以讓我們用ES6的語法去開發(fā)程序,其實(shí)就是依靠babel編譯器,將我們的ES6代碼編譯成ES6的AST,然后將ES6的AST轉(zhuǎn)換成ES5的AST或者ES3的AST,最后將AST轉(zhuǎn)成ES5或ES3的代碼來讓瀏覽器執(zhí)行。
同理,TypeScript的TSC也是一個(gè)編譯器,做的事情和babel是一樣的,只不過兩者編譯出來的ES6的AST有略微的差別,這樣就造成了TypeScript用不了Babel社區(qū)的豐富多樣的插件,如eslint等。
因?yàn)閑slint語法檢查,正是基于AST做的。
那么上面這個(gè)編譯過程有什么用呢?
JavaScript中的聲明和賦值
理解了語言的編譯過程,那么JavaScript中的聲明和賦值又是如何的一個(gè)流程呢?
比如,var age = 10;這段代碼,在JavaScript中的編譯方式是如何呢?
在JavaScript中,這段代碼大概相當(dāng)于是如下兩個(gè)過程:
var age = undefined; // 隱式賦值,編譯階段 age = 10; //變量賦值 執(zhí)行階段
函數(shù)聲明也是如此:
// 這一段代碼就是一個(gè)完整的函數(shù)聲明,在編譯階段中,會(huì)先執(zhí)行所有聲明,才會(huì)依次執(zhí)行代碼操作。 function log() { console.log('this is log') }
這個(gè)時(shí)候,我們?cè)倩仡^來,想一下提升機(jī)制是什么?
再看提升
JavaScript的執(zhí)行,被分為了兩個(gè)階段,分別是編譯階段,以及執(zhí)行階段。依照這個(gè)來看,所謂的提升機(jī)制(有的叫做變量提升,考慮到函數(shù)的定義,并未用這個(gè)名詞),就是JavaScript引擎把變量聲明和函數(shù)聲明在編譯階段首先進(jìn)行默認(rèn)賦值,之后,在程序執(zhí)行階段,才會(huì)被代碼真正的執(zhí)行。也就是說,針對(duì)聲明先提升,后執(zhí)行。
注意:函數(shù)聲明和變量都有提升機(jī)制,兩者之間也有優(yōu)先級(jí)。這都遵循一個(gè)原則:函數(shù)優(yōu)先原則。也就是說,函數(shù)聲明會(huì)提升到普通變量聲明之前。
總結(jié)
變量提升,是一個(gè)值得去探究的概念,只有理解了這個(gè)概念,我們理解JavaScript的執(zhí)行機(jī)制將會(huì)變得清晰明了起來。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
js 返回時(shí)間戳所對(duì)應(yīng)的具體時(shí)間
返回unix時(shí)間戳所對(duì)應(yīng)的具體時(shí)間的代碼2010-07-07微信小程序的宿主環(huán)境實(shí)現(xiàn)代碼
這篇文章主要介紹了微信小程序的宿主環(huán)境,包括scroll-view 組件的基本使用,text 組件的基本使用及rich-text 組件的基本使用,本文通過示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-10-10JavaScript獲取偽元素(Pseudo-Element)屬性的方法技巧
這篇文章主要介紹了JavaScript獲取偽元素(Pseudo-Element)屬性的方法技巧,本文直接給出實(shí)現(xiàn)代碼,需要的朋友可以參考下2015-03-03JS+CSS實(shí)現(xiàn)Div彈出窗口同時(shí)背景變暗的方法
這篇文章主要介紹了JS+CSS實(shí)現(xiàn)Div彈出窗口同時(shí)背景變暗的方法,是一款比較典型的javascript操作彈出窗口的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-03-03ichart.js繪制虛線、平均分虛線效果的實(shí)現(xiàn)代碼
下面小編就為大家?guī)硪黄猧chart.js繪制虛線、平均分虛線效果的實(shí)現(xiàn)代碼。小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考2016-05-05