首頁 web前端 js教程 深入理解javascript閉包

深入理解javascript閉包

Dec 06, 2017 pm 03:16 PM
javascript js 閉包

閉包包含自由(未綁定到特定物件)變數;這些變數不是在這個程式碼區塊內或任何全域上下文中定義的,而是在定義程式碼區塊的環境中定義(局部變數)。 "閉包" 一詞來自以下兩者的結合:要執行的程式碼區塊(由於自由變數被包含在程式碼區塊中,這些自由變數以及它們所引用的物件沒有被釋放)和為自由變數提供綁定的計算環境(作用域)。本文我們就帶大家深入理解javascript閉包。

一、變數的作用域

要理解閉包,首先必須理解Javascript特殊的變數作用域。

變數的作用域無非就是兩種:全域變數和局部變數。

Javascript語言的特殊之處,就在於函數內部可以直接讀取全域變數。


Js程式碼

  var n=999;

  function f1(){
    alert(n);
  }

  f1(); // 999

另一方面,在函數外部自然無法讀取函數內的局部變數。

Js程式碼

  function f1(){
    var n=999;
  }

  alert(n) alert(n) alert(n) alert(n) alert(n) alert(n) alert(n) alert(n) alert(n)( ; // error

這裡有一個地方要注意,函數內部宣告變數的時候,一定要使用var指令。如果不用的話,你實際上聲明了一個全域變數!

Js程式碼

  function f1(){
    n=999;
  }

#  f1();##f1();

  alert(n); // 999

---------------- -------------------------------------------------- --------------------------------------

#二、如何從外部讀取局部變數?

出於種種原因,我們有時候需要得到函數內的局部變數。但是,前面已經說過了,正常情況下,這是辦不到的,只有透過變通方法才能實現。

那就是在函數的內部,再定義一個函數。

Js程式碼

  function f1(){

    n=999;




# function f2(){      alert(n);

// 999

    }

 }#的程式碼中,函數f2就被包括在函數f1內部,這時f1內部的所有局部變量,對f2都是可見的。但反過來就不行,f2內部的局部變量,對f1 就是不可見的。這就是Javascript語言特有的「鍊式作用域」結構(chain scope),

子物件會一級一層地向上尋找所有父物件的變數。所以,父物件的所有變量,對子物件都是可見的,反之則不成立。

既然f2可以讀取f1中的局部變量,那麼只要把f2當作回傳值,我們不就可以在f1外部讀取它的內部變數了嗎!

Js代號

  function f1(){

    n=999;

################################################################################################################################### ###    function f2(){###      alert(n);###    }########## ##   }######### ###  ##########  var result=f1();############  result(); ###// 999############ ##########---------------------------------------- -------------------------------------------------- --------------#####################三、閉包的概念### ###### ###上一節程式碼中的f2函數,就是閉包。 ######各種專業文獻上的「閉包」(closure)定義非常抽象,很難看懂。我的理解是,閉包就是能夠讀取其他函數內部變數的函數。 ######由於在Javascript語言中,只有函數內部的子函數才能讀取局部變量,因此可以把閉包簡單理解成「定義在一個函數內部的函數」。 ######所以,在本質上,閉包就是將函數內部和函數外部連結起來的一座橋樑。 ######-------------------------------------------- -------------------------------------------------- ----------b############四、閉包的用途###### ###

閉包可以用在許多地方。它的最大用處有兩個,個是前面提到的可以讀取函數內部的變量,另一個就是讓這些變數的值始終保持在記憶體中

怎麼來理解這句話呢?請看下面的程式碼。


Js碼

  function f1(){

    var n=999;



#    nAdd=function(){n+=1}

##    function f2(){

       

##    return f2;

  }

  var result=f1();

#[ ##  result();
// 999



  nAdd();



  result();

  result();


##  result(); // 1000

在這段程式碼中,result其實就是閉包f2函數。它總共運行了兩次,第一次的值是999,第二次的值是1000。這證明了,函數f1中的局部變數n一直保存在記憶體中,並沒有在f1呼叫後被自動清除。

為什麼會這樣呢?原因就在於f1是f2的父函數,而f2被賦給了一個全域變量,這導致f2始終在記憶體中,而f2的存在依賴f1,因此f1也始終在記憶體中,不會在呼叫結束後,被垃圾回收機制(garbage collection)回收。

這段程式碼中另一個值得注意的地方,就是「nAdd=function(){n+=1}」這一行,首先在nAdd前面沒有使用var關鍵字,因此nAdd是一個全域變量,而不是局部變數。其次,nAdd的值是匿名函數(anonymous function),而這個

匿名函數本身也是一個閉包,所以nAdd相當於一個setter,可以在函數外部對函數內部的局部變數進行運算。

-------------------------------------------- -------------------------------------------------- ---------- 

五、使用閉包的注意點

1)由於閉包會使得函數中的變數都保存在記憶體中,記憶體消耗很大,所以不能濫用閉包,否則會造成網頁的效能問題,在IE中可能導致記憶體外洩。解決方法是,在退出函數之前,將不使用的局部變數全部刪除。

