JavaScript中的預(yù)解析

在ES6之前蔬芥,變量使用var聲明,會(huì)存在變量的預(yù)解析(函數(shù)也有預(yù)解析)蜀撑,我相信很多同學(xué)在剛開始學(xué)JavaScript的時(shí)候被預(yù)解析搞得團(tuán)團(tuán)轉(zhuǎn)法瑟,雖然在ES6的時(shí)候引入letconst,但是現(xiàn)階段ES6并沒有完全普及浮庐,而且很多比較老的代碼都還是按照ES5的標(biāo)準(zhǔn)甚至是ES3的標(biāo)準(zhǔn)來書寫的甚负。

一、變量和函數(shù)在內(nèi)存中的展示


JavaScript中的變量類型和其他語言一樣审残,有基本數(shù)據(jù)類型和引用數(shù)據(jù)類型梭域。基本數(shù)據(jù)類型包括:undefined搅轿、null病涨、booleanString璧坟、Number没宾;引用數(shù)據(jù)類型主要是對(duì)象(包括{}、[]沸柔、/^$/、Date铲敛、Function等)褐澎。

var num = 24;
var obj = {name:'iceman' , age:24};
function func() {
    console.log('hello world');
}
  • 當(dāng)瀏覽器加載html頁(yè)面的時(shí)候,首先會(huì)提供一個(gè)供全局JavaScript代碼執(zhí)行的環(huán)境伐蒋,稱之為全局作用域工三。
  • 基本數(shù)據(jù)類型按照值來操作迁酸,引用數(shù)據(jù)類型按照地址來操作。

根據(jù)以上原則俭正,以上的代碼在內(nèi)存中的模型為:

內(nèi)存模型.png

基本類型是直接存儲(chǔ)在棧內(nèi)存中奸鬓,而對(duì)象是存儲(chǔ)在堆內(nèi)存中,變量只是持有該對(duì)象的地址掸读。所以obj持有一個(gè)對(duì)象的地址oxff44串远,函數(shù)func持有一個(gè)地址oxff66。

在以上的代碼的基礎(chǔ)上再執(zhí)行:

console.log(func);
console.log(func());

第一行輸出的是整個(gè)函數(shù)的定義部分(函數(shù)本身):

第一行代碼輸出結(jié)果.png

上面已經(jīng)說明了儿惫,func存儲(chǔ)的是一個(gè)地址澡罚,該地址指向一塊堆內(nèi)存,該堆內(nèi)存就保留了函數(shù)的定義肾请。

第二行輸出的是func函數(shù)的返回結(jié)果:

第二行代碼輸出結(jié)果.png

由于func函數(shù)沒有返回值留搔,所以輸出undefined。
注意:函數(shù)的返回結(jié)果铛铁,return后面寫的是什么隔显,返回值就是什么,如果沒有return饵逐,默認(rèn)返回值是undefined括眠。

二、預(yù)解析


有了以上的內(nèi)存模型的理解之后梳毙,就能更好的了解預(yù)解析的機(jī)制了哺窄。所謂的預(yù)解析就是:在當(dāng)前作用域中,JavaScript代碼執(zhí)行之前账锹,瀏覽器首先會(huì)默認(rèn)的把所有帶var和function聲明的變量進(jìn)行提前的聲明或者定義萌业。

2.1. 聲明和定義

var num = 24;

這行簡(jiǎn)單的代碼其實(shí)是兩個(gè)步驟:聲明和定義。

  • 聲明:var num; 告訴瀏覽器在全局作用域中有一個(gè)num變量了奸柬,如果一個(gè)變量只是聲明了生年,但是沒有賦值,默認(rèn)值是undefined廓奕。
  • 定義:num = 12; 定義就是給變量進(jìn)行賦值抱婉。

2.2. var聲明的變量和function聲明的函數(shù)在預(yù)解析的區(qū)別

var聲明的變量和function聲明的函數(shù)在預(yù)解析的時(shí)候有區(qū)別,var聲明的變量在預(yù)解析的時(shí)候只是提前的聲明桌粉,function聲明的函數(shù)在預(yù)解析的時(shí)候會(huì)提前聲明并且會(huì)同時(shí)定義蒸绩。也就是說var聲明的變量和function聲明的函數(shù)的區(qū)別是在聲明的同時(shí)有沒同時(shí)進(jìn)行定義。

