簡單說說javascript作用域

引子

在進(jìn)入本文的主題前君旦,首先請大家判斷如下代碼輸出結(jié)果為什么陕贮?并說明理由。

function foo(){
    console.log(a);
}
function bar(){
    var a=3;
    foo();
}
var a=2;
bar();

不管大家的答案是什么,這里正確得答案是2住练,至于為什么,就請先看下文蛹尝。相信大家看完本文之后痢缎,應(yīng)該能找到真正得原因。

為什么需要作用域涌穆?

在介紹作用域前怔昨,首先請看如下兩個(gè)函數(shù),哪個(gè)函數(shù)功能更強(qiáng)大宿稀?

function func1(){
    return 1+2
}

function func2(a,b){
  return a+b;
}

顯而易見趁舀,使用了變量的函數(shù)功能更為強(qiáng)大。變量給予了程序更加強(qiáng)大的功能祝沸,如果沒有變量矮烹,程序只能做一些很簡單得運(yùn)算,有了變量罩锐,就能做更多更有意思的事情奉狈,但是程序在運(yùn)行時(shí),在需要時(shí)又是如何找到變量并使用它呢涩惑?
此時(shí)就引出了本文今天的主角——作用域仁期,作用域永遠(yuǎn)都是任何一門編程語言中的重中之重,它控制著變量的可見性生命周期。在javascript中跛蛋,作用域在代碼編譯運(yùn)行過程中幫助javascript引擎根據(jù)標(biāo)識符名稱(即變量名)進(jìn)行變量的查找與訪問碰纬。

javascript作用域

從源代碼到運(yùn)行結(jié)果

大家都知道我們現(xiàn)有的馮諾依曼體系的計(jì)算機(jī),歸根結(jié)底運(yùn)行的始終只能是機(jī)器語言问芬,機(jī)器語言是用二進(jìn)制代碼表示的計(jì)算機(jī)能直接識別和執(zhí)行的一種機(jī)器指令的集合悦析。而我們平常編程采用的編程語言,比如java此衅,c强戴,c++,Object C挡鞍,javascript等骑歹,都是屬于高級語言,高級語言是面向用戶的語言墨微,因?yàn)樗鼘θ祟惛押玫烂模桌斫馀c編寫,但是我們采用高級語言編寫的代碼始終是要在計(jì)算機(jī)上運(yùn)行的翘县,所以最域,我們用任何一種高級語言編寫的代碼程序,都必須要經(jīng)過處理锈麸,生成為機(jī)器可以理解執(zhí)行的機(jī)器語言镀脂,而把高級語言代碼轉(zhuǎn)換為機(jī)器語言指令的過程就叫編譯
傳統(tǒng)的編程語言忘伞,在執(zhí)行前會經(jīng)過如下三個(gè)步驟:

  1. 詞法分析:這個(gè)過程將由字符組成的代碼字符串分解成有意義得代碼塊薄翅,這些代碼塊即為詞法單元,如var a=1,這個(gè)語句會被分解為如下詞法單元:var氓奈、a翘魄、=、1舀奶;
  2. 語法分析:這個(gè)過程是將詞法單元(數(shù)組)轉(zhuǎn)換為一個(gè)由元素逐級嵌套所組成的代表了程序語法結(jié)構(gòu)的樹暑竟,稱為“抽象語法樹”(Abstract Syntax Tree,AST)伪节;
  3. 代碼生成:將AST轉(zhuǎn)換為可執(zhí)行代碼的過程被稱為代碼生成光羞。即將AST轉(zhuǎn)換為機(jī)器指令。
    如c語言這類靜態(tài)編譯語言怀大,首先編譯生成對象文件纱兑,然后再使用對象文件去運(yùn)行,所以可以在運(yùn)行前進(jìn)行大量的細(xì)致的優(yōu)化處理化借,而javascript是動態(tài)編譯的潜慎,它是編譯了就馬上執(zhí)行,并不生成特定對象文件。這就決定了javascript無法在編譯階段進(jìn)行過多得優(yōu)化處理铐炫。這就是javascript執(zhí)行性能始終不如C這類靜態(tài)編譯語言的原因垒手。這兩者的區(qū)別如圖:
