你不知道的JavaScript之作用域

作用域和閉包

1叠纷、作用域

編譯原理:
總所周知,JavaScript是一門解釋型語言悔耘,但事實上它是一門編譯語言。

傳統(tǒng)編譯語言編譯的步驟:

  • 分詞/詞法分析(Tokenizing/Lexing)

將由字符組成的字符串分解成( 對編程語言來說)有意義的代碼塊我擂, 這些代碼塊被稱為詞法單元(token)衬以。

var a = 2;
// 分解成 var缓艳、a、=看峻、2阶淘、;
  • 解析/語法分析(Parsing)

這個過程是將詞法單元流( 數(shù)組)轉(zhuǎn)換成一個由元素逐級嵌套所組成的代表了程序語法結(jié)構(gòu)的樹。這個樹被稱為“抽象語法樹”(Abstract Syntax Tree互妓,AST)溪窒。
var a = 2;的抽象語法樹中可能會有一個叫作VariableDeclaration的頂級節(jié)點,接下來是一個叫作Identifier(它的值是a)的子節(jié)點冯勉,以及一個叫作AssignmentExpression的子節(jié)點澈蚌。AssignmentExpression節(jié)點有一個叫作NumericLiteral(它的值是2)的子節(jié)點。

  • 代碼生成

將AST轉(zhuǎn)換為可執(zhí)行代碼的過程稱被稱為代碼生成灼狰。 這個過程與語言宛瞄、 目標(biāo)平臺等息息相關(guān)。拋開具體細(xì)節(jié)交胚,簡單來說就是有某種方法可以將var a = 2;的AST轉(zhuǎn)化為一組機器指令份汗,用來創(chuàng)建一個叫作a的變量(包括分配內(nèi)存等),并將一個值儲存在a中蝴簇。

JavaScript中的編譯

  • 首先杯活,JavaScript引擎不會有大量的(像其他語言編譯器那么多的)時間用來進(jìn)行優(yōu)化,因為與其他語言不同熬词,JavaScript的編譯過程不是發(fā)生在構(gòu)建之前的旁钧。
  • 對于JavaScript來說, 大部分情況下編譯發(fā)生在代碼執(zhí)行前的幾微秒( 甚至更短5磁臁) 的時間內(nèi)均践。
  • 簡單地說, 任何JavaScript代碼片段在執(zhí)行前都要進(jìn)行編譯( 通常就在執(zhí)行前)摩幔。因此彤委,JavaScript編譯器首先會對var a = 2;這段程序進(jìn)行編譯, 然后做好執(zhí)行它的準(zhǔn)備或衡, 并且通常馬上就會執(zhí)行它焦影。

總而言之,JavaScript不會像Java那樣封断,先進(jìn)行編譯斯辰,再把編譯過后的文件拿去運行,而是直接去運行寫好的文件坡疼,但在運行的時候會作一次內(nèi)部的編譯彬呻,再逐行執(zhí)行(那么報錯到底是發(fā)生在預(yù)編譯還是代碼執(zhí)行?)。

解析作用域

三個角色:

  • 引擎:從頭到尾負(fù)責(zé)整個JavaScript程序的編譯及執(zhí)行過程闸氮。
  • 編譯器:引擎的好朋友之一剪况,負(fù)責(zé)語法分析及代碼生成等臟活累活。
  • 作用域:引擎的另一位好朋友蒲跨, 負(fù)責(zé)收集并維護(hù)由所有聲明的標(biāo)識符( 變量)組成的一系列查詢译断,并實施一套非常嚴(yán)格的規(guī)則,確定當(dāng)前執(zhí)行的代碼對這些標(biāo)識符的訪問權(quán)限或悲。

對于 var a = 2 的處理

遇到var a孙咪,編譯器會詢問作用域是否已經(jīng)有一個該名稱的變量存在于同一個作用域的集合中。 如果是巡语, 編譯器會忽略該聲明翎蹈, 繼續(xù)進(jìn)行編譯; 否則它會要求作用域在當(dāng)前作用域的集合中聲明一個新的變量捌臊,并命名為a杨蛋。
接下來編譯器會為引擎生成運行時所需的代碼,這些代碼被用來處理a = 2這個賦值操作理澎。引擎運行時會首先詢問作用域逞力,在當(dāng)前的作用域集合中是否存在一個叫作a的變量。如果是糠爬,引擎就會使用這個變量寇荧;如果否,引擎會繼續(xù)查找該變量执隧。

