摘要:函數作用域要理解閉包,必須從理解函數被調用時都會發生什么入手。可以說,閉包是函數作用域的副產品。無論通過何種手段將內部函數傳遞到所在的詞法作用域以外,它都會持有對原始定義作用域的引用,無論在何處執行這個函數都會使用閉包。
函數作用域
要理解閉包,必須從理解函數被調用時都會發生什么入手。
我們知道,每個javascript函數都是一個對象,其中有一些屬性我們可以訪問到,有一些不可以訪問,這些屬性僅供JavaScript引擎存取,是隱式屬性。[[scope]]就是其中一個。
[[scope]]就是我們所說的作用域,其中存儲了執行期上下文的集合。由于這個集合呈鏈式鏈接,我們把這種鏈式鏈接叫做作用域鏈。
當函數被定義(創建)時有一個自己所在環境的作用域(GO全局作用域 ,若是在函數內部,就是引用別人的作用域),當函數被執行時,會將自己的獨一無二的AO(活動對象,是使用arguments和該函數內部的變量值初始化的活動對象)執行上下文放在前端,形成一個作用域鏈;當該函數執行完,自己的AO會被干掉,回到被定義時的狀態。
另外,變量的查找,就是找所在函數的作用域,首先從作用域的頂端開始查找,找不到的情況下,會查找外部函數的活動對象,依次向下查找,直到到達作為作用域鏈終點的全局執行環境。
下面看幾個查找變量例子,深入理解函數作用域及作用域鏈。
function a(){ function b(){ var b=2223; } var a=78; } a() b() console.log(b)
輸出結果: error: b is not defined
當函數a執行完畢后,該函數內部的活動對象AO就會被銷毀。所以函數外部是訪問不到函數內部的變量的。
function outer(){ function inner(){ var b=2223; a=0 } var a=78; inner() //① console.log(a) console.log(b) } outer()
輸出結果: 0 , error: b is not defined
當函數inner在被定義的階段,就會擁有(引用)函數outer的作用域(包括函數outer自己局部的活動對象AO和全局作用域);當函數inner()被執行的時候,會再創建一個自己的活動對象AO并被推入執行環境作用域鏈的前端。
inner()函數在被執行的時候,由于變量a在outer()函數中已經存在并被inner()引用,所以inner()函數內部的變量a會修改掉外部函數變量a的值,并且可以不用聲明。當inner()函數執行完畢后(執行到①處),inner()函數局部的AO會被銷毀,下面就訪問不到變量b了,而且這時候變量a的值將是被inner()函數修改過的值。
function a(){ function b(){ var b=2223; a=0 } var a=78; b=1 b() console.log(b) } a()
輸出結果: 1
var x=10; function a(){ console.log(x); } function b(){ var x=20; a(); } a();//10 b();//還是10;
總之:函數在被定義階段,會引用著其所在環境的作用域;執行階段,會創建一個自己獨一無二的活動對象AO,并推入執行環境作用域鏈的前端;函數執行完畢之后,自己的執行上下文AO會被銷毀,所以,這時候訪問其內部的變量是訪問不到的。但是,閉包的情況又有不同。
簡述什么是閉包閉包:有權訪問另一個函數作用域中的變量的函數。
從理論的角度上,所有的JavaScript函數都是閉包,因為函數在被定義階段就會存儲一個自己所在環境的作用域,可以訪問這個作用域中的所有變量。可以說,閉包是 JS 函數作用域的副產品。理解js作用域,自然就明白了閉包,即使你不知道那是閉包。
從技術實踐的角度,以下函數才算閉包:
定義該函數的執行環境的作用域(執行上下文)即使被銷毀 ,它的活動對象(AO)仍然會留在內存中,被該函數引用著。
引用了函數體外部的變量。
創建閉包常見方式,就是在一個函數A內部創建另一個函數B,然后通過return這個函數B以便在外部使用,這個函數B就是一個閉包。
舉個例子:
//也算閉包 var a = 1; function foo() { console.log(a); } foo(); //函數內部定義函數 var scope = "global scope"; function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f; } checkscope()() //這里相當于: //var foo = checkscope(); //foo();
輸出結果:local scope
f()函數在被定義階段就被保存到了外部,這個時候就相當于外部的函數可以訪問另一個函數內部的變量,f()函數會形成一個閉包。
按照函數作用域的概念,當checkscope()執行完畢后,其局部的活動對象AO會被銷毀;但是由于checkscope()函數執行完畢后返回一個函數,根據函數在被定義階段會引用該函數所在執行環境的執行上下文,被返回的函數f()即使被保存到了外部依然引用著checkscope()函數的執行期上下文,直到函數f()執行完畢,checkscope()函數的執行上下文才會被銷毀。
也就是說被嵌套的函數f()無論在什么地方執行,都會包含著外部函數(定義該函數)的活動對象。所以,即使f()被保存到外部,也可以訪問到另一個函數checkscope()中定義的變量。
無論通過何種手段將內部函數傳遞到所在的詞法作用域以外, 它都會持有對原始定義作用域的引用, 無論在何處執行這個函數都會使用閉包。
由此看來,閉包可能會導致一個問題:導致原有作用域鏈不釋放,造成內存泄漏(內存空間越來越少)。可以通過手動將被引用的函數設為null,來解除對該函數的引用,以便釋放內存。
閉包的作用實現公有變量。
function a(){ var num=100; function b(){ num++; console.log(num) } return b; } var demo=a(); demo();//101 demo();//102
function test(){ var num=100; function a(){ num++; } function b(){ num--; } return [a,b] } var demo=test() demo[0]();//101 demo[1]();//100 //函數a和函數b引用的是同一個作用域。
實現私有變量。
閉包通常用來創建內部變量,使得這些變量不能被外部隨意修改,同時又可以通過指定的函數接口來操作。
通過在立即執行函數中return 將方法保存到外部等待調用,內部的變量由于是私有的,外部訪問不到,可防止污染全局變量,利于模塊化開發。
var foo = ( function() { var secret = "secret"; // “閉包”內的函數可以訪問 secret 變量,而secret變量對于外部卻是隱藏的 return { get_secret: function () { // 通過定義的接口來訪問 secret return secret; }, new_secret: function ( new_secret ) { // 通過定義的接口來修改 secret secret = new_secret; } }; } () ); foo.get_secret (); // 得到 "secret" foo.secret; // undefined,訪問不能 foo.new_secret ("a new secret"); // 通過函數接口,我們訪問并修改了secret 變量 foo.get_secret (); // 得到 "a new secret"
var name="bcd"; var init=(function (){ var name="abc"; function callName(){ console.log(name); } //其他方法 return function () { callName(); //其他方法 } }()) init () //abc閉包經典題
function createFunctions(){ var result = new Array(); for (var i=0; i < 10; i++){ result[i] = function(){ console.log(i); }; } return result; } var fun = createFunctions(); for(var i=0;i<10;i++){ fun[i](); }
輸出結果:打印十個10
數組每個值都是一個函數,每個函數對createFunctions()形成一個閉包,此時i都是引用createFunctions()中同一個i變量。
function test(){ var arr=[]; for(var i=0;i<10;i++){ (function(j){ arr[j]=function(){ console.log(j); } }(i)) } console.log(i)//10 ,i還是10 return arr } var myArr=test(); for(var i=0;i<10;i++){ myArr[i]() }
輸出結果:從0到9
這次依然把數組每個值賦為函數,不同的是循環十次立即執行函數,并將當前循環的i作為參數傳進立即執行函數,由于參數是按值傳遞的,這樣就把當前循環的i保存下來了。
在閉包中使用this對象可能會導致一些問題,結果往往不是預想的輸出結果。
看個例子:
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ return function(){ return this.name; }; } }; alert(object.getNameFunc()());
輸出結果:The Window
this對象是在運行時基于函數的執行環境綁定的:在全局函數中,this等于window,而當函數被作為某個對象的方法調用時,this等于那個對象。匿名函數往往具有全局性,這里可以這樣理解,沒有任何對象調用這個匿名函數,雖然這個匿名函數擁有getNameFunc()的執行上下文。
因為這個匿名函數擁有getNameFunc()的執行上下文,通過把外部函數getNameFunc()作用域中的this對象保存在一個閉包能夠訪問到的變量里,就可以讓閉包訪問到該對象了。
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ var that = this; return function(){ return that.name; }; } }; alert(object.getNameFunc()());
輸出結果:My Object
理解到這里,基本上就搞定了閉包了。
學習資料JavaScript 里的閉包是什么?應用場景有哪些?
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/93121.html
摘要:的變量作用域是基于其特有的作用域鏈的。需要注意的是,用創建的函數,其作用域指向全局作用域。所以,有另一種說法認為閉包是由函數和與其相關的引用環境組合而成的實體。 作用域 定義 在編程語言中,作用域控制著變量與參數的可見性及生命周期,它能減少名稱沖突,而且提供了自動內存管理 --javascript 語言精粹 我理解的是,一個變量、函數或者成員可以在代碼中訪問到的范圍。 js的變量作...
摘要:作用域分為詞法作用域和動態作用域。這樣就形成了一個鏈式的作用域。一般情況下,當函數執行完畢時,里面的變量會被自動銷毀。而能夠訪問到這個在的編譯階段就已經定型了詞法作用域。 什么是作用域?在當前運行環境下,可以訪問的變量或函數的范圍。作用域分為詞法作用域和動態作用域。詞法作用域是在js代碼編譯階段就確定下來的; 對應的,with和eval語句會產生動態作用域。 會產生新的作用域的情況: ...
摘要:是詞法作用域工作模式。使用可以將變量綁定在所在的任意作用域中通常是內部,也就是說為其聲明的變量隱式的劫持了所在的塊級作用域。 作用域與閉包 如何用js創建10個button標簽,點擊每個按鈕時打印按鈕對應的序號? 看到上述問題,如果你能看出來這個問題實質上是考對作用域的理解,那么恭喜你,這篇文章你可以不用看了,說明你對作用域已經理解的很透徹了,但是如果你看不出來這是一道考作用域的題目,...
摘要:是詞法作用域工作模式。使用可以將變量綁定在所在的任意作用域中通常是內部,也就是說為其聲明的變量隱式的劫持了所在的塊級作用域。 作用域與閉包 如何用js創建10個button標簽,點擊每個按鈕時打印按鈕對應的序號? 看到上述問題,如果你能看出來這個問題實質上是考對作用域的理解,那么恭喜你,這篇文章你可以不用看了,說明你對作用域已經理解的很透徹了,但是如果你看不出來這是一道考作用域的題目,...
摘要:是詞法作用域工作模式。使用可以將變量綁定在所在的任意作用域中通常是內部,也就是說為其聲明的變量隱式的劫持了所在的塊級作用域。 作用域與閉包 如何用js創建10個button標簽,點擊每個按鈕時打印按鈕對應的序號? 看到上述問題,如果你能看出來這個問題實質上是考對作用域的理解,那么恭喜你,這篇文章你可以不用看了,說明你對作用域已經理解的很透徹了,但是如果你看不出來這是一道考作用域的題目,...
閱讀 863·2021-10-11 10:59
閱讀 2798·2019-08-30 15:43
閱讀 2132·2019-08-30 11:08
閱讀 1653·2019-08-29 15:20
閱讀 1007·2019-08-29 13:53
閱讀 489·2019-08-26 13:24
閱讀 1636·2019-08-26 13:24
閱讀 2824·2019-08-26 12:08