JS閉包作用域解析

airland發表於2021-09-09

什麼是閉包?

簡單理解,當在一個函式的外部訪問函式內部定義的變數的時候就會形成一個閉包,由這個理解可以知道,當一個函式執行完成的時候,一般情況下,其作用域會被銷燬,其內部定義的變數也會變得不可訪問,所以閉包打破了這個現象。閉包造成一個函式執行完成之後,其建立的作用域不會被銷燬,因為它被函式外部的物件或者變數所引用。由此可知,閉包可以實現作用域的延時存在,但這也會造成記憶體的洩露。所以在明確知道自己需要使用閉包的時候,採取使用閉包,否則就不要使用閉包。

Eg:

function demo(){

var internal=10;

return function (){

           alert(internal);

};

}

demo()();

以上的程式碼執行之後會彈窗顯示10.我們在demo方法的外部列印了其內部的變數,這個就是閉包。

  1. 作用域產生的原理

我們知道,當執行一個函式的時候,其訪問變數的順序是,首先在其自己的作用域上查詢,如果查詢不到,則會往其上層作用域來查詢,一直查詢到最終根作用域(也就是window物件),如果還是查詢不到,則會丟擲一個undefined的錯誤。

那麼作用域是如何產生的呢?

首先在執行js程式碼之前,會產生一個全域性作用域GO,也就是window物件(後面不再說明),而對於一個函式執行之前會建立自己的作用域AO,並持有其上層函式的作用域,以形成一個作用域鏈。

作用域建立的基本步驟:

1、  建立一個AO,並持有上層函式的作用域

2、  初始化形參和內部宣告的變數(變數宣告的提升),並初始化為undefined

3、  將形參和實參相統一

4、  函式定義整體提升

以一下程式碼為例

function a(){

var a1=0;

function b(){

         var b1=1;

         function c(){

                   var c=2;

}

c();

}

b();

}

a();

當js執行之前會建立一個GO

GO{

a:function()…

}

執行a函式的時候會建立a函式的作用域AO

a_AO{

           AO_chain:GO,

           a1:0,

           b:function()…

}

由以上定義可知

b的AO為

b_AO{

AO_Chain:a_AO,

b1:1,

c:function()…

}

c的作用域與此類似,由於此處重點是為了根據作用域來解釋閉包的原因,所以這裡不再詳細的說明作用域,如不瞭解請自行百度。

  1. 閉包

eg:有如下一個函式

function demo(){

var arr=[];

for(var i=0;i<10;i++){

         arr[i]=function(){

         document.write(i+ “ ”);

}

}

return arr;

}

 

var arr=demo();

 

for(var j=0;j<10;j++){

arr[j]();

}

熟悉閉包的人都知道最後列印的結果是10個10

 10 10 10 10 10 10 10 10 10 10

大家知道這是由於閉包對變數的持有造成的,那麼根據作用域的原理是怎麼產生的呢?

  1. GO

GO{

   arr = undefined,

   demo = function()…

}

  1. demo 的AO

d_AO{

ao_chain:GO,

   arr=[],//用於存放10個函式

   i=0

}

  1. 當執行完成 arr=demo[]之後;

d_AO{

   ao_chain:GO,

arr=[function()…,function()…{}],//10個函式,每個函式都是//function(){document.write(i + ‘ ’)}

i=10

}

  1. 執行arr陣列中的函式的時候

a_AO{

   ao_chain:d_AO

}

從上面的作用域鏈可以知道,當陣列的函式想要訪問i變數的時候,發現它自己的作用域中沒有這個變數,所用會透過其作用域鏈進行查詢,然後在d_AO中找到了i物件,此時i物件的值變為了10,所以arr陣列中的所有的函式最後列印的結果都是一樣的。

 

為了解決這個問題,可以使用立即執行函式

如下

function demo(){

var arr=[];

for(var i=0;i<10;i++){

         (function(j){arr[j]=function(){

         document.write(j+ “ ”);

}})(i);

}

return arr;

}

 

var arr=demo();

 

for(var j=0;j<10;j++){

arr[j]();

}

結果如圖

 0 1 2 3 4 5 6 7 8 9

從作用域的解釋來看(如果對立即執行函式不瞭解的,請百度查閱立即執行函式的相關內容)

  1. GO

GO{

   arr = undefined,

   demo = function()…

}

  1. demo 的AO

d_AO{

ao_chain:GO,

   arr=[],//用於存放10個函式

   i=0

}

前兩步與上面的方法建立的作用域相同

第3步開始有些差異

  1. 執行demo函式的時候

在執行demo函式的時候,因為內部存在一個立即執行函式,所以這個匿名函式會被立即執行,它也會建立自己的作用域

n_AO{

ao_chain:d_AO,

j:n//這裡的n儲存的是i的當前值,例如第一個n對應的為j:0

}

//對應的arr[j]=function(){document.write(j+ “ ”);},j不會被替換,仍然是保持引用

  1. 執行arr函式的時候

arr_AO{

   ao_chain:n_AO//例如arr[1]對應的就是ao_chain:1_AO

}

從上面的作用域鏈可以看出,當執行arr中的函式的時候,例如執行arr[2]的時候,首先到自己的作用域查詢j變數,發現找不到j,於是沿著作用域鏈進行查詢,首先查詢2_AO,在2_AO中找到了j變數,此時j變數的值為2,於是列印的結果就為2,這樣我們就實現了我們得需要。

 

以上解釋若有錯誤,還望指點,包涵。謝謝!

作者:此坑已滿

原文連結:https://www.cnblogs.com/ckym/p/10435092.html


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

相關文章