LHS查詢與RHS查詢

當(dāng)變量出現(xiàn)在賦值操作的左側(cè)時進(jìn)行LHS查詢揩抡,出現(xiàn)在右側(cè)時進(jìn)行RHS查詢。
RHS查詢與簡單地查找某個變量的值別無二致镀琉, 而LHS查詢則是試圖找到變量的容器本身峦嗤, 從而可以對其賦值。

var a
a = 2    // LHS查詢
console.log(a)    // RHS查詢

作用域嵌套查詢:

當(dāng)一個塊或函數(shù)嵌套在另一個塊或函數(shù)中時屋摔, 就發(fā)生了作用域的嵌套烁设。 因此, 在當(dāng)前作用域中無法找到某個變量時钓试, 引擎就會在外層嵌套的作用域中繼續(xù)查找装黑, 直到找到該變量,或抵達(dá)最外層的作用域(也就是全局作用域)為止弓熏。
當(dāng)?shù)诌_(dá)最外層的全局作用域時恋谭, 無論找到還是沒找到, 查找過程都會停止挽鞠。

LHS查詢和RHS查詢未找到預(yù)期結(jié)果的時候

LHS查詢

function foo() {
  b = 3
  return b
}
foo()
/**
 * 在執(zhí)行 b = 2 語句的時候疚颊,引擎首先會通過一次LHS查詢狈孔,試圖查詢 b 標(biāo)識符
 * 在 foo 內(nèi)部沒有找到,然后再去找上一層作用域(也就是全局作用域)材义,也沒有找到
 * 此時全局作用域就會創(chuàng)建一個變量除抛,接著引擎順利執(zhí)行了后面的賦值操作(非嚴(yán)格模式下)
 * 這也是為什么直接給一個變量賦值,會在全局作用域聲明一個同名變量并賦值
 */

RHS查詢

function bar() {
  return b
}
bar()
/**
 * 引擎通過RHS查詢首先找尋變量b
 * 在本層作用域(bar內(nèi)部)沒有找到母截,會繼續(xù)向外層作用域
 * 上一層全局作用域,依然沒有找到橄教,這時候作用域就會拋出一個錯誤
 * ReferenceError: b is not defined
 */

2清寇、詞法作用域

詞法作用域就是定義在詞法階段的作用域。 換句話說护蝶, 詞法作用域是由你在寫代碼時將變量和塊作用域?qū)懺谀睦飦頉Q定的华烟, 因此當(dāng)詞法分析器處理代碼時會保持作用域不變(大部分情況下是這樣的)。

作用域嵌套

function foo(a) {
  var b = a + 1
  function bar(c) {
    console.log(a, b, c)
  }
  bar(b * 3)
}
foo(2)

/**
 * 全局作用域  {foo: func}
 * foo作用域  {a:, b:, bar:}
 * bar作用域  {c: ,}
 */

全局變量會自動成為全局對象持灰,可通過 window.xxx 訪問盔夜。

兩種欺騙語法:

  • eval:eval是魔鬼

JavaScript中的eval(..)函數(shù)可以接受一個字符串為參數(shù), 并將其中的內(nèi)容視為好像在書寫時就存在于程序中這個位置的代碼堤魁。

function foo(str) {
  eval(str)
  console.log(a)
}
var a = 1
var evalStr = 'var a = 2'
foo(evalStr)    // 2

這段代碼會被當(dāng)作本來就在那里一樣來處理喂链。這段代碼foo(..)的詞法作用域進(jìn)行了修改,聲明了一個新的變量a妥泉。

  • with:
var obj = {
  name: 'normal',
  age: 5,
}
with(obj) {
  name = 'with'
  type = 'with'
}
/**
 * with() {}
 * () 里面表示 {} 里面寫的代碼的作用域
 */
console.log(obj)    // { name: 'with', age: 5 }