2)閉包會在父函數外部,改變父函數內部變數的值。所以,如果你把父函數當作物件(object)使用,把閉包當作它的公用方法(Public Method),把內部變數當作它的私有屬性(private value),這時一定要小心,不要隨便

###改變父函數內部變數的值。 ######-------------------------------------------- -------------------------------------------------- ----------############六、思考題### #########如果你能理解下面程式碼的運行結果,應該就算理解閉包的運作機制了。 ######Js代碼###  ###var name = "The Window";   ###  var object = {   ###    name〠 # 是#      return function(){   ###        return this.name;   ### 〠  〠   ###alert(object.getNameFunc()());  ######// The Window###############--------------------------------- -------------------------------------------------- ----------------------###############JavaScript關閉範例########### #####function outerFun()### {####  var a=0;###  function innerFun()###  {###   a++;###  ## }###innerFun()#########上面的程式碼是###錯誤###的.innerFun()的作用域在outerFun()內部,所在outerFun()外部呼叫它是錯誤的.######改成如下,也就是閉包:######Js程式碼###

function outerFun()
{
 var a=0;
 function innerFun()
 {
  a++;
  alert(a);
 };
 return innerFun;  //注意這裡
}
#var obj=outerFun();
obj();  //結果為1
#obj();  //結果為2
var obj2=outerFun();
obj2();  //結果為1
#obj2() ;  //結果為2

什麼是閉包:

當內部函數在定義它的作用域的外部被引用時,就創建了此內部函數的閉包,如果內部函數引用了位於外部函數的變數,當外部函數調用完畢後,這些變數在記憶體不會被釋放,因為閉包需要它們.

---- -------------------------------------------------- --------------------------------------------------

再來看一個範例

Js程式碼

##function outerFun(){
# var a =0;
 alert(a);  
}
var a=4;
outerFun();
alert(a);

#結果是

0,4 .  因為在函數內部使用了var關鍵字維護a的作用域在outFun()內部.

再看下面的程式碼:

##Js代碼


