一周一章前端書·第1周:《你不知道的JavaScript(上)》S01E01

第1章:作用域是什么

  • 我們通過(guò)var聲明變量時(shí)壕吹,是否考慮過(guò)這些問題:
    • 這些變量都存儲(chǔ)在哪里?
    • 程序用到它們時(shí),又是怎么找到它們的刽沾?
  • 而答案就是:不僅僅是JavaScript,任何編程語(yǔ)言都會(huì)設(shè)計(jì)一套良好的規(guī)則來(lái)存取變量排拷,而這套規(guī)則就叫做 作用域侧漓。

1.1 編譯原理

  • 雖然和靜態(tài)語(yǔ)言(比如Java)不同,JavaScript是“解釋性”的動(dòng)態(tài)語(yǔ)言监氢。
  • 但實(shí)際上布蔗,JavaScript代碼在運(yùn)行之前也是需要編譯的,并且JavaScript引擎編譯的步驟浪腐,和傳統(tǒng)的編譯語(yǔ)言非常相似纵揍,大致有以下三大步驟:
第1步:分詞/詞法分析(Tokenizing/Lexing)
  • 任何.js文件在解析前,對(duì)于JS引擎而言都是一大段文本议街,不能直接運(yùn)行泽谨。所以當(dāng)務(wù)之急,就是將文本字符串“大卸八塊”般的進(jìn)行分解傍睹。
  • 詞法分析就是 將文本內(nèi)容分解成有意義的詞法字符串(token) 隔盛。
  • 比如var a = 2;最終會(huì)分解成詞法字符串?dāng)?shù)組,得到 [var拾稳、a吮炕、=2访得、;]龙亲,而多余的空格則是無(wú)意義的陕凹。
第2步:解析/語(yǔ)法分析(Parsing)
  • 語(yǔ)法分析則是 將詞法字符串?dāng)?shù)組轉(zhuǎn)換成 “抽象語(yǔ)法樹”(Abstract Syntax Tree,AST
  • 比如代碼var a = 2;會(huì)生成以下具有層次結(jié)構(gòu)的對(duì)象
    /*變量聲明的對(duì)象*/
    VariableDeclaration : {
      /*變量名為 a*/
      Identifier : a,
      /*變量賦值表達(dá)式*/
      AssignmentExpression : {
          /*數(shù)值類型為 2*/
          NumericLiteral : 2 
      }
    }
    
第3步:代碼生成
  • 最后一步就是生成代碼鳄炉, 將AST轉(zhuǎn)換為可執(zhí)行的機(jī)器指令 杜耙。
  • 比如代碼var a = 2;會(huì)創(chuàng)建一個(gè)變量a,并為其分配內(nèi)存拂盯,然后將值2存進(jìn)這個(gè)變量佑女。

1.2 理解作用域

原書將引擎、編譯器以及作用域模擬成三個(gè)演員谈竿,用來(lái)說(shuō)明在執(zhí)行一段代碼時(shí)团驱,三者分別負(fù)責(zé)的工作。但我稍微做一些改動(dòng)空凸,將作用域比喻成一個(gè)記錄清單嚎花。

  • 執(zhí)行JS代碼依賴三個(gè)東西:
    • 引擎:負(fù)責(zé)JS代碼的編譯和執(zhí)行
    • 編譯器:在引擎工作前,負(fù)責(zé)語(yǔ)法分析和代碼生成
    • 作用域:一個(gè)具有嚴(yán)格的規(guī)則呀洲,專門負(fù)責(zé)收集并維護(hù)所有變量的清單列表紊选,通過(guò)它來(lái)存取變量
  • 閱讀代碼 var a = 2; 其實(shí)訪問了兩次作用域,一個(gè)是 在編譯器編譯時(shí)檢查變量聲明道逗,一個(gè)是 引擎運(yùn)行時(shí)檢查使用
    • 如上面所說(shuō)的兵罢,第1步編譯器會(huì)進(jìn)行詞法分析,第2步將詞法單元解析成一個(gè)樹結(jié)構(gòu)的對(duì)象憔辫;
    • 在第3步生成代碼時(shí)趣些,編譯器會(huì)去查找作用域,檢查 是否存在同名的變量贰您,如果沒有則聲明一個(gè)新的變量并賦值 坏平;
    • 最后引擎運(yùn)行代碼時(shí),會(huì)再次通過(guò)作用域 檢查 是否存在同名的變量锦亦,如果有則直接 使用舶替,沒有則繼續(xù)向上查找
  • 引擎執(zhí)行代碼到作用域查找變量,分為兩種類型:RHS查詢LHS查詢
    • “L(left)”和“R(right)”分別代表變量處于表達(dá)式的左邊還是右邊杠园;
    • RHS查詢就是查找變量顾瞪,可理解成retrieve his source value(找到它源值)。比如console.log(a)就是RHS查詢抛蚁,找到變量a的值傳遞給console.log()陈醒;
    • LHS查詢則是查找變量的容器對(duì)其進(jìn)行賦值。比如var a = 2;就是LHS查詢瞧甩,找到變量a并為它賦值= 2钉跷;
  • 我們嘗試用RHS查詢和LHS查詢的思維來(lái)閱讀JS代碼:
    function foo(a){
        console.log(a);
    }
    foo(2);
    
    我們都知道function聲明函數(shù)的方式等同于,聲明一個(gè)變量并為其賦值一個(gè)執(zhí)行方法體:
    var foo = function(a){
        console.log(a);
    }
    foo(2);
    
    • var foo = function()這是一個(gè)LHS查詢:聲明foo變量并為其賦值一個(gè)方法肚逸;
    • foo(2)屬于RHS查詢:找到foo變量的值并執(zhí)行它
    • 進(jìn)到foo方法體中爷辙,實(shí)際上這里隱藏了一句代碼a = 2;將傳遞的值賦值給形參
    • console.log(a)是RHS查詢:找到a的值彬坏,傳遞給console.log(...)
    • 值得一提的是,console.log()本身也屬于RHS查詢膝晾,會(huì)去找尋log()方法的引用并執(zhí)行它

1.3 作用域嵌套

  • 不管是RHS查詢還是LHS查詢都從當(dāng)前作用域開始栓始,如果當(dāng)前作用域無(wú)法找到變量時(shí),引擎會(huì)轉(zhuǎn)移到外層作用域中繼續(xù)查找血当,直至轉(zhuǎn)移到最頂層的作用域幻赚,也就是全局作用域。
  • 舉例:
    function foo(a){
        console.log(a + b);
    }
    var b = 2;
    foo(2);
    
    foo方法體中歹颓,變量bfoo的作用域中找不到坯屿,將會(huì)到外層的全局作用域查找,最后輸出4

1.4 異常

  • 之所以 區(qū)分RHS和LHS巍扛,是因?yàn)楫?dāng)查找到未聲明的變量時(shí),這兩種查詢的行為是不一樣的:
    • 如前文提到的乏德,LHS查詢失敗時(shí)會(huì)在全局作用域創(chuàng)建一個(gè)同名的變量撤奸;
    • 而RHS查詢失敗時(shí),則會(huì)拋出 ReferenceError異常喊括;另一種情況是胧瓜,查找到了變量,但是嘗試對(duì)這個(gè)變量的值做不合理的操作(比如對(duì)一個(gè)非函數(shù)的變量進(jìn)行調(diào)用)郑什,則拋出TypeError異常
    • 總而言之府喳,RererenceError異常是作用域判別失敗相關(guān)的, TypeError異常 則代表作用域判別成功了蘑拯,但對(duì)結(jié)果的操作是非法或不合理的

