JavaScript 中的作用域(scope)和提升(hoisting)

JavaScript

在說有關知識之前先讓大家看幾段代碼:

var i = "Hellow world!";
alert(i);

很簡單對吧叁温?輸出結果是“Hellow world!”核畴,沒有任何問題膝但,好,我們繼續(xù):

var i = "Hellow World!";
function myFunction(){
    alert(i);
}
myFunction();

結果不出意料谤草,還是“Hellow World跟束!”,我們接著看:

var i = "Hello World!"; 
function myFunction(){ 
    alert(i); 
    var i = "I love you"; 
}
myFunction();

大家來猜猜看丑孩,結果是什么冀宴?
有多少人猜到了運行結果是 undefined
那這一段代碼呢温学?

var i = 1;
function myFunction(){
    if(!i){
    var i  = 2;
    alert(i);
    }
}
myFunction();

為什么結果是 2略贮?

這其中有關 JavaScript 中的一個術語** “提升”(Hoisting)**。

作用域(scope)

在說有關提升的知識之前必須要說一下作用域(Scope)仗岖。對于初學者來說逃延,弄清楚作用域是十分重要的。

什么是作用域轧拄?

摘自 維基百科
在電腦程序設計中揽祥,作用域(scope,或譯作有效范圍)是名字(name)與實體(entity)的綁定(binding)保持有效的那部分計算機程序檩电。不同的編程語言可能有不同的作用域和名字解析)拄丰。而同一語言內(nèi)也可能存在多種作用域府树,隨實體的類型變化而不同。作用域類別影響變量的綁定方式料按,根據(jù)語言使用靜態(tài)作用域還是動態(tài)作用域變量的取值可能會有不同的結果奄侠。

  • 包含標識符的宣告或定義;
  • 包含語句和/或表達式站绪,定義或部分關于可運行的算法遭铺;
  • 嵌套嵌套或被嵌套嵌套。

名字空間是一種作用域恢准,使用作用域的封裝性質(zhì)去邏輯上組群起關相的眾識別子于單一識別子之下魂挂。因此,作用域可以影響這些內(nèi)容的名字解析馁筐。
程序員常會縮進他們的源代碼中的作用域涂召,改善可讀性。

簡單說就是敏沉,我們定義一個變量果正,該變量的有效范圍,就是它的作用域盟迟。就有了兩個名詞全局變量秋泳、局部變量。其中就涉及到變量的可見性攒菠,以及變量的生命周期等細節(jié)迫皱,這里不做太多贅述,有不清楚的同學可以自行查閱相關資料辖众。

懂得 C 語言的同學來看一下以下這一段代碼卓起,如果不懂的同學可以跳過:

#include<stdio.h>
void main(){
    int i = 1;
    printf("%d,",i);
    if(i){
        int i = 2;
        printf("%d,",i);
    }
    printf("%d",i);
}

輸出結果為1,2,1 。

熟悉 C語言的人都知道為什么會是這么輸出凹炸。在 C 語言中是塊級作用域戏阅,在 if() 語句中,聲明了同名的局部變量啤它,覆蓋了全局變量的值奕筐。而當出了塊后,局部變量銷毀变骡,重新輸出全局變量的值离赫。

再稍微科普一下什么是塊級作用域:

任何一對花括號({ 和 })中的語句集都屬于一個塊,在這之中定義的所有變量在代碼塊外都是不可見的锣光,我們稱之為塊級作用域。

我們再來看一段 JavaScript 代碼:

var i = 1;
alert(i);
if (i){
    var i = 2;
    alert(i);
}
alert(i);

輸出的結果為 1铝耻,2誊爹,2 蹬刷。

這是因為,在 JavaScript 中是函數(shù)級作用域频丘。類似 if 等塊語句办成,并不會創(chuàng)建新的作用域。只有在函數(shù)中搂漠,才會創(chuàng)建新的作用域迂卢,定義在函數(shù)中的變量在函數(shù)外是不可見的。

那么桐汤,該如何解決呢而克?我們只需要在塊級作用域中臨時創(chuàng)建新的作用域就可以了:

var i = 1; 
if (i) { 
    function temporary() { 
        var i = 2; 
        //其他代碼
    }
    temporary();
} 
// i 的值依然為 1

不過在 ES 5 中規(guī)定,函數(shù)只能在頂層作用域和函數(shù)作用域之中聲明怔毛,不能在塊級作用域聲明员萍。但是瀏覽器并沒有遵守這個規(guī)定,上面的代碼實際情況下還是可以正常運行拣度。但是在嚴格模式中是會報錯的碎绎。而且在不同的瀏覽器中對該語句的支持情況不一樣,考慮到環(huán)境導致差異太大抗果,應該避免在塊級作用域內(nèi)聲明函數(shù)筋帖。如果確實需要,也應該寫成函數(shù)表達式冤馏,而不是函數(shù)聲明語句(函數(shù)提升影響日麸,下文會說)。

