您好,登錄后才能下訂單哦!
好程序員 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 name = “abc” ;
this .getName = function (){
return name;
}
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 function bbb(){ console .log(num); }
}
aaa( )( )
盡管 bbb函數通過return在全局范圍被執行了,但作用域的鏈表關系并沒有發生改變,因此,bbb函數依然可以訪問num這個局部變量。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。