1-W-vXba0FnDOzsQH0hup-9g.png

也許大家會好奇,本文的主旨不是作用域嗎倒信?怎么說到編譯來了科贬,這是因?yàn)閖avascript在編譯階段就已經(jīng)使用了作用域!

javascript編譯與運(yùn)行階段的作用域

在說javascript編譯與運(yùn)行階段的作用域前鳖悠,首先榜掌,這里說一下javascript中得三個(gè)關(guān)鍵概念:

  • 引擎:從頭至尾負(fù)責(zé)整個(gè)javascript的編譯及運(yùn)行;
  • 編譯器:引擎對代碼進(jìn)行的編譯即由編譯器支持的乘综,主要負(fù)責(zé)語法分析憎账,代碼生成等;
  • 作用域:負(fù)責(zé)在javascript編譯階段與運(yùn)行階段收集并維護(hù)所有聲明的標(biāo)識符(變量)的查找與訪問權(quán)限的控制卡辰;
    三者關(guān)系如下圖:
2.png

下面以 var a=1為例胞皱,闡述一下作用域如何參與到j(luò)avascript的編譯階段與運(yùn)行階段中。
大部分程序員看到var a=1這條語句時(shí)九妈,都會以為這是一句聲明反砌,但是對于javascript引擎來說,卻并非如此允蚣,javascript引擎會把這條代碼一分為二于颖,拆開來看,分別為var a;a=1嚷兔,前者由編譯器在編譯時(shí)進(jìn)行處理,后者做入,由引擎在運(yùn)行時(shí)處理冒晰;

  1. 當(dāng)javascript引擎遇到var a時(shí),編譯器會在當(dāng)前作用域中查找該變量a竟块,如果能找到壶运,編譯器會忽略該聲明,否則浪秘,它會要求在當(dāng)前作用域中聲明一個(gè)新的變量蒋情,并命名為a;然后編譯器會為引擎生成運(yùn)行時(shí)所需得代碼耸携;
  2. 當(dāng)javascript引擎在運(yùn)行代碼遇到a=1這個(gè)賦值操作時(shí)棵癣,也會首先詢問作用域中是否存在一個(gè)叫做a的變量,如果存在夺衍,則使用該表明了狈谊,如果不存在,在根據(jù)作用域鏈規(guī)則,去當(dāng)前作用域的上一層級的作用域中查找變量河劝,最終找到變量a壁榕,則將1賦值給它,否則引擎拋出異常赎瞎。
    由此可見牌里,javascript引擎,在編譯時(shí)务甥,如果遇到變量聲明二庵,就會去作用域中查詢是否已有該變量,沒有則在作用域中聲明記錄缓呛,有則跳過催享;在運(yùn)行階段,當(dāng)程序需要使用變量時(shí)哟绊,則會去作用域中查找該變量因妙,有則,使用變量票髓,無則拋出異常攀涵。

javascript的作用域模型

