成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

帶你走進JS中探索閉包

開發 前端
閉包是令人困惑的,因為它是一個“無形的”概念。當使用對象、變量或函數時,你會想:“在這里我需要一個變量”,然后將其添加到你的代碼中。

閉包是令人困惑的,因為它是一個“無形的”概念。

當使用對象、變量或函數時,你會想:“在這里我需要一個變量”,然后將其添加到你的代碼中。

閉包有各種不同的形式。很多人在注意到閉包時,實際上他們已經在不知不覺中多次使用過了——可能你也是如此。所以學習閉包不是要去了解什么「新」概念,而是要了解你「已經」接觸過的東西。

[[337163]]

太長不看版

當「函數訪問在其外部定義的變量時」,你需要閉包。

例如,這段代碼包含一個閉包:

  1. let users = ['Alice', 'Dan', 'Jessica']; 
  2. let query = 'A'
  3. let user = users.filter(user => user.startsWith(query)); 

注意 user => user.startsWith(query) 本身是一個函數。它使用了 query 變量。但是,query 變量是在該函數的“外部”定義的。那就是閉包。

「如果你愿意,可以在這里就停止閱讀」。本文的其余部分會以不同的方式去處理閉包,并不解釋閉包是什么,而是帶你完成「發現」閉包的過程——就像1960年代的第一批程序員所做的那樣。

第1步:函數可以訪問外部變量