2.3. 預(yù)解析只發(fā)生在當(dāng)前的作用域下

程序最開始的時(shí)候铃肯,只對(duì)window下的變量和函數(shù)進(jìn)行預(yù)解析患亿,只有函數(shù)執(zhí)行的時(shí)候才會(huì)對(duì)函數(shù)中的變量和函數(shù)進(jìn)行預(yù)解析。

console.log(num);
var num = 24;
console.log(num);

func(100 , 200); 
function func(num1 , num2) {
    var total = num1 + num2;
    console.log(total);
}
輸出結(jié)果.png

第一次輸出num的時(shí)候押逼,由于預(yù)解析的原因步藕,只聲明了還沒有定義惦界,所以會(huì)輸出undefined;第二次輸出num的時(shí)候咙冗,已經(jīng)定義了沾歪,所以輸出24。

由于函數(shù)的聲明和定義是同時(shí)進(jìn)行的雾消,所以func()雖然是在func函數(shù)定義聲明處之前調(diào)用的灾搏,但是依然可以正常的調(diào)用,會(huì)正常輸出300仪或。

內(nèi)存模型.png

三确镊、 作用域鏈


先理解以下三個(gè)概念:

  • 函數(shù)里面的作用域成為私有作用域,window所在的作用域稱為全局作用域范删;
  • 在全局作用域下聲明的變量是全局變量蕾域;
  • 在“私有作用域中聲明的變量”和“函數(shù)的形參”都是私有變量;

在私有作用域中到旦,代碼執(zhí)行的時(shí)候旨巷,遇到了一個(gè)變量,首先需要確定它是否為私有變量添忘,如果是私有變量采呐,那么和外面的任何東西都沒有關(guān)系,如果不是私有的搁骑,則往當(dāng)前作用域的上級(jí)作用域進(jìn)行查找斧吐,如果上級(jí)作用域也沒有則繼續(xù)查找,一直查找到window為止仲器,這就是作用域鏈煤率。

當(dāng)函數(shù)執(zhí)行的時(shí)候,首先會(huì)形成一個(gè)新的私有作用域乏冀,然后按照如下的步驟執(zhí)行:

  1. 如果有形參蝶糯,先給形參賦值;
  2. 進(jìn)行私有作用域中的預(yù)解析辆沦;
  3. 私有作用域中的代碼從上到下執(zhí)行

函數(shù)形成一個(gè)新的私有的作用域昼捍,保護(hù)了里面的私有變量不受外界的干擾(外面修改不了私有的,私有的也修改不了外面的)肢扯,這也就是閉包的概念妒茬。

console.log(total); 
var total = 0;
function func(num1, num2) {
    console.log(total); 
    var total = num1 + num2;
    console.log(total);
}
func(100 , 200);
console.log(total); 

以上代碼執(zhí)行的時(shí)候,第一次輸出total的時(shí)候會(huì)輸出undefined(因?yàn)轭A(yù)解析)蔚晨,當(dāng)執(zhí)行func(100,200)的時(shí)候郊闯,會(huì)執(zhí)行函數(shù)體里的內(nèi)容,此時(shí)func函數(shù)會(huì)形成一個(gè)新的私有作用域,按照之前描述的步驟:

  • 先給形參num1团赁、num2賦值,分別為100谨履、200欢摄;
  • func中的代碼進(jìn)行預(yù)解析;
  • 執(zhí)行func中的代碼

因?yàn)樵趂unc函數(shù)內(nèi)進(jìn)行了預(yù)解析笋粟,所以func函數(shù)里面的total變量會(huì)被預(yù)解析怀挠,在函數(shù)內(nèi)第一次輸出total的時(shí)候,會(huì)輸出undefined害捕,接著為total賦值了绿淋,第二次輸出total的時(shí)候就輸出300。 因?yàn)楹瘮?shù)體內(nèi)有var聲明的變量total尝盼,函數(shù)體內(nèi)的輸出total并不是全局作用域中的total吞滞。
最后一次輸出total的時(shí)候,輸出0盾沫,這里輸出的是全局作用域中的total裁赠。

console.log(total); 
var total = 0;
function func(num1, num2) {
    console.log(total); 
    total = num1 + num2;
    console.log(total);
}
func(100 , 200);
console.log(total); 

