摘要:在編寫(xiě)一段析構(gòu)方法的研究代碼中,我遇到了交叉知識(shí)點(diǎn)導(dǎo)致的錯(cuò)誤在不同作用域,析構(gòu)方法與引用次數(shù)導(dǎo)致了不一樣的結(jié)果。前提本文假裝你已經(jīng)明白什么是析構(gòu)方法作用域及引用次數(shù)。當(dāng)析構(gòu)函數(shù)的調(diào)用鉤子去檢測(cè)引用數(shù)時(shí),全局的實(shí)例自然無(wú)法觸發(fā)這個(gè)事件。
在編寫(xiě)一段析構(gòu)方法的研究代碼中,我遇到了交叉知識(shí)點(diǎn)導(dǎo)致的錯(cuò)誤——在不同作用域,析構(gòu)方法與引用次數(shù)導(dǎo)致了不一樣的結(jié)果。前提
本文假裝你已經(jīng)明白什么是析構(gòu)方法、作用域及引用次數(shù)。關(guān)于后者,引用次數(shù)是 PHP 垃圾收集中的重要機(jī)制,它很大程度上,幫助 PHP 在程序運(yùn)行時(shí)清理內(nèi)存垃圾(參考:引用計(jì)數(shù)基礎(chǔ) - PHPDoc)。
正文 有誤的測(cè)試來(lái)看這段代碼:
class A { public $var = []; public function __construct() { echo "__construct: " . spl_object_hash($this) . " "; } public function __destruct() { echo "__destruct: " . spl_object_hash($this); } public function test() { throw new Exception("Hello"); } } $test = new A(); $test->test();
我的本意是“在拋出不捕獲的異常時(shí),析構(gòu)方法是否正常執(zhí)行”。結(jié)果是沒(méi)有執(zhí)行,OK,很穩(wěn):
__construct: 0000000045f0af9e00000000494744b0 Fatal error: Uncaught Exception: Hello in...
當(dāng)我們以為事情就此結(jié)束,后續(xù)往往會(huì)接踵而來(lái)。
翻車(chē)的代碼在公司前輩指出“你這段代碼有問(wèn)題,犯了作用域的錯(cuò)誤”之后,我是當(dāng)場(chǎng)宕機(jī)的。
啥,作用域?析構(gòu)方法?我是不是聽(tīng)錯(cuò)了,那玩意不是變量的概念么。
經(jīng)過(guò)我的追問(wèn),前輩告訴我:你把執(zhí)行代碼放到函數(shù)里試試。
避免文章過(guò)長(zhǎng),直接上差異部分的代碼,如下:
class A { // 與之前一致 } function test() { $test = new A(); $test->test(); } test();
結(jié)果如下:
__construct: 000000004b11d811000000006f9a75c7 __destruct: 000000004b11d811000000006f9a75c7 Fatal error: Uncaught Exception: Hello in...
心態(tài)如下:
說(shuō)好的不執(zhí)行呢?真是令人絕望。
當(dāng)場(chǎng)打臉,只好去琢磨“析構(gòu)方法的作用域”是個(gè)啥。搜索結(jié)果里看到了這樣的話(huà):
析構(gòu)函數(shù)會(huì)在到某個(gè)對(duì)象的所有引用都被刪除或者當(dāng)對(duì)象被顯式銷(xiāo)毀時(shí)執(zhí)行。來(lái)源:構(gòu)造與析構(gòu)函數(shù) - PHPDoc
讓我們推理一下:
函數(shù)結(jié)束后,該函數(shù)級(jí)別的作用域就結(jié)束了,而此時(shí)腳本還未結(jié)束。沒(méi)有任何引用的對(duì)象實(shí)例,自然可以執(zhí)行析構(gòu)方法;
全局作用域則不一樣,所以導(dǎo)致了對(duì)象在全局作用域結(jié)束后,沒(méi)機(jī)會(huì)調(diào)用析構(gòu)方法。
結(jié)果似乎明朗了。
深入當(dāng)然,淺嘗輒止可對(duì)不起我的好奇心。既然要搞明白這個(gè)問(wèn)題,那就問(wèn)一問(wèn)核心問(wèn)題:
證實(shí):函數(shù)級(jí)別的作用域結(jié)束與對(duì)象執(zhí)行析構(gòu)方法,是否有必然聯(lián)系?
新問(wèn)題:調(diào)用析構(gòu)方法與結(jié)束變量,誰(shuí)先誰(shuí)后?
相信在理清上述兩個(gè)問(wèn)題的答案后,這個(gè)文章也就沒(méi)有存在的意義了,笑~
函數(shù)級(jí)別的作用域結(jié)束與對(duì)象執(zhí)行析構(gòu)方法,是否有必然聯(lián)系?
很簡(jiǎn)單,咱們讓對(duì)象與函數(shù)的作用域脫鉤,就可以逆向地證實(shí)這一點(diǎn):
class A { // 與之前一致 } $i = 123; function test(&$i) // 通過(guò)引用機(jī)制,給函數(shù)的作用域增加污染變量 { $test = new A(); $i = $test; // 將對(duì)象實(shí)例的引用擴(kuò)展到全局作用域 $test->test(); } test($i);
結(jié)果如下:
__construct: 0000000042a054c3000000001f53236f Fatal error: Uncaught Exception: Hello in...
果然,當(dāng)引用計(jì)數(shù)不為 0 時(shí),析構(gòu)函數(shù)就不會(huì)被調(diào)用,賊穩(wěn)~
問(wèn)題二新問(wèn)題:調(diào)用析構(gòu)方法與結(jié)束變量,誰(shuí)先誰(shuí)后?
這個(gè)問(wèn)題就有點(diǎn)意思了,熟悉程序的朋友又應(yīng)該明白,遇到這種“X的某個(gè)機(jī)制是什么時(shí)候觸發(fā)的”,就應(yīng)該去看X的生命周期,X 泛指一切。
在經(jīng)過(guò)一番查找,我從《PHP7內(nèi)核剖析》中找到了 PHP 的生命周期,注意我標(biāo)紅圈的兩個(gè)地方:
清理全局變量與析構(gòu)方法的調(diào)用,我們就找到了。
但此時(shí)困惑了我的問(wèn)題就變成了:普通變量到底什么時(shí)候銷(xiāo)毀?
我翻遍了 PHP 的生命周期、網(wǎng)絡(luò)上的文章,也沒(méi)找到想要的答案。大家都在聊全局變量的銷(xiāo)毀事件,難道全局的普通變量是弱勢(shì)群體嗎?
直到我看到 PHP 手冊(cè)上的范例:
// 使用 global $a = 1; $b = 2; function Sum() { global $a, $b; $b = $a + $b; } Sum(); echo $b;
原來(lái) 全局范圍的普通變量 = 全局變量,這結(jié)論真是令我頭禿。
最終總結(jié)一下:
當(dāng)實(shí)例的引用為 0 時(shí),會(huì)步入銷(xiāo)毀階段,此時(shí),析構(gòu)函數(shù)才會(huì)啟用;
當(dāng)對(duì)象的實(shí)例位于全局作用域,該變量會(huì)在 全局變量銷(xiāo)毀 事件中銷(xiāo)毀,在此之前,全局變量的引用數(shù)至少為 1;
析構(gòu)函數(shù)的調(diào)用 發(fā)生在 全局變量銷(xiāo)毀 之前。
當(dāng)析構(gòu)函數(shù)的調(diào)用鉤子去檢測(cè)“引用數(shù)”時(shí),全局的實(shí)例自然無(wú)法觸發(fā)這個(gè)事件。
至于為什么會(huì)犯這樣的錯(cuò)誤,原因也有兩個(gè):
對(duì) PHP 的生命周期認(rèn)知模糊不清;
不清楚 PHP 的全局變量如何定義。
為什么會(huì)犯這兩個(gè)錯(cuò)誤,自然也有理由,但無(wú)論什么理由,都解決不了在面對(duì)知識(shí)點(diǎn)交叉時(shí),因?yàn)橹R(shí)盲點(diǎn)所犯下的錯(cuò)。下次學(xué)東西,還是跟著官方文檔學(xué)習(xí)吧。
圖片出處源自網(wǎng)絡(luò)或水印,侵刪。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/29766.html
摘要:二面向?qū)ο笥惺裁刺卣髅嫦驅(qū)ο蟮闹饕卣饔谐橄罄^承封裝和多態(tài)。析構(gòu)函數(shù)析構(gòu)函數(shù)是在引入的,它的作用與調(diào)用時(shí)機(jī)和構(gòu)造函數(shù)剛好相反,它在對(duì)象被銷(xiāo)毀時(shí)自動(dòng)執(zhí)行。 PHP面試專(zhuān)欄正式起更,每周一、三、五更新,提供最好最優(yōu)質(zhì)的PHP面試內(nèi)容。PHP中面向?qū)ο蟪?嫉闹R(shí)點(diǎn)有以下7點(diǎn),我將會(huì)從以下幾點(diǎn)進(jìn)行詳細(xì)介紹說(shuō)明,幫助你更好的應(yīng)對(duì)PHP面試常考的面向?qū)ο笙嚓P(guān)的知識(shí)點(diǎn)和考題。整個(gè)面向?qū)ο笪恼碌慕Y(jié)構(gòu)涉...
摘要:通過(guò)注冊(cè)自動(dòng)加載器,腳本引擎在出錯(cuò)失敗前有了最后一個(gè)機(jī)會(huì)加載所需的類(lèi)構(gòu)造函數(shù)和析構(gòu)函數(shù)構(gòu)造函數(shù)允行開(kāi)發(fā)者在一個(gè)類(lèi)中定義一個(gè)方法作為構(gòu)造函數(shù)。析構(gòu)函數(shù)會(huì)在到某個(gè)對(duì)象的所有引用都被刪除或者當(dāng)對(duì)象被顯式銷(xiāo)毀時(shí)執(zhí)行抽象類(lèi)支持抽象類(lèi)和抽象方法。 語(yǔ)言參考 1. 類(lèi)型 1.1 簡(jiǎn)介 showImg(https://segmentfault.com/img/bVbqlNJ?w=531&h=379);...
摘要:類(lèi)與對(duì)象基本概念如果在之后跟著的是一個(gè)包含有類(lèi)名的字符串,則該類(lèi)的一個(gè)實(shí)例被創(chuàng)建。如果該類(lèi)屬于一個(gè)名字空間,則必須使用其完整名稱(chēng)。如果一個(gè)類(lèi)被聲明為,則不能被繼承。命名空間通過(guò)關(guān)鍵字來(lái)聲明。 類(lèi)與對(duì)象 基本概念 new:如果在 new 之后跟著的是一個(gè)包含有類(lèi)名的字符串,則該類(lèi)的一個(gè)實(shí)例被創(chuàng)建。如果該類(lèi)屬于一個(gè)名字空間,則必須使用其完整名稱(chēng)。 Example #3 創(chuàng)建一個(gè)實(shí)例 ...
摘要:繼上一篇面試??純?nèi)容之面向?qū)ο蟀l(fā)表后,今天更新,需要的可以直接點(diǎn)擊文字進(jìn)行跳轉(zhuǎn)獲取。析構(gòu)函數(shù),當(dāng)對(duì)象被銷(xiāo)毀時(shí)調(diào)用。 PHP面試專(zhuān)欄正式起更,每周一、三、五更新,提供最好最優(yōu)質(zhì)的PHP面試內(nèi)容。繼上一篇PHP面試??純?nèi)容之面向?qū)ο螅?)發(fā)表后,今天更新(2),需要(1)的可以直接點(diǎn)擊文字進(jìn)行跳轉(zhuǎn)獲取。整個(gè)面向?qū)ο笪恼碌慕Y(jié)構(gòu)涉及的內(nèi)容模塊有: 一、面向?qū)ο笈c面向過(guò)程有什么區(qū)別?二、面向?qū)?..
閱讀 3044·2021-11-02 14:40
閱讀 854·2019-08-30 15:53
閱讀 1273·2019-08-30 15:53
閱讀 3269·2019-08-30 13:53
閱讀 3313·2019-08-29 12:50
閱讀 1142·2019-08-26 13:49
閱讀 1874·2019-08-26 12:20
閱讀 3672·2019-08-26 11:33