JavaScript 作用域

LHS & RHS

編譯器在遇到一個(gè)變量 a 時(shí),會(huì)去查詢作用域中是否存在 a 淋叶。但是有兩種不同查詢方式阎曹,考慮如下代碼:

        function(){
          a = 5;
        }
        console.log(a)      //5

a 是一個(gè)未聲明的變量,實(shí)際上會(huì)隱含地創(chuàng)建一個(gè)全局變量,再看另一個(gè):

        let b = a煞檩;         //TypeError: a is not defined

問題:同樣是查詢沒有聲明的變量处嫌,為什么結(jié)果不一樣?
答:對(duì)于變量斟湃,編譯器有兩種處理方式:LHS(left hand side)熏迹、RHS(right hand side)

LHS

賦值號(hào)左邊的查詢,實(shí)際上是找一個(gè)名叫 b 的容器凝赛,如果在當(dāng)前作用域沒有找到癣缅,
就向上一級(jí)作用域查詢,直到頂級(jí)作用域哄酝,如果頂級(jí)作用域還沒有友存,那就隱式創(chuàng)建一個(gè)并返回

RHS

賦值號(hào)右邊的查詢,“RHS”意味著“取得他/她的源(值)”陶衅,暗示著 RHS 的意思是“去取……的值”屡立。

考慮如下代碼:

        function foo(a) {
            console.log( a ); // 2
        }

        foo( 2 );

調(diào)用 foo(..) 的最后一行作為一個(gè)函數(shù)調(diào)用要求一個(gè)指向 foo 的 RHS 引用,意味著,“去查詢 foo 的值膨俐,并把它交給我”勇皇。另外,(..) 意味著 foo 的值應(yīng)當(dāng)被執(zhí)行焚刺,所以它最好實(shí)際上是一個(gè)函數(shù)敛摘!

這里有一個(gè)微妙但重要的賦值。你發(fā)現(xiàn)了嗎乳愉?

你可能錯(cuò)過了這個(gè)代碼段隱含的 a = 2兄淫。它發(fā)生在當(dāng)值 2 作為參數(shù)值傳遞給 foo(..) 函數(shù)時(shí),值 2 被賦值 給了參數(shù) a蔓姚。為了(隱含地)給參數(shù) a 賦值捕虽,進(jìn)行了一個(gè) LHS 查詢。

這里還有一個(gè) a 的值的 RHS 引用坡脐,它的結(jié)果值被傳入 console.log(..)泄私。console.log(..) 需要一個(gè)引用來執(zhí)行。它為 console 對(duì)象進(jìn)行一個(gè) RHS 查詢备闲,然后發(fā)生一個(gè)屬性解析來看它是否擁有一個(gè)稱為 log 的方法晌端。

小結(jié):

JavaScript 引擎 在執(zhí)行代碼之前首先會(huì)編譯它,因此恬砂,它將 var a = 2; 這樣的語句分割為兩個(gè)分離的步驟:

  1. 首先咧纠,var a 在當(dāng)前 作用域 中聲明。這是在最開始觉既,代碼執(zhí)行之前實(shí)施的。
  2. 稍后乳幸,a = 2 查找這個(gè)變量(LHS 引用)瞪讼,并且如果找到就向它賦值。

LHS 和 RHS 引用查詢都從當(dāng)前執(zhí)行中的 作用域 開始粹断,如果有需要(也就是符欠,它們?cè)谶@里沒能找到它們要找的東西),它們會(huì)在嵌套的 作用域 中一路向上瓶埋,一次一個(gè)作用域(層)地查找這個(gè)標(biāo)識(shí)符希柿,直到它們到達(dá)全局作用域(頂層)并停止,既可能找到也可能沒找到

詞法作用域

作用域的工作方式有兩種占統(tǒng)治地位的模型养筒。其中的第一種是最最常見曾撤,在絕大多數(shù)的編程語言中被使用的。它稱為 詞法作用域晕粪,我們將深入檢視它挤悉。另一種仍然被一些語言(比如 Bash 腳本,Perl 中的一些模式巫湘,等等)使用的模型装悲,稱為 動(dòng)態(tài)作用域昏鹃。

詞法作用域是 JavaScript 所采用的作用域模型。

看代碼:

        function foo(a) {

            var b = a * 2;

            function bar(c) {
                console.log( a, b, c );
            }

            bar(b * 3);
        }

        foo( 2 ); // 2 4 12

這里有三層作用域: 最外面一層诀诊、foo洞渤、bar
在上面的代碼段中,引擎 執(zhí)行語句 console.log(..) 并開始查找三個(gè)被引用的變量 a属瓣,b 和 c载迄。它首先從最內(nèi)部的作用域氣泡開始,也就是 bar(..) 函數(shù)的作用域奠涌。在這里它找不到 a宪巨,所以它向上走一層,到外面下一個(gè)最近的作用域氣泡溜畅,foo(..) 的作用域捏卓。它在這里找到了 a,于是它就使用這個(gè) a慈格。同樣的事情也發(fā)生在 b 身上怠晴。但是對(duì)于 c,它在 bar(..) 內(nèi)部就找到了