要了解閉包,我們需要對變量和函數有所了解。在這個例子中,我們在 eat 函數中聲明了 food 變量。

  1. function eat() { 
  2.   let food = 'cheese'
  3.   console.log(food + ' is good'); 
  4.  
  5. eat(); // => 'cheese is good' 

但是,如果我們以后想更改 eat 函數的 food 變量,該怎么辦?為此,我們可以將 food 變量本身從函數中移到頂層:

  1. let food = 'cheese'; // 我們把它移動到外部 
  2.  
  3. function eat() { 
  4.   console.log(food + ' is good'); 

這樣我們可以在任何有需要的時候“從外部” 修改 food:

  1. eat(); // => 'cheese is good' 
  2. food = 'pizza'
  3. eat(); // => 'pizza is good' 
  4. food = 'sushi'
  5. eat(); // => 'sushi is good' 

換句話說,food 變量不再是 eat 函數的局部變量,但是 eat 函數仍然可以輕松訪問它。「函數可以訪問它們之外的變量」。先停下來想一秒鐘,確保你對這個想法沒有任何疑問。然后繼續執行第二步。

第2步:在函數調用中包裝代碼

假設我們有一些代碼:

  1. /* 一些代碼片段 */ 

這些代碼做什么無關緊要。但是,假設「我們要運行兩次」。

一種方法是復制并粘貼:

  1. /* 一些代碼片段 */ 
  2. /* 一些代碼片段 */ 

另一種方法是使用循環:

  1. for (let i = 0; i < 2; i++) { 
  2.   /* 一些代碼片段 */ 

第三種方法,也是我們今天特別感興趣的一種方法,將其包裝在一個函數中:

  1. function doTheThing() { 
  2.   /* 一些代碼片段 */ 
  3.  
  4. doTheThing(); 
  5. doTheThing(); 

函數為我們提供了很大的靈活性,因為我們可以隨時在程序中的任何位置把這個函數執行任意次。

如果愿意,「我們也可以只調用一次」:

  1. function doTheThing() { 
  2.   /* 一些代碼片段 */ 
  3.  
  4. doTheThing(); 

請注意,上面的代碼與原始代碼段是等效的:

  1. /* 一些代碼片段 */ 

換句話說,「如果我們有一段代碼,將代碼“包裝”到一個函數中,然后只調用一次,那么我們就不會改變代碼的作用」。我們會忽略此規則的一些例外,但總的來說這應該是有道理的。停留在這個想法上,直到你的大腦完全理解為止。

第3步:發現閉包

前面我們通過兩種不同的想法進行了探索:

  • 函數可以訪問在其外部定義的變量。
  • 在函數中包裝代碼并調用一次不會改變結果。

那么如果把它們結合在一起會發生些什么呢。

我們將從第一步的代碼開始:

  1. let food = 'cheese'
  2.  
  3. function eat() { 
  4.   console.log(food + ' is good'); 
  5.  
  6. eat(); 

然后,將整個例子中的代碼包裝到一個函數中,該函數將被調用一次:

  1. function liveADay() { 
  2.   let food = 'cheese'
  3.  
  4.   function eat() { 
  5.     console.log(food + ' is good'); 
  6.   } 
  7.  
  8.   eat(); 
  9.  
  10. liveADay(); 

再次審視兩個代碼片段,并確保它們是等效的。

這段代碼有效!但是仔細看,注意 eat 函數在 liveADay 函數的內部。這允許嗎?我們真的可以將一個函數放在另一個函數中嗎?

在某些語言中,用這種方式寫出來的代碼是「無效」的。例如這種代碼在 C 語言(沒有閉包)中無效。這意味著在 C 語言中,前面的第二個結論是不正確的——我們不能隨隨便便就把一些代碼包裝在函數中。但是 JavaScript 不受這種限制。

再看這段代碼,并注意在哪里聲明和使用了 food:

  1. function liveADay() { 
  2.   let food = 'cheese'; // 聲明 `food` 
  3.  
  4.   function eat() { 
  5.     console.log(food + ' is good'); // 使用 `food` 
  6.   } 
  7.  
  8.   eat(); 
  9.  
  10. liveADay(); 

讓我們一起逐步看一下這段代碼。首先在頂層聲明 liveADay 函數,然后立即調用它。它有一個 food 局部變量,還包含一個 eat 函數。然后調用 eat 功能。因為 eat在 liveADay 內部,所以它“看到”了所有變量。這就是為什么它可以讀取 food 變量的原因。

「這就是閉包」。

「我們說當函數(例如 eat)讀取或寫入在其外部(例如在 food 中)聲明的變量(例如 food)時,存在閉包。」

花一些時間多讀幾遍,并確保你已經理解了上面的代碼代碼。

下面是本文最開始介紹過的例子:

  1. let users = ['Alice', 'Dan', 'Jessica']; 
  2. let query = 'A'
  3. let user = users.filter(user => user.startsWith(query)); 

如果用函數表達式重寫,則更容易注意到閉包:

  1. let users = ['Alice', 'Dan', 'Jessica']; 
  2. // 1. 查詢變量在外部聲明 
  3. let query = 'A'
  4. let user = users.filter(function(user) { 
  5.   // 2. 我們處于嵌套函數中 
  6.   // 3. 然后我們讀取查詢變量(在外部聲明!) 
  7.   return user.startsWith(query); 
  8. }); 

每當函數訪問在其外部聲明的變量時,我們就說它是一個閉包。這個術語本身在使用時有些寬松。在本例中,有些人把「嵌套函數本身」稱為“閉包”。其他人可能會把訪問外部變量的“技術”稱為閉包。實際上這都沒關系。

函數調用的幽靈

閉看似簡單,但是這并不意味著他們沒有自己的陷阱。如果你真正考慮一下,函數可以在外部讀取和寫入變量的事實將會產生深遠的影響。這意味著只要可以調用嵌套函數,這些變量就會“存活”下去:

  1. function liveADay() { 
  2.   let food = 'cheese'
  3.  
  4.   function eat() { 
  5.     console.log(food + ' is good'); 
  6.   } 
  7.  
  8.   // Call eat after five seconds 
  9.   setTimeout(eat, 5000); 
  10.  
  11. liveADay(); 

在這里,food 是在 liveADay() 函數調用內的局部變量。在我們退出 liveADay 之后,很容易想到它“消失了”,并且它不會回來困擾我們。

但是,在 liveADay 內部,我們告訴瀏覽器在五秒鐘內調用 eat。然后,eat 讀取 food 變量。「因此,JavaScript引擎需要使特定的 liveADay() 調用中的food變量保持可用,直到調用eat。」

從這種意義上講,我們可以將閉包視為過去函數調用的“幻象”或“內存”。即使我們的 liveADay() 函數調用已經完成很長時間,但只要仍可以調用嵌套的 eat 函數,那么它的變量就必須繼續存在。幸運的是,JavaScript 為我們做到了這一點,因此我們就無需再去考慮它了。

為什么會有“閉包”?

最后,你可能想知道為什么以這種方式調用閉包。主要是歷史原因。一位熟悉計算機科學術語的人可能會說像 user => user.startsWith(query) 之類的表達式具有“開放綁定”。換句話說,從中可以清楚地知道 user 是什么(一個參數),但是還不能確定 query 是孤立的。當我們說“實際上,query 指的是在外部聲明的變量”時,我們是在“關閉”開放綁定。換句話說,我們得到一個 閉包。

并非所有語言都實現閉包。例如在一些像 C 這樣的語言中,根本不允許嵌套函數。結果,一個函數只能訪問自己的局部變量或全局變量,永遠不會出現訪問父函數的局部變量的情況。當然,這種限制是痛苦的。

還有像 Rust 這樣的語言,它們實現了閉包,但是對于閉包和常規函數有著單獨的語法。因此,如果你想從函數外部讀取變量,則必須在 Rust 中選擇使用該變量。這是因為在底層,即使在函數調用之后,閉包也可能要求引擎保持外部變量(稱為“環境”)。這種開銷在 JavaScript 中是可以接受的,但是對于非常低級的語言來說,則可能會引發性能方面的問題。

到此為止,希望你能對閉包的概念有了深入理解!

 

責任編輯:趙寧寧 來源: 前端先鋒
相關推薦

2009-12-09 13:41:50

PHP Zend框架

2010-09-14 10:15:24

2021-05-21 09:01:29

JavaScript 前端函數閉包

2020-06-24 12:01:16

Python數據類字符

2024-01-22 09:51:32

Swift閉包表達式尾隨閉包

2015-08-18 13:42:42

js作用域鏈變量

2023-11-02 08:53:26

閉包Python

2013-12-24 11:11:27

ember.jsJavascript

2012-11-29 10:09:23

Javascript閉包

2016-11-01 09:18:33

Python閉包

2017-07-17 14:17:37

閉包匿名函數 作用域

2021-10-26 13:18:52

Go底層函數

2021-02-21 16:21:19

JavaScript閉包前端

2011-05-12 18:26:08

Javascript作用域

2011-08-24 17:09:35

LUA閉包函數

2021-01-21 15:40:45

VRARVR眼鏡

2012-01-09 10:55:44

虛擬化桌面虛擬化KVM

2023-10-26 01:15:09

得物視頻優化

2021-03-06 09:18:51

JS閉包函數

2019-06-24 16:00:17

探索Linuxrun
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 欧美日韩亚洲国产 | 蜜桃臀av一区二区三区 | 日韩成人av在线 | 午夜在线精品偷拍 | 精品久久久久久久 | 日韩一级欧美一级 | 亚洲成年影院 | 免费观看一级特黄欧美大片 | 欧美日韩三级在线观看 | 成人一区精品 | 国产高清久久 | 在线一级片 | 熟女毛片 | 欧美 日韩 视频 | 香蕉视频在线播放 | 夜夜摸天天操 | 特黄毛片视频 | www.久久.com| 一级片av| 乱码av午夜噜噜噜噜动漫 | 免费观看羞羞视频网站 | 日本涩涩视频 | 午夜精品久久久久99蜜 | 中文在线а√在线8 | 午夜爽爽男女免费观看hd | 一区二区视频在线 | 在线婷婷 | 日韩精品成人 | 91玖玖| 国产欧美一级二级三级在线视频 | 一区二区三区观看视频 | 日韩欧美视频免费在线观看 | 亚洲精品视频免费观看 | 黄色国产在线视频 | 亚洲国产情侣自拍 | 亚洲bt 欧美bt 日本bt | 久久区二区 | 亚洲一区中文字幕在线观看 | 欧美老少妇一级特黄一片 | 国产精品美女久久久久aⅴ国产馆 | 国产精品一区二区在线 |