js深刻(三)做用域鏈與閉包

在以前咱們根絕對象的原型說過了js的原型鏈,那麼一樣的js 萬物皆對象,函數也一樣存在這麼一個鏈式的關係,就是函數的做用域鏈面試

做用域鏈

首先先來回顧一下以前講到的原型鏈的尋找機制,就是實例會先從自己開始找,沒有的話會一級一級的網上翻,直到頂端沒有就會報一個undefined閉包

一樣的js的機制就是這樣的,函數在執行的時候會先函數自己的上下文的變量對象中查找,沒有的話,也會從這個函數被建立的時候的父級的執行上下文的變量對象中去找(詞法環境),一直找到全局上下文的變量對象(好比客戶端的window對象),這個多層的執行上下文的鏈式關係就是函數的做用域鏈函數

盜一張圖this

做用域被建立的時機

你們能夠看到,我在控制檯聲明瞭一個函數,而且打印了他,這個a函數的裏邊有一個[[scope]]屬性,
這是一個內部屬性,當一個函數被建立的時候,會保存全部的父級的變量對象(詞法環境)到這個裏邊,好比說上圖中 就有一個global 屬性展開後,往下找你會發現不少咱們常見的屬性和方法,好比alert等等code

如圖對象

函數做用域的生命週期

姑且叫他生命週期,我是這麼理解的,當進入一個函數的上下文,經歷了建立階段以後,就會把函數的做用域鏈建立出來,直到銷燬這個上下文,這個做用域鏈也是存在的blog

先來一個正經的例子生命週期

function a(){
    var aaa = 'aaa';
    return aaa;
}
checkscope();

這個函數的生命週期是這樣的圖片

  • 首先函數被建立,先把函數的做用域鏈保存到函數的[[scope]]屬性上邊原型鏈

    a.[[scope]] = [
    globalContext.VO//這個也就是咱們上邊圖片裏邊看的golbal
    ];

globalContext 全局上下文 VO 這個以前沒有介紹 是Variable object的簡稱,也就是以前常常提到的變量對象
還有一個AO ,這個AO指的是函數被激活的時候(被執行)得活動對象

  • 建立完成以後,執行到a函數,建立了a函數得執行上下文,並壓入執行棧裏邊

如今執行棧裏邊已經有了兩個執行上下文一個globalContext還有一個aContext

  • 到了a函數以後,首先會作一些列得準備工做,就是以前講到得函數得arguments,this等等

首先第一步複製以前得[[scope]]屬性,建立做用域鏈

aContext = {
    Scope: a.[[scope]],
}

而後開始初始化活動變量 argments對象 形參,函數聲明,變量聲明等等

最後把把活動變量也塞到做用域鏈中去

以上,一個函數得準備工做就算是作完了,而後下一步就是函數得執行階段

  • 以前講過,在以後階段得時候函數會根據代碼給以前得活動對象賦值,而後執行裏邊得代碼,直到執行完畢

  • 最後,函數執行完畢,函數得上下文被從上下文棧中彈出銷燬

在彈出得最後時候,a函數得結構大概長成這個樣子

aContext = {
    AO: {
        arguments: {
            length: 0
        },
    },
    Scope: [AO, [[Scope]]]
}

接下來咱們在舉一個不正經得例子,就是爲了證實一下做用域鏈即便在函數被銷燬後,也會存在這麼一個事實

閉包

首先什麼是閉包,閉包是指在一個函數內部可以訪問不是函數得參數,也不是局部變量得函數,因此廣義得講咱們用的全部得函數都是可算做是閉包,都能訪問全局變量。。。

不過工做中不是這樣子得,說正題,給上邊得問題舉個例子

var item = '1'
function a(){
    var item = '2'
    function b(){
        return item
    }
    return b;
}

var foo = a();
foo();

試着猜測一下這段代碼得執行過程

仍是來一步一步得解釋一下

  • 首先不用多想,進入全局代碼,建立全局執行上下文,推入執行棧,全局上下文得一系列初始化

  • 而後建立a , 建立上下文,推入執行棧,一些列得初始化

在執行a得時候建立了b函數,這個時候,還記得上邊以前說過得把,做用域鏈是在被建立得時候肯定得

這個時候得b函數得做用域鏈應該是這個樣子的

bContext = {
    Scope: [AO, aContext.AO, globalContext.VO],
}

這個是重點,咱們先把執行過程說完

  • 在a函數執行完畢以後,a的上下文棧被彈出

  • 而後在後邊執行b函數,而後同樣的套路,進上下文壓入棧

  • 進棧一些列的初始化

  • 執行完畢b的上下文被彈出

上邊已經把順序說的很清楚了對吧, 執行過程是a進棧出棧,b進棧出棧,可是你打印這段代碼的時候
會打印出一個2,就是由於雖說a的上下文被銷燬了,可是b的做用域鏈裏邊仍是有a的活動對象的
,在b的上下文裏邊能夠找到這個item

這也就是咱們以前所說的閉包,也符合閉包的定義

  • 建立他的函數的上下文被銷燬,可是他依然存在

  • 在代碼中引用了不是自身的參數或者局部變量

最後放一個網上很常見的面試題

var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}

data[0]();
data[1]();
data[2]();

從做用域鏈的角度思考一下會打印出什麼結果,爲何會打印出這個結果

以上是我對js的做用域鏈和閉包的一些認識,有不足之處,但願批評指正

相關文章
相關標籤/搜索