將代碼作小小的變形之后,func函數(shù)體內(nèi)的total并沒有使用var聲明赴精,所以total不是私有的佩捞,會(huì)到全局作用域中尋找total,也就說說這里出現(xiàn)的所有total其實(shí)都是全局作用域下的蕾哟。

四一忱、 全局作用域下帶var和不帶var的區(qū)別


在全局作用域中聲明變量帶var可以進(jìn)行預(yù)解析,所以在賦值的前面執(zhí)行不會(huì)報(bào)錯(cuò)谭确;聲明變量的時(shí)候不帶var的時(shí)候帘营,不能進(jìn)行預(yù)解析,所以在賦值的前面執(zhí)行會(huì)報(bào)錯(cuò)琼富。

console.log(num1);
var num1 = 12;

console.log(num2);
num2 = 12;
輸出結(jié)果.png
  • num2 = 12; 相當(dāng)于給window增加了一個(gè)num2的屬性名仪吧,屬性值是12;
  • var num1 = 12; 相當(dāng)于給全局作用域增加了一個(gè)全局變量num1鞠眉,但是不僅如此薯鼠,它也相當(dāng)于給window增加了一個(gè)屬性名num,屬性值是12械蹋;

問題:在私有作用域中出現(xiàn)一個(gè)變量出皇,不是私有的,則往上級(jí)作用域進(jìn)行查找哗戈,上級(jí)沒有則繼續(xù)向上查找郊艘,一直找到window為止,如果window也沒有呢?

  • 獲取值:console.log(total); --> 報(bào)錯(cuò) Uncaught ReferenceError: total is not defined
  • 設(shè)置值:total= 100; --> 相當(dāng)于給window增加了一個(gè)屬性名total纱注,屬性值是100
function fn() {
    // console.log(total); // Uncaught ReferenceError: total is not defined
    total = 100;
}
fn();
console.log(total);

注意:JS中畏浆,如果在不進(jìn)行任何特殊處理的情況下,上面的代碼報(bào)錯(cuò)狞贱,下面的代碼都不再執(zhí)行了

五刻获、 預(yù)解析中的一些變態(tài)機(jī)制


5.1 不管條件是否成立,都要把帶var的進(jìn)行提前的聲明

if (!('num' in window)) { 
    var num = 12;
}
console.log(num); // undefined

JavaScript進(jìn)行預(yù)解析的時(shí)候瞎嬉,會(huì)忽略所有if條件蝎毡,因?yàn)樵贓S6之前并沒有塊級(jí)作用域的概念。本例中會(huì)先將num預(yù)解析氧枣,而預(yù)解析會(huì)將該變量添加到window中沐兵,作為window的一個(gè)屬性。那么 'num' in window 就返回true便监,取反之后為false扎谎,這時(shí)代碼執(zhí)行不會(huì)進(jìn)入if塊里面,num也就沒有被賦值茬贵,最后console.log(num)輸出為undefined簿透。

5.2 只預(yù)解析“=”左邊的,右邊的是指針解藻,不參與預(yù)解析

fn(); // -> undefined();  // Uncaught TypeError: fn is not a function
var fn = function () {
    console.log('ok');
}

fn(); -> 'ok'
function fn() {
    console.log('ok');
}
fn(); -> 'ok'

建議:聲明變量的時(shí)候盡量使用var fn = ...的方式老充。

5.3 自執(zhí)行函數(shù):定義和執(zhí)行一起完成

(function (num) {
  console.log(num);
})(100);

自執(zhí)行函數(shù)定義的那個(gè)function在全局作用域下不進(jìn)行預(yù)解析,當(dāng)代碼執(zhí)行到這個(gè)位置的時(shí)候螟左,定義和執(zhí)行一起完成了啡浊。

補(bǔ)充:其他定義自執(zhí)行函數(shù)的方式

~ function (num) {}(100)  
+ function (num) {}(100)  
- function (num) {}(100)  
! function (num) {}(100)  

5.4 return下的代碼依然會(huì)進(jìn)行預(yù)解析

function fn() {                             
    console.log(num); // -> undefined
    return function () {             
                                   
    };                               
    var num = 100;                   
}                                  
fn();  

