摘要:在之前我們根絕對象的原型說過了的原型鏈,那么同樣的萬物皆對象,函數也同樣存在這么一個鏈式的關系,就是函數的作用域鏈作用域鏈首先先來回顧一下之前講到的原型鏈的尋找機制,就是實例會先從本身開始找,沒有的話會一級一級的網上翻,直到頂端沒有就會報一
在之前我們根絕對象的原型說過了js的原型鏈,那么同樣的js 萬物皆對象,函數也同樣存在這么一個鏈式的關系,就是函數的作用域鏈
作用域鏈首先先來回顧一下之前講到的原型鏈的尋找機制,就是實例會先從本身開始找,沒有的話會一級一級的網上翻,直到頂端沒有就會報一個undefined
同樣的js的機制就是這樣的,函數在執行的時候會先函數本身的上下文的變量對象中查找,沒有的話,也會從這個函數被創建的時候的父級的執行上下文的變量對象中去找(詞法環境),一直找到全局上下文的變量對象(比如客戶端的window對象),這個多層的執行上下文的鏈式關系就是函數的作用域鏈
盜一張圖
作用域被創建的時機大家可以看到,我在控制臺聲明了一個函數,并且打印了他,這個a函數的里邊有一個[[scope]]屬性,
這是一個內部屬性,當一個函數被創建的時候,會保存所有的父級的變量對象(詞法環境)到這個里邊,比如說上圖中 就有一個global 屬性展開后,往下找你會發現很多我們常見的屬性和方法,比如alert等等
如圖
函數作用域的生命周期姑且叫他生命周期,我是這么理解的,當進入一個函數的上下文,經歷了創建階段之后,就會把函數的作用域鏈創建出來,直到銷毀這個上下文,這個作用域鏈也是存在的
先來一個正經的例子
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的作用域鏈和閉包的一些認識,有不足之處,希望批評指正
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/105504.html
摘要:在的開發者工具中,通過斷點調試,我們能夠非常方便的一步一步的觀察的執行過程,直觀感知函數調用棧,作用域鏈,變量對象,閉包,等關鍵信息的變化。其中表示當前的局部變量對象,表示當前作用域鏈中的閉包。 showImg(https://segmentfault.com/img/remote/1460000008404321); 在前端開發中,有一個非常重要的技能,叫做斷點調試。 在chrome...
摘要:圖片中的作用域鏈,是全局執行環境中的作用域鏈。然后此活動對象被推入作用域鏈的最前端。在最后調用的時候,創建先構建作用域鏈,再創建執行環境,再創建執行環境的時候發現了一個變量標識符。 從圖書館翻過各種JS的書之后,對作用域/執行環境/閉包這些概念有了一個比較清晰的認識。 栗子說明一切 第一個栗子 來看一個來自ECMA-262的栗子: var x = 10; (function foo(...
摘要:之前一篇文章我們詳細說明了變量對象,而這里,我們將詳細說明作用域鏈。而的作用域鏈,則同時包含了這三個變量對象,所以的執行上下文可如下表示。下圖展示了閉包的作用域鏈。其中為當前的函數調用棧,為當前正在被執行的函數的作用域鏈,為當前的局部變量。 showImg(https://segmentfault.com/img/remote/1460000008329355);初學JavaScrip...
摘要:作用域執行上下文變量提前函數聲明提前確定值范圍一段或者一個函數都會生成一個執行上下文全局一段變量定義函數聲明函數變量定義函數聲明參數集合變量提前代碼解析執行過程變量定義提前賦值函數聲明提前代碼解析函數聲明函數表達式執行過程執行過程執行時才能 1.作用域 執行上下文 (變量提前、函數聲明提前、確定this值、arguments) 范圍:一段或者一個函數(都會生成一個執行上下文) ...
摘要:而外層的函數不能訪問內層的變量或函數,這樣的層層嵌套就形成了作用域鏈。閉包閉包是指有權訪問另一個函數作用域中的變量的函數,創建閉包的最常見的方式就是在一個函數內創建另一個函數,通過另一個函數訪問這個函數的局部變量。 閉包是js中一個極為NB的武器,但也不折不扣的成了初學者的難點。因為學好閉包就要學好作用域,正確理解作用域鏈,然而想做到這一點就要深入的理解函數,所以我們從函數說起。 函數...
閱讀 1438·2021-09-22 15:43
閱讀 2153·2019-08-30 15:54
閱讀 1153·2019-08-30 10:51
閱讀 2081·2019-08-29 18:35
閱讀 425·2019-08-26 11:58
閱讀 2474·2019-08-26 11:38
閱讀 2432·2019-08-23 18:35
閱讀 3626·2019-08-23 18:33