目前,作用域共有兩種主要的工作模型洽沟,第一種是最為普遍以故,被大多數(shù)編程語言如java、c裆操、c++等所采用的詞法作用域怒详,另一種是動態(tài)作用域,只有少數(shù)編程語言使用踪区,如bash腳本昆烁,perl等;而javascript采用的就是詞法作用域缎岗。

  • 詞法作用域:詞法作用域就是定義在詞法階段(編譯階段的詞法分析階段)的作用域静尼。換句話說,詞法作用域是由程序員在編寫代碼時(shí)將變量與函數(shù)聲明寫在哪里來決定的传泊。詞法作用域的作用域鏈?zhǔn)腔诖a中得作用域嵌套鼠渺。

  • 動態(tài)作用域:動態(tài)作用域并不關(guān)心函數(shù)和作用域是如何聲明以及在何處聲明的,只關(guān)心它們從何處調(diào)用眷细,也就是說拦盹,動態(tài)作用域的作用域鏈?zhǔn)腔谡{(diào)用棧的,而不是代碼中得作用域嵌套薪鹦。
    這里我們回到文件開頭得那段代碼:

      function foo(){
        console.log(a);
      }
      function bar(){
        var a=3;
        foo();
      }
      var a=2;
      bar();
    

在詞法作用域與動態(tài)作用域下掌敬,作用域關(guān)系圖分別如下:

3.png

在詞法作用域下惯豆,函數(shù)foo中并沒有變量a,所以會沿著作用域鏈向它得上一級作用域奔害,也就是全局作用域查找變量a楷兽,此時(shí)查找到a,且a的值為2华临,所以芯杀,代碼輸出結(jié)果為2;
而在動態(tài)作用域下雅潭,函數(shù)foo中也沒有變量a揭厚。所以會沿著作用域鏈向它得上一級作用域,也就是函數(shù)bar的作用域中查找變量a扶供,此時(shí)筛圆,函數(shù)bar中定義了一個(gè)局部變量a,且a的值為3椿浓,所以輸出結(jié)果會為3太援。
那么接下來,請思考扳碍,如果把代碼改為如下形式提岔,輸出結(jié)果又是什么?

   function foo(){
      console.log(a);
    }
    function bar(){
      var a=3;
      foo();
    }
    bar();
    var a=2;

如果改成如下代碼笋敞,結(jié)果又如何碱蒙?

function foo(){
    console.log(a);
}
function bar(){
    var a=3;
    foo();
}
bar();
let a=2;

ES6明確規(guī)定,如果區(qū)塊中存在let和const命令夯巷,這個(gè)區(qū)塊對這些命令聲明的變量赛惩,從一開始就形成了封閉作用域。凡是在聲明之前就使用這些變量鞭莽,就會報(bào)錯(cuò)坊秸。
總之,在代碼塊內(nèi)澎怒,使用let命令聲明變量之前,該變量都是不可用的阶牍。這在語法上喷面,稱為“暫時(shí)性死區(qū)”(temporal dead zone,簡稱 TDZ)走孽。

結(jié)語

本文簡單介紹了一下javascript的作用域的基本知識惧辈,作用域是所有編程語言的重中之重,更是javascript中得重中之重磕瓷,是理解javascript中閉包概念的基礎(chǔ)盒齿。如果你的目標(biāo)是精通JavaScript語言念逞,深入的理解它的各個(gè)組成,那么理解作用域便是你的起點(diǎn)边翁。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末翎承,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子符匾,更是在濱河造成了極大的恐慌叨咖,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件啊胶,死亡現(xiàn)場離奇詭異甸各,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)焰坪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進(jìn)店門趣倾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人某饰,你說我怎么就攤上這事儒恋。” “怎么了露乏?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵碧浊,是天一觀的道長。 經(jīng)常有香客問我瘟仿,道長箱锐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任劳较,我火速辦了婚禮驹止,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘观蜗。我一直安慰自己臊恋,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布墓捻。 她就那樣靜靜地躺著抖仅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪砖第。 梳的紋絲不亂的頭發(fā)上撤卢,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天,我揣著相機(jī)與錄音梧兼,去河邊找鬼放吩。 笑死,一個(gè)胖子當(dāng)著我的面吹牛羽杰,可吹牛的內(nèi)容都是我干的渡紫。 我是一名探鬼主播到推,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼惕澎!你這毒婦竟也來了莉测?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤集灌,失蹤者是張志新(化名)和其女友劉穎悔雹,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體欣喧,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡腌零,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了唆阿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片益涧。...
    茶點(diǎn)故事閱讀 38,566評論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖驯鳖,靈堂內(nèi)的尸體忽然破棺而出闲询,到底是詐尸還是另有隱情,我是刑警寧澤浅辙,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布扭弧,位于F島的核電站,受9級特大地震影響记舆,放射性物質(zhì)發(fā)生泄漏鸽捻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一泽腮、第九天 我趴在偏房一處隱蔽的房頂上張望御蒲。 院中可真熱鬧,春花似錦诊赊、人聲如沸厚满。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽碘箍。三九已至,卻和暖如春鲸郊,著一層夾襖步出監(jiān)牢的瞬間敲街,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工严望, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人逻恐。 一個(gè)月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓像吻,卻偏偏與公主長得像峻黍,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子拨匆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評論 2 348

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