JavaScript閉包的深入理解

来源:https://www.cnblogs.com/gulei/archive/2018/01/19/8317746.html
-Advertisement-
Play Games

閉包算是javascript中一個比較難理解的概念,想要深入理解閉包的原理,首先需要搞清楚其他幾個概念: 一、棧記憶體和堆記憶體 學過C/C++的同學可能知道,電腦系統將記憶體分為棧和堆兩部分(大學的基礎課,忘掉的趕緊重新撿起來)。 棧記憶體(連續的存儲空間,類似數據結構中的棧):主要用來存放數值、字元、 ...


閉包算是javascript中一個比較難理解的概念,想要深入理解閉包的原理,首先需要搞清楚其他幾個概念:

一、棧記憶體和堆記憶體

學過C/C++的同學可能知道,電腦系統將記憶體分為棧和堆兩部分(大學的基礎課,忘掉的趕緊重新撿起來)。

棧記憶體(連續的存儲空間,類似數據結構中的棧):主要用來存放數值、字元、記憶體地址等小數據

堆記憶體(散列的存儲空間,類似數據結構中的鏈表):存放可以動態變化的大數據

 

二、基本類型和引用類型

JavaScript將變數分為兩種類型:

基本類型:Number、String、Boolean 、undefined、null(值被保存在棧記憶體中)

引用類型:Object、Array、function(具體內容被保存在堆記憶體中,在棧記憶體中僅保存堆記憶體的地址)

如上圖,當在程式中在執行中有如下情況:

1、聲明變數a為基本類型時,直接在棧記憶體中保存它的值為100;

2、當將a賦值給b時,b在棧記憶體中新建空間,將a的值複製過來

(註:之後a和b就沒有關係了,再改變a或b的值,不影響另外一個,它們是獨立的)

3、聲明變數p1為引用類型時,將p1的內容保存在堆記憶體中,並將堆記憶體的物理地址保存在棧記憶體中

4、當將p1賦值給p2時,p2在棧記憶體中新建空間,僅複製堆記憶體的物理地址

(註:p1和p2中都保存的是指向堆記憶體的地址,即指的是同一個對象,當修改p1對象的屬性後,p2對象的屬性同時被修改)

 

另外,在電腦語言中還有一些很重要的特性:

1、修改基本類型的值,實際上是新建空間存一個新值,然後將變數名指向新的空間(舊值依然存在棧記憶體中,只是缺少變數名指向它)

2、刪除引用類型,其實並不刪除堆記憶體中的內容,僅刪除了棧記憶體中的物理地址(對象的內容依然存在堆記憶體中,只是缺少了地址的指向)

(註:電腦關於記憶體的管理,跟我們正常想到的不一樣,例如硬碟恢復就是利用這個原理,為刪除的內容重新建立一個指向即可訪問)

 

二、變數作用域

javascript中變數又分為全局變數和局部變數

全局變數:在全局環境中聲明的變數

局部變數:在函數中聲明的變數

當函數在執行時,會創建一個封閉的執行期上下文環境,函數內部聲明的變數僅可在函數內部使用,外部無法訪問,而全局變數則在任何地方都可以使用

 

三、預編譯

JavaScript的運行為三步:語法分析》預編譯》解釋執行

1、語法分析:通篇掃描js文件,檢查是否有低級語法錯誤

2、預編譯四部曲:(發生在解釋執行的前一刻)
  a、創建AO對象(執行期上下文對象,全局為GO)
  b、將形參和變數聲明作為AO對象的屬性名,值為undefined
  c、將實參值傳遞給形參,即賦值給AO對象對應屬性名
  d、將函數聲明為AO對象的方法名,值為函數體

3、解釋執行:解釋一行,執行一行。

function test(a){
  var b=1;
 function c(){}  
}
test(2);
/* 函數預編譯四部曲(函數執行前一刻,不執行不會預編譯),全局預編譯同理
 * 1---testAO{}
 * 2---testAO{a:undefined,b:undefined}
 * 3---testAO{a:2,b:undefined}
 * 4---testAO{a:2,b:1,c:function(){}}
 */

 

四、作用域鏈

每個JavaScript函數都是一個對象,對象中有些屬性可以訪問(比如name),有些屬性不可以訪問(比如[[scope]]僅供js引擎使用)

[[scope]]用來存儲了運行期上下文對象的集合(即作用域鏈),作用域鏈中除了自身創建的AO對象外,還包括了所有父級運行期上下文對象(AO)

function a(){
  function b(){
    var b = 234;   
  }
  var a = 123;
  b();
}
var glob = 100;
a();

 

當b執行完成後,b的AO要被銷毀,即b的[[scope]]第0位將被置空,如果再次執行b,將新建一個新的AO將其地址存到第0位,

當a也執行完成後,a的AO要被銷毀,即a的[[scope]]第0位將被置空,同時a的AO中存著b,b也將被一同銷毀

 

在瞭解如上這些概念後,我們再來看下麵這個經典的閉包,你會有一個全新的認識

function a(){
  var b=123;
  function c(){
    console.log(b+=1);
  }
  return c;
}
var d=a();
d();

當這段代碼在執行時的順序如下:

1、預編譯全局,生成執行上下文對象GO{d:undefined,a:function(){}}

2、定義a函數,將a函數的[[scope]]屬性設置為{0:GO}

3、預編譯a函數,生成a的執行上下文對象aAO{b:undefined,c:function(){}},修改a函數的[[scope]]屬性為{0:aAO,1:GO}