一旦找到第一個(gè)匹配浴捆,作用域查詢就停止了

注意:全局變量也自動(dòng)地是全局對(duì)象(在瀏覽器中是 window蒜田,等等)的屬性,所以不直接通過全局變量的詞法名稱选泻,而通過將它作為全局對(duì)象的一個(gè)屬性引用來間接地引用冲粤,是可能的

        window.a
欺騙詞法作用域 eval()

詞法作用域是由函數(shù)被聲明的位置唯一定義的,而且這個(gè)位置完全是一個(gè)編寫時(shí)的決定页眯。

但是! 不完全是這樣梯捕!

JavaScript 中的 eval(..) 函數(shù)接收一個(gè)字符串作為參數(shù)值,并將這個(gè)字符串的內(nèi)容看作是好像它已經(jīng)被實(shí)際編寫在程序的那個(gè)位置上窝撵。

換句話說傀顾,你可以用編程的方式在你編寫好的代碼內(nèi)部生成代碼,而且你可以運(yùn)行這個(gè)生成的代碼碌奉,就好像它在編寫時(shí)就已經(jīng)在那里了一樣

        function foo(str, a) {
            eval( str ); // 作弊短曾!
            console.log( a, b );
        }

        var b = 2;

        foo( "var b = 3;", 1 ); // 1 3
  • 默認(rèn)情況下,如果 eval(..) 執(zhí)行的代碼字符串包含一個(gè)或多個(gè)聲明(變量或函數(shù))的話赐劣,這個(gè)動(dòng)作就會(huì)修改這個(gè) eval(..) 所在的詞法作用域嫉拐。

  • 當(dāng) eval(..) 被用于一個(gè)操作它自己的詞法作用域的 strict 模式程序時(shí),在 eval(..) 內(nèi)部做出的聲明不會(huì)實(shí)際上修改包圍它的作用域

          function foo(str) {
             "use strict";
             eval( str );
             console.log( a ); // ReferenceError: a is not defined
          }
    
          foo( "var a = 2" );
    

JavaScript 引擎 在編譯階段期行許多性能優(yōu)化工作魁兼。其中的一些優(yōu)化原理都?xì)w結(jié)為實(shí)質(zhì)上在進(jìn)行詞法分析時(shí)可以靜態(tài)地分析代碼椭岩,并提前決定所有的變量和函數(shù)聲明都在什么位置,這樣在執(zhí)行期間就可以少花些力氣來解析標(biāo)識(shí)符。

但是判哥!使用eval()會(huì)讓編譯器假定自己知道的所有的標(biāo)識(shí)符的位置可能是無效的献雅,因?yàn)樗豢赡茉谠~法分析時(shí)就知道你將會(huì)向eval(..)傳遞什么樣的代碼來修改詞法作用域

盡可能避免使用 eval() ,為了性能塌计!

函數(shù)與塊兒作用域

隱藏于普通作用域

拿你所編寫的代碼的任意一部分挺身,在它周圍包裝一個(gè)函數(shù)聲明,這實(shí)質(zhì)上“隱藏”了這段代碼锌仅。

有多種原因驅(qū)使著這種基于作用域的隱藏章钾。它們主要是由一種稱為“最低權(quán)限原則”的軟件設(shè)計(jì)原則引起的note-leastprivilege,有時(shí)也被稱為“最低授權(quán)”或“最少曝光”

將變量和函數(shù)“隱藏”在一個(gè)作用域內(nèi)部的另一個(gè)好處是热芹,避免兩個(gè)同名但用處不同的標(biāo)識(shí)符之間發(fā)生無意的沖突贱傀。沖突經(jīng)常導(dǎo)致值被意外地覆蓋。

函數(shù)作為作用域

我們可以拿來一段代碼并在它周圍包裝一個(gè)函數(shù)伊脓,而這實(shí)質(zhì)上對(duì)外部作用域“隱藏”了這個(gè)函數(shù)內(nèi)部作用域包含的任何變量或函數(shù)聲明

        var a = 2;

        function foo() { // <-- 插入這個(gè)

            var a = 3;
            console.log( a ); // 3

        } // <-- 和這個(gè)
        foo(); // <-- 還有這個(gè)

        console.log( a ); // 2

雖然這種技術(shù)“可以工作”府寒,但它不一定非常理想。它引入了幾個(gè)問題报腔。首先是我們不得不聲明一個(gè)命名函數(shù) foo()株搔,這意味著這個(gè)標(biāo)識(shí)符名稱 foo 本身就“污染”了外圍作用域(在這個(gè)例子中是全局)。我們要不得不通過名稱(foo())明確地調(diào)用這個(gè)函數(shù)來使被包裝的代碼真正運(yùn)行纯蛾。

幸運(yùn)的是纤房,JavaScript 給這兩個(gè)問題提供了一個(gè)解決方法--IIFE

        var a = 2;

        (function foo(){ // <-- 插入這個(gè)

            var a = 3;
            console.log( a ); // 3

        })(); // <-- 和這個(gè)

        console.log( a ); // 2