function outerFun()
{ 
//沒有var
 a =0; alert(a);  
#}
var a=4;
outerFun();
alert(a);

#結果為
0,0
真是奇怪,為什麼呢?作用域鍊是描述一種路徑的術語,沿著該路徑可以確定變數的值.當執行a=0時,因為沒有使用var關鍵字,因此賦值操作會沿著作用域鏈到var a= 4;  並改變其值.

---------------------------------- -------------------------------------------------- -------------------------------------------------- ------------


如果你對javascript閉包還不是很理解,那麼請看下面轉載的文章:(轉載:http:// www.felixwoo.com/archives/247)



#一、什麼是閉包? 官方」的解釋是:閉包是一個擁有許多變數和綁定了這些變數的環境的表達式(通常是一個函數),因而這些變數也是這個表達式的一部分。包

。程式碼有兩個特點:


1、函數b嵌套在函數a內部;2、函數a回傳函數b。

#  這樣在執行完var c=a()後,變數c其實是指向了函數b,再執行c()後就會彈出一個視窗顯示i的值(第一次為1)。 ##當函數a的內部函數b被函數a外的一個變數引用的時候,就創建了一個閉包。所謂“閉包”,就是在構造函數體內定義另外的函數作為目標對象的方法函數,而這個對象的方法函數反過來引用外層函數體中的臨時變量。能保持其方法,就能間接保持原構造函數體當時用到的臨時變數值。引用到該變數的值,而且該值只能通這種方法來存取。次呼叫的是各自獨立的。完並回傳後,閉包使得Javascript的垃圾回收機制GC不會收回a所佔用的資源,因為a的內部函數b的執行需要依賴a中的變數。 ,不專業也不嚴謹,但大概意思是這樣,理解閉包需要循序漸進的過程。

在上面的例子中,由于闭包的存在使得函数a返回后,a中的i始终存在,这样每次执行c(),i都是自加1后alert出i的值。

  那 么我们来想象另一种情况,如果a返回的不是函数b,情况就完全不同了。因为a执行完后,b没有被返回给a的外界,只是被a所引用,而此时a也只会被b引 用,因此函数a和b互相引用但又不被外界打扰(被外界引用),函数a和b就会被GC回收。(关于Javascript的垃圾回收机制将在后面详细介绍)

三、闭包内的微观世界

  如果要更加深入的了解闭包以及函数a和嵌套函数b的关系,我们需要引入另外几个概念:函数的执行环境(excution context)、活动对象(call object)、作用域(scope)、作用域链(scope chain)。以函数a从定义到执行的过程为例阐述这几个概念。

  1. 定义函数a的时候,js解释器会将函数a的作用域链(scope chain)设置为定义a时a所在的“环境”,如果a是一个全局函数,则scope chain中只有window对象。

  2. 执行函数a的时候,a会进入相应的执行环境(excution context)

  3. 在创建执行环境的过程中,首先会为a添加一个scope属性,即a的作用域,其值就为第1步中的scope chain。即a.scope=a的作用域链。

  4. 然后执行环境会创建一个活动对象(call object)。活动对象也是一个拥有属性的对象,但它不具有原型而且不能通过JavaScript代码直接访问。创建完活动对象后,把活动对象添加到a的作用域链的最顶端。此时a的作用域链包含了两个对象:a的活动对象和window对象。

  5. 下一步是在活动对象上添加一个arguments属性,它保存着调用函数a时所传递的参数。

  6. 最后把所有函数a的形参和内部的函数b的引用也添加到a的活动对象上。在这一步中,完成了函数b的的定义,因此如同第3步,函数b的作用域链被设置为b所被定义的环境,即a的作用域。

到此,整个函数a从定义到执行的步骤就完成了。此时a返回函数b的引用给c,又函数b的作用域链包含了对函数a的活动对象的引用,也就是说b可以访问到a中定义的所有变量和函数。函数b被c引用,函数b又依赖函数a,因此函数a在返回后不会被GC回收。

当函数b执行的时候亦会像以上步骤一样。因此,执行时b的作用域链包含了3个对象:b的活动对象、a的活动对象和window对象,如下图所示:

如图所示,当在函数b中访问一个变量的时候,搜索顺序是:

  1. 先搜索自身的活动对象,如果存在则返回,如果不存在将继续搜索函数a的活动对象,依次查找,直到找到为止。

  2. 如果函数b存在prototype原型对象,则在查找完自身的活动对象后先查找自身的原型对象,再继续查找。这就是Javascript中的变量查找机制。

  3. 如果整个作用域链上都无法找到,则返回undefined。

小结,本段中提到了两个重要的词语:函数的定义执行。文中提到函数的作用域是在定义函数时候就已经确定,而不是在执行的时候确定(参看步骤1和3)。用一段代码来说明这个问题:

function f(x) { 
  var g = function () { return x; }  return g;}var h = f(1);alert(h());
登入後複製

这段代码中变量h指向了f中的那个匿名函数(由g返回)。

  • 假设函数h的作用域是在执行alert(h())确定的,那么此时h的作用域链是:h的活动对象->alert的活动对象->window对象。

  • 假设函数h的作用域是在定义时确定的,就是说h指向的那个匿名函数在定义的时候就已经确定了作用域。那么在执行的时候,h的作用域链为:h的活动对象->f的活动对象->window对象。

如果第一种假设成立,那输出值就是undefined;如果第二种假设成立,输出值则为1。

运行结果证明了第2个假设是正确的,说明函数的作用域确实是在定义这个函数的时候就已经确定了。

四、闭包的应用场景
保护函数内的变量安全。以最开始的例子为例,函数a中i只有函数b才能访问,而无法通过其他途径访问到,因此保护了i的安全性。

  1. 在記憶體中維持一個變數。依然如前例,由於閉包,函數a中i的一直存在於記憶體中,因此每次執行c(),都會給i自加1。

  2. 透過保護變數的安全實作JS私有屬性和私有方法(不能被外部存取)
    私有屬性和方法在Constructor外是無法被存取的

    function Constructor(...){  
      #var that = this;  
      var = value; 
      function membername(...){...}
    }

#以上3點是閉包最基本的應用場景,很多經典案例都源自於此。

五、Javascript的垃圾回收機制

在Javascript中,如果一個物件不再被引用,那麼這個物件就會被GC回收。如果兩個物件互相引用,而不再被第3者所引用,那麼這兩個互相引用的物件也會被回收。因為函數a被b引用,b又被a外的c引用,這就是為什麼函數a執行後不會被回收的原因。

理解JavaScript的閉包是邁向高級JS程式設計師的必經之路,了解其解釋和運行機制才能寫出更安全和優雅的程式碼​​。以上內容就是深入理解javascript閉包的解說,希望能幫助大家。

相關推薦:

JS閉包常見形式詳解

#經典js閉包詳

詳解js閉包_基礎知識

#

以上是深入理解javascript閉包的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

建議:優秀JS開源人臉偵測辨識項目 建議:優秀JS開源人臉偵測辨識項目 Apr 03, 2024 am 11:55 AM

人臉偵測辨識技術已經是一個比較成熟且應用廣泛的技術。而目前最廣泛的網路應用語言非JS莫屬,在Web前端實現人臉偵測辨識相比後端的人臉辨識有優勢也有弱勢。優點包括減少網路互動、即時識別,大大縮短了使用者等待時間,提高了使用者體驗;弱勢是:受到模型大小限制,其中準確率也有限。如何在web端使用js實現人臉偵測呢?為了實現Web端人臉識別,需要熟悉相關的程式語言和技術,如JavaScript、HTML、CSS、WebRTC等。同時也需要掌握相關的電腦視覺和人工智慧技術。值得注意的是,由於Web端的計

C++ lambda 表達式中閉包的意思是什麼? C++ lambda 表達式中閉包的意思是什麼? Apr 17, 2024 pm 06:15 PM

在C++中,閉包是能夠存取外部變數的lambda表達式。若要建立閉包,請擷取lambda表達式中的外部變數。閉包提供可重複使用性、資訊隱藏和延遲求值等優點。它們在事件處理程序等實際情況中很有用,其中即使外部變數被銷毀,閉包仍然可以存取它們。

C++ Lambda 表達式如何實作閉包? C++ Lambda 表達式如何實作閉包? Jun 01, 2024 pm 05:50 PM

C++Lambda表達式支援閉包,即保存函數作用域變數並供函數存取。語法為[capture-list](parameters)->return-type{function-body}。 capture-list定義要捕獲的變量,可以使用[=]按值捕獲所有局部變量,[&]按引用捕獲所有局部變量,或[variable1,variable2,...]捕獲特定變量。 Lambda表達式只能存取捕獲的變量,但無法修改原始值。

C++ 函式中閉包的優點和缺點是什麼? C++ 函式中閉包的優點和缺點是什麼? Apr 25, 2024 pm 01:33 PM

閉包是一種巢狀函數,它能存取外層函數作用域的變量,優點包括資料封裝、狀態保持和靈活性。缺點包括記憶體消耗、效能影響和調試複雜性。此外,閉包還可以建立匿名函數,並將其作為回調或參數傳遞給其他函數。

函數指標和閉包對Golang效能的影響 函數指標和閉包對Golang效能的影響 Apr 15, 2024 am 10:36 AM

函數指針和閉包對Go性能的影響如下:函數指針:稍慢於直接調用,但可提高可讀性和可復用性。閉包:通常更慢,但可封裝資料和行為。實戰案例:函數指標可最佳化排序演算法,閉包可建立事件處理程序,但會帶來效能損失。

js和vue的關係 js和vue的關係 Mar 11, 2024 pm 05:21 PM

js和vue的關係:1、JS作為Web開發基石;2、Vue.js作為前端框架的崛起;3、JS與Vue的互補關係;4、JS與Vue的實踐應用。

PHP 函數的鍊式呼叫與閉包 PHP 函數的鍊式呼叫與閉包 Apr 13, 2024 am 11:18 AM

是的,可以透過鍊式呼叫和閉包優化程式碼簡潔性和可讀性:鍊式呼叫可將函數呼叫連結為一個流暢介面。閉包可建立可重複使用程式碼區塊,並在函數外部存取變數。

閉包在 Java 中是如何實現的? 閉包在 Java 中是如何實現的? May 03, 2024 pm 12:48 PM

Java中的閉包允許內部函數存取外部的作用域變量,即使外部函數已經退出。透過匿名內部類別實現,內部類別持有一個外部類別的引用,使外部變數保持活動。閉包增強了程式碼靈活性,但需要注意記憶體洩漏風險,因為匿名內部類別對外部變數的參考會保持這些變數的活動狀態。

See all articles