JavaScript之深入函數(二)

来源:https://www.cnblogs.com/ruhaoren/archive/2019/09/06/11474076.html
-Advertisement-
Play Games

上一篇我們主要講解了函數的執行過程和原理,本篇我們將介紹函數的另外兩個特殊表現:閉包和立即執行函數。 一 閉包 1, 閉包的形成 之前我們提到,函數執行完畢,馬上就會銷毀自己的AO對象。但是如果遇到下麵這種情況:有子函數的定義,並將子函數返回。它真的就完全銷毀了自己的AO對象嗎? 這將列印什麼呢?表 ...


  上一篇我們主要講解了函數的執行過程和原理,本篇我們將介紹函數的另外兩個特殊表現:閉包和立即執行函數。

 

一   閉包

  1,  閉包的形成

  之前我們提到,函數執行完畢,馬上就會銷毀自己的AO對象。但是如果遇到下麵這種情況:有子函數的定義,並將子函數返回。它真的就完全銷毀了自己的AO對象嗎?

1 function fn(){
2     var a = 1;
3     function son(){
4          console.log(a);
5     }
6     return son;
7 }
8 var test = fn();
9 test();//error ? 1

  這將列印什麼呢?錶面上看,test是son的另一個引用,son內並沒有變數的聲明,consol.log()訪問a應該拋出錯誤。

  但事實上,test()將列印1,這是為什麼呢?回憶上一篇文章函數的作用域鏈,不難發現:

    當fn執行時:fn.[[scope]] --- {0:AO(fn),1:GO};son被聲明:son.[[scope]]  --- {0:AO(fn),1:GO};

  renturn son也將保留該屬性,這時fn已經執行完畢:

    fn.[[scope]] --- {0:GO}(AO(fn)被銷毀?);

  直到test()執行時,test.[[csope]]  --- {0:AO(son),1:AO(fn),2:GO}( test是son的另一個引用,實際上他們是同一個函數)。

  這時test想要訪問變數a,那麼他將先在自己的AO內查找,沒有,那麼他將到fn的AO里去查找,剛好有,所以最終列印的是1。

       這裡被看似已經被fn銷毀的AO(fn),實際上還被son引用著,所以它並沒有真正的被完全銷毀,只是對於fn來說,已經丟棄了對這個對象的引用,看起來像被銷毀了。這個還被son保留著的AO對象我們即稱之為閉包。閉包能幫助一個函數讀取另一個函數內部的變數,它起到了連接兩個函數的橋梁作用。

         總結一下,在JavaScripe中形成閉包需要三個要素:

    1,  父函數內定義了子函數。

    2,  子函數內訪問了父函數的變數。

    3,  子函數被返回。

 

  2,閉包的應用

    a) 變數私有化,但可以實現全局變數的效果

 1 function add(){
 2     var count = 0;
 3     return function (){
 4         count ++;
 5         //some code
 6         console.log(count);
 7     }
 8 }
 9 var myAdd = add();
10 myAdd();//1
11 myAdd();//2
12 myAdd();//3

    b) 用作(類似)緩存

 1 function person(){
 2     var money = 0;
 3     var obj = {
 4         pay:function (){
 5             if(money > 0){
 6                 console.log("I spent one yuan.");
 7                 money --;
 8             }else{
 9                 console.log("I run out of my money.");
10             }
11         },
12         make:function (){
13             console.log("I made one yuan.");
14             money ++;
15         }
16     };
17     return obj;
18 }
19 var person1 = person();
20 person1.pay();//"I run out of my money."
21 person1.make();//"I made one yuan."
22 person1.pay();//"I spent one yuan."

    c) 模塊化開發,防止變數污染

 1 var a = "Global";
 2 function p0(){
 3     console.log(a);
 4 }
 5 function p1(){
 6     var a = "p1";
 7     return function(){
 8         console.log(a);
 9     };
10 }
11 function p2(){
12     var a = "p2";
13     return function(){
14         console.log(a);
15     };
16 }
17 var myP1 = p1();
18 var myP2 = p2();
19 
20 p0();//"Global"
21 myP1();//"p1"
22 myP2();//"p2"

  大型項目一般都是多人協同開發,每個人負責不同的模塊,不可避免的,大家可能使用了相同的變數名,這將造成全局變數污染。使用閉包,即可解決這個問題題。瞭解了下一節的立即執行函數,這段代碼還可以加以優化。

 

二     立即執行函數

         在認識立即執行函數之前,讓我們先來瞭解執行符()的兩個特點

         1)只有表達式才能被()執行。

1 function test(){
2     console.log(1);
3 }();//error 這是函數聲明
4 var test = function (){
5     console.log(1);
6 }();//1 這是函數表達式

         2)能被()執行的表達式會被系統忽略函數名稱。

1 var test = function (){
2     console.log(1);
3 }();//1
4 console.log(test);//undefined
5 //這是一個有趣的現象:我們聲明瞭變數test,並把一個函數賦值給它,緊接著使用()執行了這個表達式,隨即列印出了1。
按理說,這時test的值應該是一個匿名函數的函數體才對,但實際上它是undefined,變數剛被聲明的狀態,即系統放棄了變數test對函數的引用。

  這很好的印證了()執行符的第二個特點。

  1,立即執行函數的形式

  我們知道"()"括弧實際上也是一種數學運算符,表示運算優先順序的。那麼我們當然可以把函數聲明用括弧包起來,使它成為一個表達式。這樣我們就可以使用()執行符馬上執行它並得到函數執行的結果了。