當(dāng)然也可以這樣寫:

        var a = 2;

        (function IIFE( global ){

            var a = 3;
            console.log( a ); // 3
            console.log( global.a ); // 2

        })( window );

        console.log( a ); // 2
塊兒作為作用域

我們經(jīng)常不經(jīng)意間聲明了全局變量:

        for (var i=0; i<10; i++) {
            console.log( i );
        }
        console.log(i)          //  10

let

let 關(guān)鍵字將變量聲明附著在它所在的任何塊兒(通常是一個(gè) { .. })的作用域中。換句話說翻诉,let 為它的變量聲明隱含地劫持了任意塊兒的作用域炮姨。

        var foo = true;

        if (foo) {
            { // <-- 明確的塊兒
                let bar = foo * 2;
                bar = something( bar );
                console.log( bar );
            }
        }

        console.log( bar ); // ReferenceError

注意:var 會(huì)有聲明提前,然而碰煌,使用 let 做出的聲明將 不會(huì) 在它們所出現(xiàn)的整個(gè)塊兒的作用域中提升舒岸。如此,直到聲明語句為止拄查,聲明將不會(huì)“存在”于塊兒中

       {
          console.log( bar ); // ReferenceError!
          let bar = 2;
       }
關(guān)于 var 和 let
       for (var i = 1; i <= 5; i++) {
           setTimeout(function timer() {
             console.log(i);
           }, i * 1000)
       }                            // 隔一秒輸出吁津, 5 個(gè) 6


        for (let i = 1; i <= 5; i++) {
                       setTimeout(function timer() {
                         console.log(i);
                       }, i * 1000)
                   }              // 隔一秒輸出 1堕扶,2,3梭依,4稍算,5

上面的代碼展示了 var 和 let 的不同。 let 聲明時(shí)保存了當(dāng)時(shí)的環(huán)境役拴,保留了每一個(gè)值糊探,下一次循環(huán)再在(另一個(gè)"塊")創(chuàng)建一個(gè)另外的變量
而 var 從頭到尾只有一個(gè)變量,并且輸出時(shí)值為 6

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市科平,隨后出現(xiàn)的幾起案子褥紫,更是在濱河造成了極大的恐慌,老刑警劉巖瞪慧,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件髓考,死亡現(xiàn)場離奇詭異,居然都是意外死亡弃酌,警方通過查閱死者的電腦和手機(jī)氨菇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來妓湘,“玉大人查蓉,你說我怎么就攤上這事“裉” “怎么了豌研?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長竣灌。 經(jīng)常有香客問我聂沙,道長,這世上最難降的妖魔是什么初嘹? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任及汉,我火速辦了婚禮,結(jié)果婚禮上屯烦,老公的妹妹穿的比我還像新娘坷随。我一直安慰自己,他們只是感情好驻龟,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布温眉。 她就那樣靜靜地躺著,像睡著了一般翁狐。 火紅的嫁衣襯著肌膚如雪类溢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天露懒,我揣著相機(jī)與錄音闯冷,去河邊找鬼。 笑死懈词,一個(gè)胖子當(dāng)著我的面吹牛蛇耀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播坎弯,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼纺涤,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼译暂!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起撩炊,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤外永,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后拧咳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體象迎,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年呛踊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了砾淌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡谭网,死狀恐怖汪厨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情愉择,我是刑警寧澤劫乱,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站锥涕,受9級(jí)特大地震影響衷戈,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜层坠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一殖妇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧破花,春花似錦谦趣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至峭梳,卻和暖如春舰绘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背葱椭。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來泰國打工捂寿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人挫以。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓者蠕,卻偏偏與公主長得像窃祝,于是被迫代替她去往敵國和親掐松。 傳聞我的和親對(duì)象是個(gè)殘疾皇子踱侣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

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

  • 目錄 1.靜態(tài)作用域與動(dòng)態(tài)作用域 2.變量的作用域 3.JavaScript 中變量的作用域 4.JavaScri...
    一縷殤流化隱半邊冰霜閱讀 7,086評(píng)論 37 113
  • 如果我的文章對(duì)你有用抡句,請(qǐng)給我一個(gè)贊,讓我有繼續(xù)堅(jiān)持的動(dòng)力/微笑杠愧。原創(chuàng)文章待榔,此文章僅供學(xué)習(xí)參考使用,歡迎訪問我的個(gè)人...
    我就是z閱讀 485評(píng)論 0 3
  • 最近看了《你不知道的JavaScript》這本書的上卷流济,感覺相當(dāng)不錯(cuò)锐锣,一些以前模糊不清的感念從這本書里都弄懂了,下...
    劉尐六閱讀 347評(píng)論 0 3
  • 引子 在進(jìn)入本文的主題前绳瘟,首先請(qǐng)大家判斷如下代碼輸出結(jié)果為什么雕憔?并說明理由。 不管大家的答案是什么糖声,這里正確得答案...
    markouy閱讀 488評(píng)論 0 2
  • 以下內(nèi)容來自《你不知道的JavaScript上》 什么是作用域 作用域是一組規(guī)則斤彼,它決定了在哪里和如何查找一個(gè)變量...
    尛臉仺白閱讀 170評(píng)論 0 0