1.5 小結(jié)

  • 作用域是一套存取變量的規(guī)則钝满;
  • 在代碼執(zhí)行前,會(huì)先由編譯器進(jìn)行編譯申窘,JavaScript引擎在執(zhí)行代碼時(shí)會(huì)進(jìn)行LHS查詢和RHS查詢:
    • LHS查詢是對(duì)變量進(jìn)行賦值弯蚜,其中=操作符或者調(diào)用函數(shù)時(shí)傳參的操作,都會(huì)導(dǎo)致相關(guān)作用域的賦值操作剃法;
    • RHS查詢是對(duì)變量的值進(jìn)行查找碎捺;
  • LHS和RHS查詢都會(huì)從當(dāng)前執(zhí)行作用域開始,如果當(dāng)前作用域找不到贷洲,就會(huì)往上級(jí)作用域繼續(xù)查找收厨,每次上升一級(jí)作用域,直至到頂級(jí)的全局作用域
  • 不成功的RHS查詢會(huì)拋出Reference異常优构,而不成功的LHS查詢會(huì)自動(dòng)式地創(chuàng)建一個(gè)全局變量
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末诵叁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子俩块,更是在濱河造成了極大的恐慌黎休,老刑警劉巖浓领,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異势腮,居然都是意外死亡联贩,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門捎拯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)泪幌,“玉大人,你說(shuō)我怎么就攤上這事署照』隼幔” “怎么了?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵建芙,是天一觀的道長(zhǎng)没隘。 經(jīng)常有香客問我,道長(zhǎng)禁荸,這世上最難降的妖魔是什么右蒲? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮赶熟,結(jié)果婚禮上瑰妄,老公的妹妹穿的比我還像新娘。我一直安慰自己映砖,他們只是感情好间坐,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著邑退,像睡著了一般竹宋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上瓜饥,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天逝撬,我揣著相機(jī)與錄音,去河邊找鬼乓土。 笑死宪潮,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的趣苏。 我是一名探鬼主播狡相,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼食磕!你這毒婦竟也來(lái)了尽棕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤彬伦,失蹤者是張志新(化名)和其女友劉穎滔悉,沒想到半個(gè)月后伊诵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡回官,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年曹宴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片歉提。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡笛坦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出苔巨,到底是詐尸還是另有隱情版扩,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布侄泽,位于F島的核電站礁芦,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏悼尾。R本人自食惡果不足惜宴偿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望诀豁。 院中可真熱鬧,春花似錦窥妇、人聲如沸舷胜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)烹骨。三九已至,卻和暖如春材泄,著一層夾襖步出監(jiān)牢的瞬間沮焕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工拉宗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留峦树,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓旦事,卻偏偏與公主長(zhǎng)得像魁巩,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子姐浮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

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