好程式員web前端帶你了解JS的作用域鍊,我們都知道js是一個基于對象的語言,系統内置各種對象。
而window作為一個天然存在的全局對象,它承擔了所有全局資源的存儲。
我們使用的任何全局變量,都是window下的。也就是說,在js中,實際上沒有任何對象、方法是可以獨立的,它們必須依賴于某個對象才可以被通路或執行。
就像alert(),它的完整寫法是window.alert()
parseInt(), 完整寫法是window.parseInt()
所有放在window對象下的資源,通路時可以預設省略window
但有一種情況非常特殊,例如函數中的局部變量:
function Person(){
var name = “abc”;
}
當我們試圖通路這個name屬性時
console.log(newPerson().name);
結果是undefined
我們隻能在函數内部通路:
console.log(name);
這種屬性,在構造函數中,也被稱為私有屬性,我們必須提供一種對外公開的方法才可以被外界通路。例如:
this.getName = function(){
returnname;
this.setName= function(_name){
name = _name;
這是一個典型的作用域問題, 似乎我們每個人都知道。
但這似乎也違反了我們的一個常識:那就是在js中,所有資源都必須依賴對象才能存在,不可獨立使用。比如說:
function aaa(){
function bbb(){ }
bbb();
這段代碼看上去并沒有錯,但是請問bbb這個函數為什麼可以獨立存在呢?如果我們把它換成這樣:
window.bbb();
結果是運作錯誤!
那如果換成這樣呢?
this.bbb();
結果還是運作錯誤!
那麼我們不禁要發問了,bbb這個函數到底是屬于哪個對象的?
當我們在調用一個函數的時候,浏覽器會為這個函數的執行開辟一塊記憶體區域用來存儲這個方法在執行的臨時資料。而對象作為js的基本存儲機關,是以,臨時資料實際上都被儲存到了一個對象當中,這個對象,就是我們平時所說的執行上下文環境
當我們調用aaa函數時,例如window.aaa()
浏覽器會為這一次函數的執行,建立一個執行上下文環境對象,我們暫時給它起個名字,叫做contextAAA吧,當然它是臨時的,因為函數執行完它也就消失了。
那我們的代碼實際上會變成這樣:
function bbb(){ }
contextAAA.bbb();
盡管contextAAA對象是看不見的,但它确實存在。
而當我們執行bbb函數時,浏覽器會再次為這一次函數調用建立一個臨時的執行上下文環境,我們暫且叫它contextBBB
那麼contextAAA 和contextBBB以及window之間會形成鍊條關系,
舉個例子來說明吧
var num = 888;
var num = 100;
function bbb(){
var num= 200;
console.log(num);
bbb();
aaa();
那麼contextAAA 如下:
contextAAA = {
num :100,
bbb : function(){ … },
parentContext: window//父級上下文對象
那麼contextBBB如下:
contextBBB = {
num : 200,
parentContext: contextAAA //父級上下文對象
是以我們發現,在父級上下文對象中,我們沒有辦法通路到子級上下對象,這是一個單向連結清單,這就是全局不能通路局部的原因。
而bbb函數中列印出的num應該是多少呢?這取決在上下文對象中的查找順序,順序大概是這樣的:
首先在目前上下文對象contextBBB中,找一下有沒有num變量,找到就直接列印。以我們目前的代碼看,結果應該是200
我們把代碼改造一下:
var num= 888;
由于這次在contextBBB對象中找不到num變量了,是以它會從父級上下文對象中查找,也就是contextAAA裡面的num,是以列印的結果是100;
我們再把代碼改造一下
由于這次連contextAAA對象裡也找不到了,會再次向它的父級上下文對象,也就是window查找,是以列印結果是888
contextAAA和contextBBB的父子關系,在你寫代碼的一刻就決定了,這就是作用域。
var num = 10;
function bbb(){ console.log(num); }
而代碼執行時,産生的上下文對象,是連結清單的關系。這就是我們所說的作用域鍊,它的原理跟原型鍊是一樣的。

了解了這一點,也能弄明白閉包的原理。
var num = 10;
return functionbbb(){ console.log(num); }
aaa( )( )
盡管bbb函數通過return在全局範圍被執行了,但作用域的連結清單關系并沒有發生改變,是以,bbb函數依然可以通路num這個局部變量。