好程序員web前端帶你了解JS的作用域鏈
好程序員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
我們只能在函數內部訪問:
function Person(){
var name = “abc”;
console.log(name);
}
這種屬性,在構造函數中,也被稱為私有屬性,我們必須提供一種對外公開的方法才可以被外界訪問。例如:
function Person(){
var
this.getName = function(){
returnname;
}
this.setName= function(_name){
name = _name;
}
}
這是一個典型的作用域問題, 似乎我們每個人都知道。
但這似乎也違反了我們的一個常識:那就是在js中,所有資源都必須依賴對象才能存在,不可獨立使用。比如說:
function aaa(){
function bbb(){ }
bbb();
}
這段代碼看上去並沒有錯,但是請問bbb這個函數為什麽可以獨立存在呢?如果我們把它換成這樣:
function aaa(){
function bbb(){ }
window.bbb();
}
結果是運行錯誤!
那如果換成這樣呢?
function aaa(){
function bbb(){ }
this.bbb();
}
結果還是運行錯誤!
那麽我們不禁要發問了,bbb這個函數到底是屬於哪個對象的?
當我們在調用一個函數的時候,瀏覽器會為這個函數的執行開辟一塊內存區域用來存儲這個方法在執行的臨時數據。而對象作為js的基本存儲單位,因此,臨時數據實際上都被保存到了一個對象當中,這個對象,就是我們平時所說的執行上下文環境
當我們調用aaa函數時,例如window.aaa()
瀏覽器會為這一次函數的執行,創建一個執行上下文環境對象,我們暫時給它起個名字,叫做contextAAA吧,當然它是臨時的,因為函數執行完它也就消失了。
那我們的代碼實際上會變成這樣:
function aaa(){
function bbb(){ }
contextAAA.bbb();
}
盡管contextAAA對象是看不見的,但它確實存在。
而當我們執行bbb函數時,瀏覽器會再次為這一次函數調用創建一個臨時的執行上下文環境,我們暫且叫它contextBBB
那麽contextAAA 和contextBBB以及window之間會形成鏈條關系,
舉個例子來說明吧
var num = 888;
function aaa(){
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;
function aaa(){
var num = 100;
function bbb(){
console.log(num);
}
bbb();
}
aaa();
由於這次在contextBBB對象中找不到num變量了,因此它會從父級上下文對象中查找,也就是contextAAA裏面的num,因此打印的結果是100;
我們再把代碼改造一下
var num= 888;
function aaa(){
function bbb(){
console.log(num);
}
bbb();
}
aaa();
由於這次連contextAAA對象裏也找不到了,會再次向它的父級上下文對象,也就是window查找,因此打印結果是888
contextAAA和contextBBB的父子關系,在你寫代碼的一刻就決定了,這就是作用域。
function aaa(){
var num = 10;
function bbb(){ console.log(num); }
}
而代碼執行時,產生的上下文對象,是鏈表的關系。這就是我們所說的作用域鏈,它的原理跟原型鏈是一樣的。
理解了這一點,也能弄明白閉包的原理。
function aaa(){
var num = 10;
return functionbbb(){ console.log(num); }
}
aaa( )( )
盡管bbb函數通過return在全局範圍被執行了,但作用域的鏈表關系並沒有發生改變,因此,bbb函數依然可以訪問num這個局部變量。
好程序員web前端帶你了解JS的作用域鏈