首頁 web前端 js教程 JavaScript閉包詳解_基礎知識

JavaScript閉包詳解_基礎知識

May 16, 2016 pm 04:16 PM
javascript 閉包

在上一篇文章我們對預解釋作了概述,在寫這篇博文前打算寫幾個經典案例,考慮到那些案例綜合性比較強,也就循序漸進的有了這篇博文,這樣對於學習和深入JavaScript也更容易入手。

一同事去面試,面試官問了一道題目:你寫一個閉包我看下?於是同事火速寫出如下碼:

複製程式碼 程式碼如下:

function fn(){
    alert('Hello JavaScript Closure!!!');//媽蛋,E文字來就不好,找翻譯才把閉包單字寫出來
}
fn();

然後面試官搖搖頭說道:“這怎麼能叫閉包呢?”,最終兩人爭執不下,同事果斷走人,面試官什麼玩意兒? (本故事純屬虛構,如有雷同純屬巧合)

閉包可能在很多人眼中都是「高大不好上」的技術,可能在很多人眼中只有這樣才算得上閉包:

範例1:

複製程式碼 程式碼如下:

function fn() {
    return function () {
        alert('例1');
    }
}
fn()();

範例1 PS:這個看起來不怎麼高級,看樣子這人水平不咋地哦!

範例2:

複製程式碼 程式碼如下:

;(function () {
    alert('例2');
})();

範例2 PS:這個看起來比上一個要高級,而且第一個括號前還加了一個分號,為何加一個分號,好吧我們先把這個疑問留這兒,後面會講到。

範例3:

複製程式碼 程式碼如下:

~function fn() {
    alert('範例3')
}();

範例3 PS:這個最高級了,簡直吊炸天,我讀書少,你們別騙我!

擼主讀書不多,僅能寫出這三種“閉包”,相信博友們能寫出更多更優秀的“閉包”;到此請先暫停我的瞎掰,接下來研究下函數運作的機制,似乎有人已經知道了,肯定是作用域,我真的很不想在標題上再加上這個作用域,這樣總感覺差點兒意思,這個幾個東西本來都是一起的,為何要重複呢?舊習慣,先上程式碼:

複製程式碼 程式碼如下:

var n = 10;
function fn(){
    alert(n);
    var n = 9;
    alert(n);
}
fn();

好簡單的說,我們畫圖(擼主只會用Windows自帶的畫圖軟體,若有更好的請博友推薦)來分析下:

分析1

    從圖中我們看到了兩個作用域,一個是window作用域(頂級作用域),一個是fn調用的時候形成的一個私有作用域;那什麼是作用域,作用域其實就是代碼執行的環境。舉個栗子,一個學生他的學習環境是學校,相當於他的作用域是學校,假如這個學生很調皮,晚上經常FanQiang去網吧打遊戲,相當於形成了一個私有環境,這個作用域就是網吧。好吧!這個栗子太TM像擼主本人了,不由感嘆一句:「少壯不努力,長大乾挨踢」。還是回到正題,其實函數fn的定義就是指向一段程式碼的描述(圖中紅框),當這個fn呼叫(圖中的綠框)的時候,就會形成一個作用域,當然這個作用域裡的程式碼執行前也會預先解釋,我是不會告訴你這個作用域是當它執行完畢後會被銷毀,這個fn再次呼叫也會形成一個新的作用域,然後執行前預解釋,然後程式碼執行,最後執行完畢銷毀。

理解閉包

    我們知道函數被呼叫執行的時候會形成一個私有作用域(執行環境),而這個私有作用域就是閉包。回頭再看看閉包還是傳說中的「高大不好上」嗎?我們再回頭看看第一個面試故事,還有我寫的三個範例,它們其實都是閉包,確切的說那三個範例都是閉包的常用形式。

應用場景

現在有這樣一個需求:HTML頁面中有一個ul標籤,ul下面有5個li標籤,要求任意點擊一個li,彈出被點擊的這個li所在的索引(索引從0開始)位置,HTML結構如下:

複製程式碼 程式碼如下:


       
  • 列表1

  •    
  • 列表2

  •    
  • 列表3

  •    
  • 列表4

  •    
  • 列表5



機智的我火速寫出如下碼:

複製程式碼 程式碼如下:

var lis = document.getElementById('ul').getElementsByTagName('li');
for (var i = 0, len = lis.length; i     lis[i].onclick = function () {
        alert(i);
    };
}

最終測試,看是否完美實現這個需求:

發現無論點擊多少次,最終都彈出這個結果,而需求期望的結果是:點擊列表1彈出0,點擊列表2彈出1,點擊列表3彈出2……此時此刻只想用這張圖來形容現在的心情:

(當原型在演示時沒能按設計的要求運行時的樣子)

這可如何才好,為何總是彈出5呢?理論上很正確呀!我們不妨畫圖分析下:

其實我們只是給每一個li的onclick其實就是保存的一段函數的描述字符串,這個字符串內容就是上圖紅框中的內容,如果您還是不信,我有圖有真相:

在Chrome控制台下輸入:lis[4].onclick,其值就是函數的描述。當我們在點擊第5個列表時,其實就是相當於lis[4].onclick(),呼叫了這段函數描述,我們知道函數在被呼叫執行的時會形成一個私有作用域,在這個私有作用域下也是先預先解釋,然後程式碼執行,此時會去找i,在目前私有作用域下沒有i,然後去window作用域下找到了i,因此每次點擊都彈出5。

顯然上面的程式碼無法滿足這個需求,我們程式碼那麼寫是不正確的,我們思考一下出現問題的原因是什麼?其實原因就是每次點擊的時候都是讀取的window下的i,此時這個i的值已經是5了,於是有瞭如下代碼:

方式一:

複製程式碼 程式碼如下:

var lis = document.getElementById('ul').getElementsByTagName('li');
function fn(i) {
    return function () {
        alert(i);
    }
}
for (var i = 0, len = lis.length; i     lis[i].onclick = fn(i);
}

方式二:

複製程式碼 程式碼如下:

var lis = document.getElementById('ul').getElementsByTagName('li');
 
for (var i = 0, len = lis.length; i     ;(function (i) {
        lis[i].onclick = function () {
            alert(i);
        };
    })(i);
}

方式三:

複製程式碼 程式碼如下:

var lis = document.getElementById('ul').getElementsByTagName('li');
 
for (var i = 0, len = lis.length; i     lis[i].onclick = function fn(i) {
        return function () {
            alert(i);
        }
    }(i);
}

一口氣寫了三種方式,其思想都是一樣的,就是將這個變數i用一個私有變數儲存起來,這裡我就只講方式二,當然明白其中一個其餘也就都明白了。按照慣例,我們畫圖一步步分析下:

我詳細的對整個程式碼執行做了描述,需要注意的是:每個li的onclick屬性都要佔用(function(i){ … })(i)作用域,當這個函數執行完畢後不會被銷毀,因為它被外面的li(這個li是window作用域下的)佔用著,因此這個作用域不會被銷毀。當點選任一li時,function(){ alert(i); }會被執行,也會形成一個作用域,這個作用域沒有i,它會去(function(){ … })(i)作用域找i,最後在形參找到i,這個形參i的值就是for迴圈時傳進去的;這個例子巧妙地使用閉包來貯存值,完美解決問題。

PS:剛剛說(function(i){ … })(i)為什麼在前面加一個分號,原因就是防止前面的語句忘記加分號,這樣導致JavaScript在解析時出錯,僅此而已。當然上面的一個應用場景就是Tabs實作原理,可以有其他實作方式,例如自訂屬性方式、透過DOM節點關係找到索引,而擼主採用這樣一種方式只是為了加深對閉包的理解。

總結

    閉包並不是傳說中的高大不好上,其核心就是理解函數定義、調用,函數調用時會形成一個新的私有作用域,當某個作用域被外面佔用,那麼這個作用域將不會被銷毀。擼主讀書甚少,有說得不對的地方請博友們指正,同時也感謝大家對擼主文章的支持。

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡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

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

熱工具

記事本++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教學
1664
14
CakePHP 教程
1422
52
Laravel 教程
1316
25
PHP教程
1267
29
C# 教程
1239
24
C++ lambda 表達式中閉包的意思是什麼? C++ lambda 表達式中閉包的意思是什麼? Apr 17, 2024 pm 06:15 PM

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

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

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

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

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

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

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

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

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

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

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

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

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

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

See all articles