好程式設計師web前端帶你瞭解JS的作用域鏈

好程式設計師IT發表於2019-05-13

好程式設計師 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這個區域性變數。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69913892/viewspace-2644218/,如需轉載,請註明出處,否則將追究法律責任。

相關文章