web前端學習教程:JS的作用域鏈

Eric_V發表於2019-05-13

我們都知道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(){

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之間會形成鏈條關係,如圖

#FormatImgID_0#

舉個例子來說明吧

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); }

}

而程式碼執行時,產生的上下文物件,是連結串列的關係。這就是我們所說的作用域鏈,它的原理跟原型鏈是一樣的。


web前端學習教程:JS的作用域鏈

理解了這一點,也能弄明白閉包的原理。

function aaa(){

var num = 10;

return functionbbb(){ console.log(num); }

}

aaa( )( )

儘管bbb函式通過return在全域性範圍被執行了,但作用域的連結串列關係並沒有發生改變,因此,bbb函式依然可以訪問num這個區域性變數。


相關文章