eval(..)和with會在運行時修改或創(chuàng)建新的作用域椭微, 以此來欺騙其他在書寫時定義的詞法作用域∶ち矗總而言之蝇率,不要使用它們。

3刽沾、函數(shù)作用域和塊作用域

函數(shù)作用域的含義是指本慕, 屬于這個函數(shù)的全部變量都可以在整個函數(shù)的范圍內(nèi)使用及復(fù)用( 事實上在嵌套的作用域中也可以使用)。

使用函數(shù)作用域的好處:

  • 使用函數(shù)作用域把變量包裝起來侧漓,可以避免污染全局變量
  • 可以實現(xiàn)模塊化管理锅尘,向外暴露盡量少的東西。

函數(shù)聲明和函數(shù)表達(dá)式

/**
 * es6函數(shù)新增了name屬性
 * 匿名函數(shù)在棧追蹤中不會顯示出有意義的函數(shù)名火架,使得調(diào)試很困難鉴象。
 */
function foo() {}
foo.name    // 'foo'
// 函數(shù)表達(dá)式可以是匿名的
var bar = function () {}
bar.name    // 'bar'
// 函數(shù)表達(dá)式也可以是具名的
var foo = function bar() {}
foo.name    // 'bar'

立即執(zhí)行函數(shù)

兩種形式:

  • (function () {}())
  • (function () {})()

塊級作用域

一個容易忽視的點:

for (var i = 0; i < 5; i ++) {
  console.log(i)    // 0 1 2 3 4
}
// 這里的 i 在全局也能訪問
console.log(i)    // 5 當(dāng) i 等于 5 的時候,就不符合 i < 5何鸡,就不會走后面的 i++

我們在for循環(huán)的頭部直接定義了變量i纺弊,通常是因為只想在for循環(huán)內(nèi)部的上下文中使用i,而忽略了i會被綁定在外部作用域(函數(shù)或全局)中的事實骡男。

try/catch

JavaScript的ES3規(guī)范中規(guī)定try/catch的catch分句會創(chuàng)建一個塊作用域淆游,其中聲明的變量僅在catch內(nèi)部有效。

try {
  console.log(a)
} catch (err) {
  console.log(err)    // 程序不會報錯,打印出出錯信息 a is not defined
}
console.log(err)    // 程序出錯犹菱,a is not defined

let 聲明

let關(guān)鍵字可以將變量綁定到所在的任意作用域中(通常是{ .. }內(nèi)部) 拾稳。換句話說,let為其聲明的變量隱式地了所在的塊作用域腊脱。

for(let i = 0; i < 5; i ++) {

}
console.log(i)    // 報錯
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末访得,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子陕凹,更是在濱河造成了極大的恐慌悍抑,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杜耙,死亡現(xiàn)場離奇詭異搜骡,居然都是意外死亡,警方通過查閱死者的電腦和手機佑女,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門记靡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人团驱,你說我怎么就攤上這事摸吠。” “怎么了嚎花?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵蜕便,是天一觀的道長。 經(jīng)常有香客問我贩幻,道長轿腺,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任丛楚,我火速辦了婚禮族壳,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘趣些。我一直安慰自己仿荆,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布坏平。 她就那樣靜靜地躺著拢操,像睡著了一般。 火紅的嫁衣襯著肌膚如雪舶替。 梳的紋絲不亂的頭發(fā)上令境,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機與錄音顾瞪,去河邊找鬼舔庶。 笑死抛蚁,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的惕橙。 我是一名探鬼主播瞧甩,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼弥鹦!你這毒婦竟也來了肚逸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤彬坏,失蹤者是張志新(化名)和其女友劉穎吼虎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體苍鲜,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年玷犹,在試婚紗的時候發(fā)現(xiàn)自己被綠了混滔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡歹颓,死狀恐怖坯屿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情巍扛,我是刑警寧澤领跛,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站撤奸,受9級特大地震影響吠昭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜胧瓜,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一矢棚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧府喳,春花似錦蒲肋、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至弯蚜,卻和暖如春孔轴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背碎捺。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工距糖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留玄窝,地道東北人。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓悍引,卻偏偏與公主長得像恩脂,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子趣斤,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,486評論 2 348