4、執行a函數,給aAO的屬性賦值{b:123,c:function(){}}

5、定義c函數,將c函數的[[scope]]屬性設置為{0:aAO,1:GO},並將c返回給d

6、a函數執行完畢,銷毀[[scope]]屬性第0位對aAO對象的引用

7、執行d函數(等於執行c函數)之前,先預編譯生成c的執行上下文對象cAO{},修改c函數的[[scope]]屬性為{0:cAO,1:aAO,2:GO}

8、執行c函數,b變數在cAO中沒有,到[[scope]]屬性中的下一位aAO中獲取

 

 

 

我的博客即將搬運同步至騰訊雲+社區,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1, 轉義字元 轉義字元:用於表示網頁中的特殊字元 XHTML不直接輸入符號,建議使用轉義字元。 &nbsp 空格; &copy 版權; &reg 註冊商標 如果輸入連續的空格要使用&的轉義字元&(&amp;)nbsp,即&amp;nbsp; 2, 水平線 <Hr> 水平線 <hr width="5 ...
  • xss攻擊(跨站腳本) 是網站應用程式的安全泄露攻擊,是代碼註入的一種。它允許惡意用戶將代碼註入到網頁上,其他用戶在觀看網頁時就會受到影響。 攻擊原理 其特點是不對伺服器端造成任何傷害,而是通過一些正常的站內交互途徑,例如發佈評論,提交含有 JavaScript 的內容文本。這時伺服器端如果沒有過濾 ...
  • 前端框架層出不窮,不過萬變不離其宗,就是從MVC過渡到MVVM。從數據映射到DOM,angular中用的是watcher對象,vue是觀察者模式,react就是state了。 React通過管理狀態實現對組件的管理,通過this.state()方法更新state。當this.setState()被調 ...
  • 在網上找了好久針對react-native的測試方法,但是沒有找到靠譜的方式。要麼很淺只是跑了一下官方的例子,要麼就是版本有點老舊,照著無法進行。jest提供的react-native例子很少,而enzyme提供的react-native-mock庫也是各種報錯,讓人很是絕望。於是乎在搜索到的信息指 ...
  • 屬性 屬性名 屬性類型 描述說明 預設值 language String 多語言設置,使用時需提前引入\locales文件夾下對應的語言文件,中文zh,引入語言文件必須放在fileinput.js之後 'en' showCaption Boolean 是否顯示被選文件的簡介 true showBro ...
  • Skip to content This repository Search Pull requests Issues Marketplace Explore @VIVI863628 Sign out Unwatch 1 Star 0 Fork 0 VIVI863628/PouchDB Code I... ...
  • JavaScript數組去重 1、原型去重法。通過prototype找到數組的源性對象Array,在數組的原型上添加unique()方法。需要使用的時候使用 點 “ . ” 進行連接。 優點:擴展性比較高,復用性比較高。 缺點:通過給數組對象擴展,新增方法,導致數組結構中有新增了一個方法。此時如果用 ...
  • 個人JavaScript小工具,每天熟記一點點。多練習,達到熟練的效果。 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 在我們開發過程中基本上不可或缺的用到一些敏感機密數據,比如SQL伺服器的連接串或者是OAuth2的Secret等,這些敏感數據在代碼中是不太安全的,我們不應該在源代碼中存儲密碼和其他的敏感數據,一種推薦的方式是通過Asp.Net Core的機密管理器。 機密管理器 在 ASP.NET Core ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 順序棧的介面程式 目錄順序棧的介面程式頭文件創建順序棧入棧出棧利用棧將10進位轉16進位數驗證 頭文件 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> 創建順序棧 // 指的是順序棧中的元素的數據類型,用戶可以根據需要進行修改 ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • C總結與剖析:關鍵字篇 -- <<C語言深度解剖>> 目錄C總結與剖析:關鍵字篇 -- <<C語言深度解剖>>程式的本質:二進位文件變數1.變數:記憶體上的某個位置開闢的空間2.變數的初始化3.為什麼要有變數4.局部變數與全局變數5.變數的大小由類型決定6.任何一個變數,記憶體賦值都是從低地址開始往高地 ...
  • 如果讓你來做一個有狀態流式應用的故障恢復,你會如何來做呢? 單機和多機會遇到什麼不同的問題? Flink Checkpoint 是做什麼用的?原理是什麼? ...
  • C++ 多級繼承 多級繼承是一種面向對象編程(OOP)特性,允許一個類從多個基類繼承屬性和方法。它使代碼更易於組織和維護,並促進代碼重用。 多級繼承的語法 在 C++ 中,使用 : 符號來指定繼承關係。多級繼承的語法如下: class DerivedClass : public BaseClass1 ...
  • 前言 什麼是SpringCloud? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分散式系統的開發,比如服務註冊、服務發現、網關、路由、鏈路追蹤等。Spring Cloud 並不是重覆造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從 ...
  • class_template 類模板和函數模板的定義和使用類似,我們已經進行了介紹。有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。類模板用於實現類所需數據的類型參數化 template<class NameType, class AgeType> class Person { publi ...
  • 目錄system v IPC簡介共用記憶體需要用到的函數介面shmget函數--獲取對象IDshmat函數--獲得映射空間shmctl函數--釋放資源共用記憶體實現思路註意 system v IPC簡介 消息隊列、共用記憶體和信號量統稱為system v IPC(進程間通信機制),V是羅馬數字5,是UNI ...