看到這里是不是已經(jīng)有人認為 JavaScript 沒有塊級作用域宿接,只有函數(shù)級作用域了赘淮?這句話在前年說,那就是對的睦霎。但是在 2015 年 6 月梢卸,隨著 ECMAScript 6(即 ECMAScript 2015,以后簡稱ES 6)的發(fā)布 副女,JavaScript 也擁有塊級作用域了蛤高。

var 在 JavaScript 中聲明的變量要么是全局變量,要么為函數(shù)級變量碑幅,在 ES 6之前戴陡,是沒有塊級作用域的。在 ES 6 中新增了兩個聲明變量的方式 letconst 沟涨。

  • let 聲明了一個塊級作用域的局部變量恤批,它的作用范圍僅僅為最接近的塊作用域(如果在所有塊以外就是全局作用域),這將會比 var 的函數(shù)作用域更小裹赴。
  • const 聲明的是一個只讀的常量喜庞,而該常量一旦聲明诀浪,常量的值就無法更改。const 聲明的變量不得改變值延都,也就是說雷猪,const 一旦聲明變量,就必須立即初始化晰房,不能留到以后賦值求摇。

注意:

  1. constlet 只在聲明所在的塊級作用域內(nèi)有效。
  2. constlet 不存在提升殊者。
console.log(i); // 輸出 undefined
console.log(j); // 報錯
var i = 1;
let j = 1;
  1. 只要塊級作用域內(nèi)存在 letconst 命令与境,它所聲明的變量就 綁定(binding)這個區(qū)域,不再受外部的影響幽污。ES 6 明確規(guī)定嚷辅,如果區(qū)塊中存在 letconst 命令,這個區(qū)塊對這些命令聲明的變量距误,從一開始就形成了封閉作用域簸搞。凡是在聲明之前就使用這些變量,就會報錯准潭,在聲明變量之前趁俊,該變量都是不可用的,只能在聲明的位置后面使用刑然。這在語法上寺擂,稱為暫時性死區(qū)(temporal dead zone,簡稱TDZ)泼掠。暫時性死區(qū)中不存在變量的提升怔软。
var i = 1;
if (true) {
        i = "1"; // 報錯
        let i;
}
  1. countlet 不可重復聲明參數(shù)。
  2. 必須在嚴格模式下才可以使用 countlet 择镇。

為什么我先那么大篇幅的介紹了作用域挡逼?如果我們能充分理解了作用域,再去理解提升就會比較容易腻豌。

提升(hoisting)

變量提升

JavaScript 的函數(shù)定義有個特點家坎,它會先掃描整個函數(shù)體的語句,把所有申明的變量“提升”到函數(shù)頂部吝梅。但是虱疏,變量提升所提升的僅僅為變量的聲明,并不會將變量的賦值也提升上來苏携。

"use strict";
function myFunction() {
    var x = "Hellow," + y; 
    alert(x);
var y = "World";
}
myFunction();

輸出結果為:Hellow做瞪,undefined。
雖然是 strict 模式右冻,但是也不會報錯装蓬,因為在運行之前衩侥,瀏覽器會先進行一次預編譯,會將變量和函數(shù)先在函數(shù)的最頂部進行預編譯矛物。上面的代碼經(jīng)過編譯后輸出的結果為:

"use strict";
function myFunction() {
    var x;
    var y;
    x = "Hellow," + y; 
    alert(x);
    y = "World";
}
myFunction();

所以結果并不會報錯,而會輸出 undefined跪但。

所以履羞,在我們寫 JavaScript 代碼的時候,需要養(yǎng)成習慣屡久,要把變量放在函數(shù)級作用域的最頂端忆首,防止出現(xiàn)意外。

函數(shù)提升

變量提升是將變量提升到函數(shù)級作用域的最頂端被环,而函數(shù)的提升則是將整個函數(shù)都提到整個作用域的最頂端糙及。不過函數(shù)的聲明跟變量的聲明有一點不一樣。函數(shù)的聲明會連函數(shù)體也會被一同提升筛欢。函數(shù)有兩種聲明方式浸锨,一種是變量指向的函數(shù)表達式,另外一種是函數(shù)的聲明版姑。需要注意的是柱搜,只有函數(shù)的聲明形式才會被提升。二話不多說剥险,先上代碼:

function myFunctionOne(){ 
    myFunctionTwo(); 
    function myFunctionTwo(){ 
        alert("我是 myFunctionTwo"); 
    } 
} 
myFunctionOne(); 

輸出結果為 :我是 myFunctionTwo聪蘸。

我們再來看第二段:

function myFunctionOne(){ 
    myFunctionTwo(); 
    var i =function myFunctionTwo(){ 
      alert("我是 myFunctionTwo"); 
    } 
} 
myFunctionOne(); 

結果報錯。

好的表制,有關的知識到這里就結束了健爬,現(xiàn)在大家應該明白了有關 JavaScript 中的提升和作用域了吧。


有沒有同學在看完了整篇文章之后再回頭看第一段代碼發(fā)現(xiàn)還是沒看懂么介?先把最早的代碼貼上來:

var i = "Hello World!"; 
function myFunction(){ 
    alert(i); 
    var i = "I love you"; 
}
myFunction();

不知道有沒有人有疑惑娜遵,在外部有一個全局變量 i ,但是為什么在函數(shù)內(nèi)部為什么無法引用輸出外面的全局變量夭拌。就算應用了作用域和提升后也無法解釋魔熏。

我們都知道,在 JavaScript 中是可以重復聲明的鸽扁,而且重復聲明并不會修改賦值蒜绽。但是,在局部變量中如果聲明了重名的全局變量桶现,局部變量就會在作用域中覆蓋掉全局變量躲雅。

我們只需要對 var i = "I love you";中的賦值注釋掉后運行,然后再將整個語句注釋后再運行骡和,就可以的得到驗證相赁,兩次輸出的結果先后為 undefined相寇,Hello World!。

結尾

有關作用域和提升相關的知識就總結了這一些钮科,如有遺漏希望讀者們評論補充唤衫。這些特性我們總會在不經(jīng)意間遇到,當我們有時候遇到某些“坑”的時候绵脯,要記得想想這些 JavaScript 中的特性佳励,說不定就能找到問題的所在。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蛆挫,一起剝皮案震驚了整個濱河市赃承,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌悴侵,老刑警劉巖瞧剖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異可免,居然都是意外死亡抓于,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門浇借,熙熙樓的掌柜王于貴愁眉苦臉地迎上來毡咏,“玉大人,你說我怎么就攤上這事逮刨∨荤裕” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵修己,是天一觀的道長恢总。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任配猫,我火速辦了婚禮,結果婚禮上砂豌,老公的妹妹穿的比我還像新娘。我一直安慰自己光督,他們只是感情好阳距,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著结借,像睡著了一般筐摘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天咖熟,我揣著相機與錄音圃酵,去河邊找鬼。 笑死馍管,一個胖子當著我的面吹牛郭赐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播确沸,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼堪置,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了张惹?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤岭洲,失蹤者是張志新(化名)和其女友劉穎宛逗,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盾剩,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡雷激,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了告私。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片屎暇。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖驻粟,靈堂內(nèi)的尸體忽然破棺而出根悼,到底是詐尸還是另有隱情,我是刑警寧澤蜀撑,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布挤巡,位于F島的核電站,受9級特大地震影響酷麦,放射性物質(zhì)發(fā)生泄漏矿卑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一沃饶、第九天 我趴在偏房一處隱蔽的房頂上張望母廷。 院中可真熱鬧,春花似錦糊肤、人聲如沸琴昆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽椎咧。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間勤讽,已是汗流浹背蟋座。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留脚牍,地道東北人向臀。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像诸狭,于是被迫代替她去往敵國和親券膀。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

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

  • let 和 const 命令 let 命令 塊級作用域 const 命令 頂層對象的屬性 gl...
    安小明閱讀 978評論 0 0
  • 《你不知道的JavaScript》真的是一本好書驯遇,閱讀這本書芹彬,我有多次“哦,原來是這樣”的感覺叉庐,以前自以為理解了(...
    然并阮閱讀 619評論 2 9
  • 介紹 JavaScript中有一個被稱為作用域(Scope)的特性舒帮。雖然對于許多新手開發(fā)者來說,作用域的概念并不是...
    安_6dd1閱讀 958評論 0 8
  • let 命令 塊級作用域 const 命令 頂層對象的屬性 global 對象 let 命令 基本用法 ES6 新...
    卞卞村長L閱讀 587評論 0 0
  • 花到了陡叠,挺開心玩郊。唯有瑕疵便是提前讓她知曉我準備的驚喜。 本該開心的一天卻因為我工作的事情再三拖延枉阵,導致原本不舒服的...
    Ermao閱讀 185評論 0 1