JavaScript是基于詞法作用域的語言:通過閱讀包含變量定義在內(nèi)的數(shù)行源碼就能知道變量的作用域。全局變量在程序中始終是有定義的考杉。局部變量在聲明它的函數(shù)體內(nèi)以及所嵌套的函數(shù)內(nèi)始終是有定義的。
Tips:
1.在函數(shù)體內(nèi),局部變量的優(yōu)先級高于同名的全局變量。
2.在全局作用域編寫代碼是可以不寫var語句踱承,但聲名局部變量時,必須使用var語句哨免,如果省略 var 操作符茎活,那么聲明的變量就是全局變量,擁有全局作用域铁瞒。
3.JS中沒有塊級作用域妙色,而是使用函數(shù)作用域:變量在聲明它們的函數(shù)體內(nèi)以及所嵌套的任意函數(shù)內(nèi)始終是有定義的桅滋。這意味著變量在聲明之前甚至已經(jīng)可用慧耍,即JS的聲明提前(hoisting),意思是JS函數(shù)里聲明的所有變量(但不涉及賦值)都被“提前”至函數(shù)的頂部丐谋。
如果將一個局部變量看作是自定義實現(xiàn)的對象的屬性的話芍碧,那么可以換個角度來解讀變量作用域。每一段JavaScript代碼(全局代碼或函數(shù))都有一個與之關(guān)聯(lián)的作用域鏈(scope chain)号俐。這個作用域鏈?zhǔn)且粋€對象列表或者鏈表泌豆,這組對象定義了這段代碼“作用域中”的變量。當(dāng)JavaScript需要查找變量x的值的時候(這個過程稱作“變量解析”(variable resolution))吏饿,它會從鏈中的第一個對象開始查找踪危,如果這個對象有一個名為x的屬性,則會直接使用這個屬性的值猪落,如果第一個對象中不存在名為x的屬性贞远,JavaScript會繼續(xù)查找鏈上的下一個對象。如果第二個對象依然沒有名為x的屬性笨忌,則會繼續(xù)查找下一個對象蓝仲,以此類推。如果作用域鏈上沒有任何一個對象含有屬性x官疲,那么就認(rèn)為這段代碼的作用域鏈上不存在x袱结,并最終拋出一個引用錯誤(ReferenceError)異常。
我們可以認(rèn)為:
1.函數(shù)的局部環(huán)境可以訪問函數(shù)作用域中的變量途凫,也可以訪問和操作父環(huán)境乃至全局環(huán)境中的變量垢夹。
2.父環(huán)境只能訪問其包含環(huán)境和自己環(huán)境中的變量和函數(shù),不能訪問其子環(huán)境中的變量和函數(shù)维费。
3.全局環(huán)境只能訪問全局環(huán)境中的變量和函數(shù)棚饵,不能直接訪問局部環(huán)境中的任何數(shù)據(jù)煤裙。
在JavaScript最頂層代碼中(也就是不包含在任何函數(shù)定義內(nèi)的代碼),作用域鏈由一個全局對象組成噪漾。在不包含嵌套的函數(shù)體內(nèi)硼砰,作用域鏈上有兩個對象,第一個是定義函數(shù)參數(shù)和局部變量的對象欣硼,第二個是全局對象题翰。在一個嵌套的函數(shù)體內(nèi),作用域鏈上至少有三個對象诈胜。理解對象鏈的創(chuàng)建規(guī)則是非常重要的豹障。當(dāng)定義一個函數(shù)時,它實際上保存一個作用域鏈焦匈。當(dāng)調(diào)用這個函數(shù)時血公,它創(chuàng)建一個新的對象來存儲它的局部變量,并將這個對象添加至保存的那個作用域鏈上缓熟,同時創(chuàng)建一個新的更長的表示函數(shù)調(diào)用作用域的“鏈”累魔。對于嵌套函數(shù)來講,事情變得更加有趣够滑,每次調(diào)用外部函數(shù)時垦写,內(nèi)部函數(shù)又會重新定義一遍。因為每次調(diào)用外部函數(shù)的時候彰触,作用域鏈都是不同的梯投。內(nèi)部函數(shù)在每次定義的時候都有微妙的差別——在每次調(diào)用外部函數(shù)時,內(nèi)部函數(shù)的代碼都是相同的况毅,而且關(guān)聯(lián)這段代碼的作用域鏈也不相同.
(摘抄參考于《JS權(quán)威指南》和《JS高級程序設(shè)計》)