函數(shù)體中return下面的代碼,雖然不再執(zhí)行了胶背,但是需要進(jìn)行預(yù)解析巷嚣,return中的代碼,都是我們的返回值钳吟,所以不進(jìn)行預(yù)解析廷粒。

5.5 名字已經(jīng)聲明過了,不需要重新的聲明红且,但是需要重新的賦值

var fn = 13;                                       
function fn() {                                    
    console.log('ok');                               
}                                                  
fn(); // Uncaught TypeError: fn is not a function  

經(jīng)典題目

fn(); // -> 2                                             
function fn() {console.log(1);}                           
fn(); // -> 2                                             
var fn = 10; // -> fn = 10                                
fn(); // -> 10()  Uncaught TypeError: fn is not a function                          
function fn() {console.log(2);}                           
fn();       

個(gè)人公眾號(hào)(icemanFE):分享更多的前端技術(shù)和生活感悟

個(gè)人公眾號(hào).png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末坝茎,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子暇番,更是在濱河造成了極大的恐慌嗤放,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件壁酬,死亡現(xiàn)場(chǎng)離奇詭異次酌,居然都是意外死亡恨课,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門岳服,熙熙樓的掌柜王于貴愁眉苦臉地迎上來剂公,“玉大人,你說我怎么就攤上這事吊宋∥芰簦” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵贫母,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我盒刚,道長(zhǎng)腺劣,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任因块,我火速辦了婚禮橘原,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘涡上。我一直安慰自己趾断,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布吩愧。 她就那樣靜靜地躺著芋酌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪雁佳。 梳的紋絲不亂的頭發(fā)上脐帝,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音糖权,去河邊找鬼堵腹。 笑死,一個(gè)胖子當(dāng)著我的面吹牛星澳,可吹牛的內(nèi)容都是我干的疚顷。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼禁偎,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼腿堤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起届垫,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤释液,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后装处,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體误债,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡浸船,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了寝蹈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片李命。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖箫老,靈堂內(nèi)的尸體忽然破棺而出封字,到底是詐尸還是另有隱情,我是刑警寧澤耍鬓,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布阔籽,位于F島的核電站,受9級(jí)特大地震影響牲蜀,放射性物質(zhì)發(fā)生泄漏笆制。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一涣达、第九天 我趴在偏房一處隱蔽的房頂上張望在辆。 院中可真熱鬧,春花似錦度苔、人聲如沸匆篓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鸦概。三九已至,卻和暖如春疗认,著一層夾襖步出監(jiān)牢的瞬間完残,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工横漏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谨设,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓缎浇,卻偏偏與公主長(zhǎng)得像扎拣,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子素跺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 深入理解JavaScript系列文章二蓝,包括了原創(chuàng),翻譯指厌,轉(zhuǎn)載刊愚,整理等各類型文章,如果對(duì)你有用踩验,請(qǐng)推薦支持一把鸥诽,給大...
    DaveWeiYong閱讀 598評(píng)論 0 1
  • 繼承 一商玫、混入式繼承 二、原型繼承 利用原型中的成員可以被和其相關(guān)的對(duì)象共享這一特性牡借,可以實(shí)現(xiàn)繼承撞叨,這種實(shí)現(xiàn)繼承的...
    magic_pill閱讀 1,054評(píng)論 0 3
  • 代碼解析參與者 需要了解變量是如何進(jìn)行預(yù)解析的家妆,首先要知道解析代碼的參與者焙糟,有三個(gè):引擎额港、編譯器、作用域 編譯器對(duì)...
    素彌閱讀 508評(píng)論 0 1
  • 文章名:改變看事情的角度——人對(duì)錯(cuò)誤念念不忘 作者:霧滿攔江 重點(diǎn)摘要: 1.偉大的人物碴里,都有過人的秉質(zhì)沈矿。 同樣也...
    一念_花開閱讀 175評(píng)論 0 0
  • 兩年前無緣無故地過敏,每天晚上夜不能寐咬腋,皮膚上大面積地長(zhǎng)紅疹细睡,有時(shí)候整張臉都是紅腫的。去看醫(yī)生帝火,中醫(yī)和西醫(yī)都找不出...
    不知什么時(shí)候會(huì)游水的漁閱讀 163評(píng)論 0 0