1 (function test(){
2     console.log(1);
3 }());//1
4 //集合()執行符的第二個特點,我們還可以將它優化
5 (function (){
6     console.log(1);
7 });//1

  以上就是立即執行函數的最終形式。另外,把()執行符放在函數聲明的括弧外面其實也是可以的。

1 (function (){
2     console.log(1);
3 })();

  2,立即執行函數的特點

  知道了()執行符的特點,其實我們不難發現:

    1)立即執行函數被聲明後會馬上執行函數體內的代碼。

    2)執行完畢後立即銷毀,不會被一直保存在記憶體中。

    3)只能被執行一次,不能起到代碼塊復用的功能。

  除了上述特點外,立即執行函數和普通函數的功能完全一樣。

  3,立即執行函數結合閉包的經典應用

 1 function fn(){
 2     var arr = [];
 3     for(var i = 0; i < 10; i++){
 4         arr[i] = function () {
 5             console.log(i);
 6         };
 7     }
 8     return arr;
 9 }
10 var myArr = fn();
11 myArr.map(function (item){
12     item();
13 });//10 10 10 10 10 10 10 10 10 10 

  我們是想依次輸出1--9啊!為什麼跑出來10個10呢?

  仔細想一想,不難發現,這是因為所有子函數和fn形成的是同一個閉包,所以最後都列印了10,那麼要怎樣才能實現我們想要的功能呢?

 1 function fn(){
 2     var arr = [];
 3     for(var i = 0; i < 10; i++){
 4        (function (j){
 5             arr[j] = function () {
 6                 console.log(j);
 7             }
 8        }(i));
 9     }
10     return arr;
11 }
12 var myArr = fn();
13 myArr.map(function (item){
14     item();
15 });//0 1 2 3 4 5 6 7 8 9    

  通過利用立即執行函數定義完即被執行的特點,使每個子函數都和fn形成單獨的閉包,再把每次迴圈的i的值當做它的實參傳遞進去,那麼最終子函數在執行時訪問到的其實都是各自閉包里的i的值了。這樣就得到了我們想要的結果了。

 

  雖然閉包能在很多地方發揮很大作用,但閉包也有它自身的缺陷:閉包將一直占用記憶體空間,嚴重時將導致記憶體泄漏,甚至系統崩潰。所以我們應該儘量避免使用閉包,如果別無他法,也應該在使用完後手動的解除它對記憶體的占用,比如把引用返回函數的變數賦值為null。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 過去十年,是前端覺醒的十年。 前端這個行業很年輕,更早時候其實是有前端工作但是沒有前端崗位。大家覺得這個東西程式員做也行,設計師做也行。前端工作一直存在,但是沒有人認為它是一個獨立的工種和崗位,也沒有人去發展它的工程體系,更沒有人去找它的核心價值。所以如果說過去十年前端是什麼樣的十年,我認為是覺醒的 ...
  • 本來是想做一個滑鼠點擊事件:A,B兩個東西,B先隱藏,點擊A,B出現,再點一次A,B消失,然後發現在判斷不同的情況下,出現了一點小問題 暫時沒有問題的寫法: 然後,我把它的判斷條件改了一下,其實這是我第一次寫的想法: 然後它的效果是,第一次點擊的時候沒有反應,要點第二次,B才會出來,有點不明白 ...
  • (馬蜂窩技術原創內容,公眾號 ID:mfwtech) 一份來自 Akamai 的研究報告顯示,在對 1048 名網購戶進行採訪後發現: 約 47% 的用戶期望他們的頁面在兩秒之內載入完成。 如果頁面載入時間超過 3s,約 40% 的用戶會選擇離開或關閉頁面。 約 47% 的用戶期望他們的頁面在兩秒之 ...
  • Vuex 是一個專為 Vue.js 應用程式開發的狀態管理模式,它採用集中式存儲管理應用的所有組件的狀態,註意:使用前需要先載入vue文件才可以使用(在node.js下需要使用Vue.use(Vuex)來安裝vuex插件,在瀏覽器環境下直接載入即可,vuex會自行安裝) vuex的使用方法很簡單,首 ...
  • 在項目中常常需要用級聯地址,為了實現這個功能,大家肯定會瘋狂得找插件快速實現,但是博主就喜歡使用純js來實現。 首先建立一個二維數組,第一列存省,第二列存城市並用分隔符分隔 建立兩個一維數組,一個存省,一個存城市 創建兩個初始變數,一個存當前選中得省,一個存當前選中得城市 給省的select選擇框添 ...
  • 先貼一張圖片做個例子 看不清圖片的朋友可以右鍵圖片,在<新標簽頁中打開圖片>; 可以看到,由於內容過長,導致後面操作的增刪改要拉到最後才能操作. 在我們的FastAdmin中,目前我還沒找到可以調整列表大小的文檔,如果有,歡迎大家告知.下麵是我用來解決這個問題的方法 我們可以在生成的頁面好添加兩段代 ...
  • 1 2 3 4 5 Document 6 13 21 22 23 24 25 ...
  • 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Document</title> 6 <style type="text/css"> 7 #div1{ 8 width: 200px; 9 ... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...