目錄
JavaScript欄位好好介紹閉包。 " >今天JavaScript欄位好好介紹閉包。
前言
了解閉包
1. 什麼是閉包
2. 感受一下閉包
閉包長什麼樣子?
閉包產生的條件?
深入體驗一下閉包
1.建立多個閉包
2.使用闭包实现模块化
试着用闭包解决问题
总结
写在最后
首頁 web前端 js教程 JavaScript 來好好盤一盤閉包!

JavaScript 來好好盤一盤閉包!

Oct 21, 2020 pm 05:41 PM
javascript 閉包

今天JavaScript欄位好好介紹閉包。

JavaScript 來好好盤一盤閉包!

前言

想要深入學習JavaScript這門語言,閉包這個概念幾乎是繞不開的關鍵,今天就讓我們一起好好盤一盤,閉包到底是什麼東西。
如果是零基礎的小夥伴,可以先看看前一篇文章,幫助你更好的理解本文的內容:

  • 【JavaScript】有趣的作用域與提升

了解閉包

1. 什麼是閉包

我們先來看看閉包在MDN中的定義:

一個函數和對其周圍狀態(lexical environment,詞法環境)的引用捆綁在一起(或者說函數被引用包圍),這樣的組合就是閉包(closure)

#閉包的定義非常晦澀難懂,如果通俗的解釋一下的話,就是:

  • 一個定義在函數內部的函數和所在作用域的組合稱為閉包。  

這麼講可能還是很抽象,不過我們先有個概念,以下會詳細解釋這個概念。對於閉包,其實還有另一種解釋:

  • 一個函數中宣告了一個內部函數並返回,外部可以透過這個內部函數存取到內部的值,這樣的函數,我們稱作閉包

區別於第一種解釋,第二種解釋把函數本身稱為閉包,這樣的解釋也合理,但更準確的話其實我們可以把這個函數稱為閉包函數。這樣的話,其實兩種解釋都沒有問題,也可以同時存在了。

2. 感受一下閉包

看完了閉包的概念,我們透過實際的例子感受一下,閉包長什麼樣子?閉包產生的條件?

閉包長什麼樣子?

function outer(){    var a=0;    function inner(){        console.log(++a);
    }    return inner;
}var f=outer();
f();  //1f();  //2复制代码
登入後複製

我們先來看下這個例子裡,閉包是如何體現的。我們首先定義了outer函數,然後在outer函數的內部宣告了一個inner函數並傳回。

在外部,我們執行outer函數,並將返回的inner函數保存在了f中,現在的f就是我們說的閉包函數了。

在範例中,我們執行了兩次f函數,分別輸出了12,說明我們在執行f函數中的console.log( a);語句時,根據我們先前對作用域的解釋,會去函數所在的作用域鏈上尋找變量,最終在outer函數中找到了這個變量,並進行了增值操作。

同時要注意的是,我們輸出的結果並不是兩次1,表示我們的作用域是共享的,可以看成outer的作用域延伸到了外部。

outer作用域本來應該在執行結束後釋放,但根據GC機制(我們這裡先不展開介紹)會發現:

  • 我準備要釋放outer了,但a變數好像被引用了,而且外部還保存了起來不知道什麼時候呼叫?好收回,那我就先不釋放了。

閉包就這樣誕生了,在作用域消失之後,外部依然可以存取作用域中的變數

閉包產生的條件?

我們根據剛剛的範例和解釋總結一下,產生一個閉包需要哪些條件:

  1. 在外部函數中宣告一個內部函數,並傳回內部函數。
    這裡的outer就是外部函數,inner就是回傳的內部函數。
  2. 內部的函數引用了外部函數中的變數。
    在我們的例子中,inner函數就引用了變數a
  3. 外部函數需要被執行,建立一個閉包,同時傳回的內部函數需要被保存。
    外部函數outer需要被執行,同時需要把傳回的內部函數給保存起來,也就是我們範例中的var f=outer()

只有同時滿足了這三個條件,才能產生閉包。看似很難達成,事實上,我們實際程式碼中經常不經意就能產生閉包。

透過剛剛的介紹,相信大家應該對閉包有了一個概念,接下來就讓我們出發,深入的了解一下閉包的方方面面。

深入體驗一下閉包

1.建立多個閉包

在剛剛的範例中,我們建立了一個閉包,執行多次閉包函數後,增加的都是同一個作用域中的變數a,那麼我們試試看建立多個閉包會怎麼樣:

function outer() {  var a = 0;  function inner() {    console.log(++a);
  }  return inner;
}var f1 = outer();var f2 = outer();
f1();  //1f2();  //1复制代码
登入後複製

这段代码在刚刚的例子上进行了改动,我们执行了两次外部函数outer,并分别用不同的变量f1f2保存。当执行f1f2时会发现,输出的结果都是1,说明f1f2的作用域是独立的,f1f2属于两个不同的闭包,我们用一张图来理解下:JavaScript 來好好盤一盤閉包!

当分别创建f1f2时,调用了两次outer函数,创建了两个不同的上下文。而当f1f2分别执行时,根据作用域的查找规则,会去对应的作用域中查找变量,并执行增值输出,所以最终两个值均为2

2.使用闭包实现模块化

我们知道,作用域的外部无法拿到作用域内部的值,而通过闭包,我们可以把作用域我们需要的值或者方法暴露出去,比如:

function outer() {  var myNumber = 0;  function addNumber() {
    myNumber++;
  }  function getNumber() {    return myNumber;
  }  return {
    addNumber, getNumber
  }
}var module = outer();module.addNumber();module.addNumber();module.getNumber();复制代码
登入後複製

在这个例子中,我们同样定义了一个外部函数outer,另外还分别定义了两个函数addNumbergetNumber,用于增加和获取变量myNumber

