JS閉包作用域解析


什么是閉包?

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

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,這樣我們就實現了我們得需要。

 

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM