搜尋
首頁後端開發php教程PHP匿名函數及閉包

[iefreer] 轉載一篇對PHP閉包語法講解比較深入到位的文章,後續還會轉一篇這些新語法如何巧妙應用的文章。

匿名函數在程式語言中出現的比較早,最早出現在Lisp語言中,隨後很多的程式語言都開始有這個功能了,

目前使用比較廣泛的Ja​​vascript以及C#,PHP直到5.3才開始真正支援匿名函數,C++的新標準C++0x也開始支援了。

匿名函數是一類不需要指定標示符,而又可以被呼叫的函數或子例程,匿名函數可以方便的作為參數傳遞給其他函數,最常見應用是作為回調函數。

閉包(Closure)

說到匿名函數,就不得不提到閉包了,閉包是詞法閉包(Lexical Closure)的簡稱,是引用了自由變量的函數,這個被應用的自由變量將和這個函數一同存在,即使離開了創建它的環境也一樣,所以閉包也可認為是有函數和與其相關引用組合而成的實體。在某些語言中,在函數內定義另一個函數的時候,如果內部函數引用到外部函數的變量,則可能產生閉包。在運行外部函數時,一個閉包就形成了。

這個字和匿名函數很容易被混用,其實這是兩個不同的概念,這可能是因為很多語言實作匿名函數的時候允許形成閉包。

使用create_function()建立"匿名"函數

前面提到PHP5.3才開始正式支援匿名函數,說到這裡可能會有細心讀者有意見了,因為有個函數是可以產生匿名函數的: create_function函數,在手冊裡可以查到這個函數在PHP4.1和PHP5中就有了,這個函數通常也能作為匿名回呼函數使用,例如如下:

[php] 觀點 plaincopy

  1.    
  2.     2, 3, 4);  array_walk
  3. ($array, create_function($array, create_function('$value''echo $value'));'echo $value'));當然也能做更多的事情。 那為什麼這不算真正的匿名函數呢,我們先看看這個函數的回傳值,這個函數回傳一個字串,通常我們可以像下面這樣呼叫一個函數:
[php] view plaincopy

  •    
  •     echo
  •  
  • 'function a';  
  • }     $a
  •  = 
  • ();  
  • 我們在實現回調函數的時候也可以用這樣的方式,例如:[php] view plaincopy
  •    
  • ) {  

        

    // doing

      

    1.   
    2. # ...  
    3.        // done  ;  
    4. }  
    5. 這樣就能實現於函數do_something ()執行完成之後呼叫$callback指定的函數。回到create_function函數的回傳值:函數傳回一個唯一的字串函數名,出現錯誤的話則回傳FALSE。這麼說這個函數也只是動態的創建了一個函數,而這個函數是有函數名的,也就是說,其實這並不是匿名的。只是創建了一個全域唯一的函數而已。
    6. [php] view plaincopy
      1. $func = create_function( );  echo  
      2. $func// lambda_1  .  // Function created dynamic
      3.   
      4.          
      5.    
      6.       
      7. $my_func = 'lambda_1';  
      8. lambda_1(); // 不存在這個函數
      9.   

      上面這段程式碼的前面很好理解,create_function就是這麼用的,後面透過函數名來調用卻失敗了,這就有些不好理解了,php是怎麼保證這個函數是全域唯一的? lambda_1看起來也是一個很普通的函數名,如果我們先定義一個叫做lambda_1的函數呢?這裡函數的返回字串會是lambda_2,它在創建函數的時候會檢查是否這個函數是否存在知道找到合適的函數名,但如果我們在create_function之後定義一個叫做lambda_1的函數會怎麼樣呢? 這樣就出現函數重複定義的問題了,這樣的實作恐怕不是最好的方法,實際上如果你真的定義了名為lambda_1的函數也是不會出現我所說的問題的。這究竟是怎麼回事呢?上面程式碼的倒數2兩行也說明了這個問題,實際上並沒有定義名為lambda_1的函數。

      也就是說我們的lambda_1和create_function返回的lambda_1並不是一樣的!? 怎麼會這樣呢? 那隻能說明我們沒有看到實質,只看到了表面,表面是我們在echo的時候輸出了lambda_1,而我們的lambda_1是我們自己敲入的. 我們還是使用debug_zval_dump函數來看看吧。

        [php] view plaincopy
      1. $func ,function( ;');     
    7. $my_func_name
    8.  = 'lambda_1';     // string(9) "lambda_1" refcount(2)
    9.   
    10. debug_zval_dump($my_func_name); // string(8) "lambda_1" refcount(2)
    11. 他們的長度居然不一樣,長度不一樣也就是說不是同一個函數,所以我們呼叫的函數當然是不存在的,我們還是直接看看create_function函數到底都做了些什麼吧。實作見: $PHP_SRC/Zend/zend_builtin_functions.c
    12. [php] view plaincopy
      1. #define LAMBDA_TEMP_FUNCNAME    "__lambda_func"  _F  
      2. {  
      3.     // ... 省去無關代碼
      4.   // ... 省去無關代碼
      5.   
      6.     function_name = (char *) emalloc(sizeof("0lambda_"
      7. )+MAX_LENGTH_OF_LONG);  
      8.     function_name[0] = ' '
      9. ;  
      10. //       do
    13.  ngth = 1 + sprintf(function_name + 1, 
    14. “lambda_%d”, ++EG( lambda_count ));  
    15.     } 
    16. while (zend_hash_add(EG(function_table), function_name  
    17.     zend_hash_del(EG(function_table), LAMBDA_TEMP_FUNCNAME, sizeof(LAMBDA_TEMP_FUNCNAME));  
    18.     RETURN_STRINGL(function_name, function_name_length, 0);  
    19. }  
    20. 該函數在定義了一個函數之後,給函數起個名字,將函數名的第一個字符修改為'',然後在函數表中查找是否已經定義了這個函數,如果已經有了則產生新的函數名,第一個字元為空字元的定義方式比較特殊,因為在使用者程式碼中無法定義出這樣的函數,也不會存在命名衝突的問題了,這也是土種取巧(tricky)的做法,在了解了這個特殊的函數之後,我們其實還是可以調用到這個函數的,只要我們在函數名前加一個空字符就可以了,chr()函數可以幫助我們生成這樣的字串,例如前面建立的函數可以透過以下的方式存取:
    21. [php] 看純文字

       
    1. 「lambda_1」
    2. ;  
    3. $my_func(); // Hello  這種創建「匿名函數」的方式有一些缺點:
    4. 函數的定義是透過字串動態評估的方法,此時無法進行基本檢查的語法; 此類函數和普通函數沒有本質區別,無法實現閉包的效果。引入的__invoke 魔幻方法。符重載一些類似的,例如可以像下面這樣使用:[php] 查看 plaincopy

    1. class
    2.  Callme 
     

    函數

     __invoke(

    $phone_num

    ) {  

            
      echo
    1.  「你好:$phone_num」
    2. ;  
    3.     }  
    4. }     打電話給我();  $撥打(13810688888); 
    5. // "Hello: 13810688888  函數確實就是用這種方式實現的。 看純文字
      1. $func = f echo 
      2. "Hello, anonymous function"
      3. ;   }     
      4. echo
      5.  
      6. get object
      7.   echo get_class($func );  // Closure  
      8. 原來匿名函數也只是一個普通的類別而已。熟悉Javascript的同學對匿名函數的使用方法很熟悉了,PHP也使用和Javascript類似的語法來定義, 匿名函數可以賦值給一個變量, 因為匿名函數其實是一個類別實例, 所以能複製也是很容易理解的, 在Javascript中可以將一個匿名函數賦值給一個物件的屬性, 例如:[php] view plaincopyvar
       a = {};  

      a.callcom

      );}  

      1. a .call(); // alert called  // alert called
      2.   這在Javascript中使用的導致類別尋找類別中定義的方法,在PHP中屬性名稱和定義的方法名稱是可以重複的, 這是由PHP的類別模型所決定的, 當然PHP在這方面是可以改進的, 後續的版本中可能會允許這樣的調用,這樣的話就更容易靈活的實作一些功能了。目前想要實現這樣的效果也是有方法的: 使用另外一個魔幻方法__call(),至於怎麼實現就留給各位讀者當做習題吧。 閉包的使用
      3. PHP使用閉包(Closure)來實現匿名函數, 匿名函數最強大的功能也就在匿名函數所提供的一些動態特性以及閉包效果,匿名函數在定義的時候如果需要使用作用域外的變數需要使用以下的語法來實現:
      4. [php] view plaincopy

      $name

       

      $func

       = 
      1. function()  use
      2. ($name) {       
      3. }     $func (); // Hello TIPI Team  
      4. 語句看起來還是挺起來的比較語法, 因為和Javascript的作用域不同, PHP在函數內定義的變量預設就是局部變量, 而在Javascript中則相反,除了明確定義的才是局部變量, PHP在變異的時候則無法確定變量是局部變數還是上層作用域內的變量, 當然也可能有辦法在編譯時確定,不過這樣對於語言的效率和複雜性就有很大的影響。 這個語法比較直接,如果需要存取上層作用域內的變數則需要使用use語句來申明, 這樣也簡單易讀,說到這裡, 其實可以使用use來實現類似global語句的效果。 匿名函數在每次執行的時候都能訪問到上層作用域內的變量, 這些變量在匿名函數被銷毀之前始終保存著自己的狀態,例如如下的例子:
      5. [php] view plaincopy
        1. function getCounter() {    
        2.     return 
        3. function
        4. ()  use($i) { // 這裡如果使用引用傳入變數: use(&$i)  ++$i ;  
        5.     };  
        6. }  @ );  
        7. $counter
        8. (); 
        9. // 1
        10.   
        11. $counter(); // 1
        12.   
        13. 與增加P是透過拷貝的方式傳入上層變數進入匿名函數,如果需要改變上層變數的值則需要以引用的方式傳遞。所以上面得程式碼沒有輸出1, 2而是1,1閉包的實作前面提到匿名函數是透過閉包來實現的, 現在我們開始看看閉包(類)是怎麼實現的。匿名函數和普通函數除了是否有變數名稱以外並沒有差別,閉包的實作碼在$PHP_SRC/Zend/zend_closure.c。匿名函數"物件化"的問題已經透過Closure實現, 而對於匿名是怎麼樣存取到創建該匿名函數時的變數的呢?例如如下這段程式碼:
        14. [php] view plaincopy

        $i

        =100; function

        () 

        use

        (

        $i

        ) {  

        1.     debug_zval_dump($i);     
        2. $counter();  
        3. 透過VLD來查看這段編碼編譯什麼樣的opcode了[php] view plaincopy
          1. $ php -dvld.active=1 closure.php  
          2.    
          3. $計數器   # *  op                           以      
          4. ----------------------------------- - ------------------------------------------------  
          5. 0  >   分配                                                   !0, 100  
          6. 1      ZEND_DECLARE_LAMBDA_FUNCTION                              '%00%7Bclosure  
          7. 2      分配                                                 !1, ~1  
          8. 3      INIT_FCALL_BY_NAME                                       !1  
          9. 4      DO_FCALL_BY_NAME                                      0       0            
          10. 5    > 回                          1  
          11.    
          12. 函數 名稱:  {closure}
        4. 操作數量:  5  
        5. 編譯變量:  !0 = $i  
        6. 行     # *  操作                           獲取          ext  返回  操作數  
        7. ---- -------------------------------------------------- --------------------------  
        8.   3     0  >   FETCH_R                    $0      'i'         1      分配                          !0, 
        9. $0
        10.     4                                     0  
        11.         3     DO_FCALL                1          
        12. '有使用                                    從上到下,第1開始將100個情況賦值給! 0則表示$i,判斷執行ZEND_DECLARE_LAMB DA_FUNCTION,那我們去相關的操作碼執行函數看看這裡是怎麼執行的,這個操作碼的處理函數位於$PHP_SRC/Zend/zend_vm_execute.h中:
        13. [ php] 檢視純文字
        14. 靜態
        15.  int ZEND_FASTCALL  ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_CONST_CONST_OPvER(ZEND__NCODE_FUNCTION 

            zend_op *opline = EX(opline);  

           

              
        1. if (zend_hash_quick_find(EG(function_table), Z_STRVAL(opline->op1.u.constant), Z_STRSTRLENfline-VAL(opline->op1.u.constant), Z_STRLENfL.Lopf. ), (void *) &op_arra  
        2. y) == 失敗 ||  
        3.         zend_error_noreturn(E_ERROR, 
        4. "未找到用於閉包的基本lambda 函數「
        5. );  
        6.     }  
        7.   .u.var).tmp_var, op_array TSRMLS_CC);  
        8.    
        9.     ZEND_VM_NEXT_OPCODE();  
        10. }  

          這個函數呼叫了zend_create_closure()函數來建立一個閉包物件, 那我們繼續看看位於$PHP_SRC/Zend/zend_closures.c的zend_create_closure()函數都做了些什麼。

          [php] view plaincopy

          1. ZEND_API void zend_create_closure(zval *res, zend_function *func TS     zend_closure *closure;  
          2.    
          3.     object_init_ex(cmure);
          4.    
          5.     closure = (zend_closure *)zend_object_ 
          6.     closure->func = *func;  
          7.    
          8.    ->func.type == ZEND_USER_FUNCTION) { // 如果是使用者定義的匿名函數
          9.   
          10. static_variables) {  
          11.             HashTable * static_variables = closure->func.op_array.static_variables;     🎠    
          12.             ALLOC_HASHTABLE(closure->func.op_array.static_variables );               zend_hash_init(closure->func.op_array.static_variables, zend_hash_Delements;  
          13.    
          14.             
          15. // 循環中使用靜態變數項清單,而使用
          16.               zend_hash_apply_with_arguments(static_variables  zend_hash_apply_with_arguments(static_variables TSRMLS,_LLLLam集co_pools_pMr. >func.op_array.static_variables);  
          17.         }  
          18.  +;  
          19.     }  
          20.      }  
          21. 如上段程式碼註解所說, 繼續看看zval_copy_static_var( )函數的實作:
          22. [php] view plaincopy
            1. static int zval_copy_static_var(zval **p TSRMLS_DC, int num_args, va_list args,
            2.     HashTable *target = va_arg(args, HashTable*);  
            3.     zend_bool is_ref;  
            4.    
            5.    版本下對靜態中變數所影響的變數值  
            6.     if (Z_TYPE_PP(p) & (IS_LEXICAL_VAR|IS_LEXICAL_REF))) = Z_TYPE_PP(p) & IS_LEXICAL_REF;  
            7.      (active_symbol_table)) {  
            8.             zend_rebuild_symbol_table(TSRMLS_C);  
            9.         }  
            10.         // 如果當前作用域內沒有這個變量  
            11.         
            12. if (zend_hash_quick_find(EG( active_symbol_table), key->arKey, key->nKeyLength, key->h, (void **) &p) == FAILURE) {   
            13.  (is_ref) {  
            14.                 zval * tmp;  
            15.                   
            16.                 ALLOC_INIT_ZVAL(tmp);   Z_SET_ISREF_P(tmp);  
            17.                 zend_hash_quick_add(EG()_symbolboltabletable), sizeof(zval*), (void**)&p);  
            18.             } else
            19.  {     
            20.                 p =         zend_error(E_NOTICE,
            21. "Undefined variable : %s", key->arKey);  
            22.             
            23.  {              
            24. // 若有這個變數,而 依變數進行引用或複製
            25.               
            26. if         SEPARATE_ZVAL_TO_MAKE_IS_REF(p);  
            27.          } else if (Z_ISREF_PP(p)) {  
          23.  
          24.             }  
          25.        if
          26.  ( zend_hash_quick_add(target, key->arKey, key->nKeyLength, key->h, p, sizeof(zval*), NULL) == SUCCESS) {  🠎 ;  
          27.     }       return
          28.  ZEND_HASH_APPLY_KEEP;  
          29. } ly_with_arguments()函數, 每次讀取hash表中的值之後就由這個函數進行處理,而這個函數對所有use語句定義的變數值賦值給這個匿名函數的靜態變量, 這樣匿名函數就能存取到use的變數了。
          30. 原文連結:
          31. http://www.php-internals.com/book/?p=chapt04/04-04-anonymous-function 參考閱讀:http://php.net/manual/zh/functions.anonymous.php 以上就介紹了PHP匿名函數及閉包,包括了方面的內容,希望對PHP教程有興趣的朋友有幫助。
    陳述
    本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
    PHP中的依賴注入:避免常見的陷阱PHP中的依賴注入:避免常見的陷阱May 16, 2025 am 12:17 AM

    DependencyInjection(DI)inPHPenhancescodeflexibilityandtestabilitybydecouplingdependencycreationfromusage.ToimplementDIeffectively:1)UseDIcontainersjudiciouslytoavoidover-engineering.2)Avoidconstructoroverloadbylimitingdependenciestothreeorfour.3)Adhe

    如何加快PHP網站:性能調整如何加快PHP網站:性能調整May 16, 2025 am 12:12 AM

    到Improveyourphpwebsite的實力,UsEthestertate:1)emplastOpCodeCachingWithOpcachetCachetOspeedUpScriptInterpretation.2)優化的atabasequesquesquesquelies berselectingOnlynlynnellynnessaryfields.3)usecachingsystemssslikeremememememcachedisemcachedtoredtoredtoredsatabaseloadch.4)

    通過PHP發送大規模電子郵件:有可能嗎?通過PHP發送大規模電子郵件:有可能嗎?May 16, 2025 am 12:10 AM

    是的,ItispossibletosendMassemailswithp.1)uselibrarieslikeLikePhpMailerorSwiftMailerForeffitedEmailsending.2)enasledeLaysBetenemailstoavoidSpamflagssspamflags.3))

    PHP中依賴注入的目的是什麼?PHP中依賴注入的目的是什麼?May 16, 2025 am 12:10 AM

    DependencyInjection(DI)inPHPisadesignpatternthatachievesInversionofControl(IoC)byallowingdependenciestobeinjectedintoclasses,enhancingmodularity,testability,andflexibility.DIdecouplesclassesfromspecificimplementations,makingcodemoremanageableandadapt

    如何使用PHP發送電子郵件?如何使用PHP發送電子郵件?May 16, 2025 am 12:03 AM

    使用PHP發送電子郵件的最佳方法包括:1.使用PHP的mail()函數進行基本發送;2.使用PHPMailer庫發送更複雜的HTML郵件;3.使用SendGrid等事務性郵件服務提高可靠性和分析能力。通過這些方法,可以確保郵件不僅到達收件箱,還能吸引收件人。

    如何計算PHP多維數組的元素總數?如何計算PHP多維數組的元素總數?May 15, 2025 pm 09:00 PM

    計算PHP多維數組的元素總數可以使用遞歸或迭代方法。 1.遞歸方法通過遍歷數組並遞歸處理嵌套數組來計數。 2.迭代方法使用棧來模擬遞歸,避免深度問題。 3.array_walk_recursive函數也能實現,但需手動計數。

    PHP中do-while循環有什麼特點?PHP中do-while循環有什麼特點?May 15, 2025 pm 08:57 PM

    在PHP中,do-while循環的特點是保證循環體至少執行一次,然後再根據條件決定是否繼續循環。 1)它在條件檢查之前執行循環體,適合需要確保操作至少執行一次的場景,如用戶輸入驗證和菜單系統。 2)然而,do-while循環的語法可能導致新手困惑,且可能增加不必要的性能開銷。

    PHP中如何哈希字符串?PHP中如何哈希字符串?May 15, 2025 pm 08:54 PM

    在PHP中高效地哈希字符串可以使用以下方法:1.使用md5函數進行快速哈希,但不適合密碼存儲。 2.使用sha256函數提高安全性。 3.使用password_hash函數處理密碼,提供最高安全性和便捷性。

    See all articles

    熱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

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

    熱門文章

    北端:融合系統,解釋
    1 個月前By尊渡假赌尊渡假赌尊渡假赌
    Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
    4 週前By尊渡假赌尊渡假赌尊渡假赌
    <🎜>掩蓋:探險33-如何獲得完美的色度催化劑
    2 週前By尊渡假赌尊渡假赌尊渡假赌

    熱工具

    Safe Exam Browser

    Safe Exam Browser

    Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

    WebStorm Mac版

    WebStorm Mac版

    好用的JavaScript開發工具

    Dreamweaver CS6

    Dreamweaver CS6

    視覺化網頁開發工具

    記事本++7.3.1

    記事本++7.3.1

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

    MantisBT

    MantisBT

    Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。