当我们执行outer()语句的时候,会创建一个上下文,同时把内部返回的对象保存在module变量上,此时我们就创建了一个闭包,以及一个包含方法addNumbergetNumber的对象。

由于外部是无法直接访问变量myNumber的,但由于闭包的原因,addNumbergetNumber是可以访问到这个变量的,因此我们成功的把变量myNumber隐藏了起来,并且对外只提供了增加和获取myNumber值的方法。

试着用闭包解决问题

通过刚刚的例子,相信大家应该对闭包有了一定了解,接下来我们试着运用闭包来解决实际问题,先看一下例子:

for (var i = 0; i < 2; i++) {      setTimeout(() => {        console.log(i);
      }, 500);
    }复制代码
登入後複製

这是一个十分容易令人误解的例子。接触过的小伙伴肯定都知道,最后会输出两次2而不是依次输出01,我们来看看为什么会这样。
首先,外部是一个for循环,执行了两次语句。

for (var i = 0; i < 2; i++) {
    ... //  执行两次}复制代码
登入後複製

在函数的内部,我们调用了setTimeout函数,关键的地方来了,这个函数是一个异步函数,并不会马上执行,所以实际上等外部的for循环执行结束了,才会真的执行setTimeout中的函数。还有第二个关键点在于,for循环中,var定义的变量相当于定义在全局,而不存在块级作用域。那么刚刚的代码就可以近似的看成这样了。

var i=0;
i++;    //i=1i++;    //i=2console.log(i);console.log(i);复制代码
登入後複製

非常直接粗暴,但可以很清晰的看出输出结果为何是两次2了,因为大家共用了同一个作用域,i的值被覆盖了。那么知道了问题出在哪里,我们试着用上我们刚刚学习的闭包,来创建不同的作用域:

   for (var i = 0; i < 2; i++) {      function outer() {        var j = i;        setTimeout(function inner() {          console.log(j);
        }, 500);
      }
      outer();
    }复制代码
登入後複製

我们按照闭包的样式对刚刚的代码进行了改造,这里的setTimeout并不直接就是inner函数,这是因为它在这里起到了定义inner函数,并保存执行inner函数的功能。

我们可以看到,最终结果依次输出了01,说明我们的闭包是成功了的,但这样的闭包比较臃肿,我们试着提高一下,写的更加优雅一点:

   for (var i = 0; i < 2; i++) {
        (function() {   //用自执行函数代替了`outer`函数的定义和执行两个步骤
            var j = i;            setTimeout(function inner() {                console.log(j);
            }, 500);
        })();
    }复制代码
登入後複製

还可以再简化一下:

for (var i = 0; i < 5; i++) {        for (var i = 0; i < 2; i++) {
            (function(j) {//用自执行函数代替了`outer`函数的定义和执行两个步骤
                setTimeout(function inner() {                    console.log(j);
                }, 500);
            })(i);//var j=i的步骤,通过传入i值替换
        }
    }复制代码
登入後複製

这样就大功告成了!

总结

本篇首先介绍了闭包的定义以及不同人对闭包的理解,之后介绍了闭包产生的原因并总结了三个条件,之后举例说明了如何创建多个闭包和通过闭包实现模块,最后讲述了如何通过闭包解决for循环中使用异步函数依次输出值的问题。其实闭包没有想象的那么可怕,只要你愿意静下心去探索去了解,闭包也会对你敞开大门~

写在最后

都看到这里了,如果觉得对你有帮助的话不妨点个赞支持一下呗~

以后会陆续更新更多文章和知识点,感兴趣的话可以关注一波~

如果哪里有错误的地方或者描述不准确的地方,也欢迎大家指出交流~

相关免费学习推荐:javascript(视频)

以上是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脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 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)

熱門話題

Java教學
1665
14
CakePHP 教程
1424
52
Laravel 教程
1321
25
PHP教程
1269
29
C# 教程
1249
24
C++ lambda 表達式中閉包的意思是什麼? C++ lambda 表達式中閉包的意思是什麼? Apr 17, 2024 pm 06:15 PM

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

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

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

解決閉包導致的記憶體洩漏問題 解決閉包導致的記憶體洩漏問題 Feb 18, 2024 pm 03:20 PM

標題:閉包造成的記憶體洩漏及解決方法引言:閉包是JavaScript中一個非常常見的概念,它可以讓內部函數存取外部函數的變數。然而,閉包在使用不當的情況下可能導致記憶體洩漏。本文將探討閉包所造成的記憶體洩漏問題,並提供解決方法及具體程式碼範例。一、閉包引起的記憶體洩漏問題閉包的特性是內部函數可以存取外部函數的變量,這意味著在閉包中引用的變數不會被垃圾回收。如果使用不當,

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表達式只能存取捕獲的變量,但無法修改原始值。

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

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

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

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

golang函數閉包在測試中的作用 golang函數閉包在測試中的作用 Apr 24, 2024 am 08:54 AM

Go語言函數閉包在單元測試中發揮著至關重要的作用:捕獲值:閉包可以存取外部作用域的變量,允許在巢狀函數中捕獲和重複使用測試參數。簡化測試程式碼:透過擷取值,閉包消除了對每個循環重複設定參數的需求,從而簡化了測試程式碼。提高可讀性:使用閉包可以組織測試邏輯,使測試程式碼更清晰、更易於閱讀。

golang匿名函數和閉包的優缺點總結 golang匿名函數和閉包的優缺點總結 May 05, 2024 am 09:54 AM

匿名函數簡潔、匿名,但可讀性差、調試困難;閉包能封裝資料、管理狀態,但可能導致記憶體消耗和循環引用。實戰案例:匿名函數可用於簡單數值處理,閉包可實現狀態管理。

See all articles