摘要:概念理解使用行為可以在不修改現有類的情況下,對類的功能進行擴充。最后將行為名稱和行為實例放到的屬性中,至此,行為的綁定就結束了。不過在解除的時候雖然都是刪掉相應的,但是解除行為還需要解除在綁定行為的時候綁定的事件,這點不太一樣。
概念理解:使用行為(behavior)可以在不修改現有類的情況下,對類的功能進行擴充。 通過將行為綁定到一個類,可以使類具有行為本身所定義的屬性和方法,就好像類本來就有這些屬性和方法一樣。 而不需要寫一個新的類去繼承或包含現有類。在功能上類似于 Traits ,達到類似于多繼承的目的。行為的實現demo
"afterAttach" ]; } public function afterAttach() { echo "事件已觸發"; } } // 在控制器或者命令行下調用 $myClass = new MyClass(); $myBehavior = new BehaviorTest(); // 將行為綁定到 MyClass 的實例 $myClass->attachBehavior("test", $myBehavior); // MyClass 實例調用行為類中的屬性 echo $myClass->_val; // MyClass 實例調用行為類中的方法 $myClass->getOutput(); // MyClass 實例觸發行為類中定義的事件 $myClass->trigger(BehaviorTest::EVENT_AFTER_SAVE);行為的綁定原理
我們先來看一下 $myClass->attachBehavior("test", $myBehavior); 行為綁定的時候做了哪些事情?好的,又是我們的老朋友yiiaseComponent了
// yiiaseComponent 的部分代碼 private $_behaviors; public function behaviors() { return []; } public function attachBehavior($name, $behavior) { $this->ensureBehaviors(); return $this->attachBehaviorInternal($name, $behavior); } public function ensureBehaviors() { if ($this->_behaviors === null) { $this->_behaviors = []; foreach ($this->behaviors() as $name => $behavior) { $this->attachBehaviorInternal($name, $behavior); } } } private function attachBehaviorInternal($name, $behavior) { if (!($behavior instanceof Behavior)) { $behavior = Yii::createObject($behavior); } if (is_int($name)) { $behavior->attach($this); $this->_behaviors[] = $behavior; } else { if (isset($this->_behaviors[$name])) { $this->_behaviors[$name]->detach(); } $behavior->attach($this); $this->_behaviors[$name] = $behavior; } return $behavior; }
$myClass 調用 attachBehavior() 傳入倆個參數,一個是行為名稱,另一個是行為類的名稱或實例或者是數組,接著 attachBehavior() 調用了 ensureBehaviors(),這個函數我們暫時用不到,因為我們沒有在MyClass里面重載behaviors(),不過也能大概猜到ensureBehaviors()的用途了。再往下調用的是私有函數attachBehaviorInternal(),這個函數先判斷傳進來的$behavior 是否已實例化,如果還沒有則進行實例化,再通過$name判斷是匿名行為還是命名行為,如果是命名行為,需要查看是否已經綁定同名的行為,如果綁定了同名的行為,會將以前的行為先解綁再調用$behavior->attach($this);[注:這里$this指的MyClass實例,也就是$myClass],這樣我們就來到了yiiaseBehavior的attach()方法,下面是attach()方法的源碼:
public function attach($owner) { $this->owner = $owner; foreach ($this->events() as $event => $handler) { $owner->on($event, is_string($handler) ? [$this, $handler] : $handler); } }
$this->owner = $owner;[注:這里$this 指的是$behavior也就是類BehaviorTest的實例],這句代碼指定了行為類的主人是誰,后面的代碼看著似乎似曾相識?是的,就是將行為類events()方法里面的事件也綁定的宿主身上,這里不具體展開,有興趣的小伙伴可以看一下淺析Yii2.0的事件Event。最后將行為名稱和行為實例放到$myClass的屬性_behavior中,至此,行為的綁定就結束了。好像也沒干什么啊,我們現在可以打印一下$myClass的數據結構是怎樣的?
commoncomponentsMyClass Object ( [_events:yiiaseComponent:private] => Array ( [eventAfterAttach] => Array ( [0] => Array ( [0] => Array ( [0] => commoncomponentsBehaviorTest Object ( [_val] => 我是BehaviorTest里面的公有屬性_val [owner] => commoncomponentsMyClass Object *RECURSION* ) [1] => afterAttach ) [1] => ) ) ) [_eventWildcards:yiiaseComponent:private] => Array ( ) [_behaviors:yiiaseComponent:private] => Array ( [test] => commoncomponentsBehaviorTest Object ( [_val] => 我是BehaviorTest里面的公有屬性_val [owner] => commoncomponentsMyClass Object *RECURSION* ) ) )
可以看到$myClass已經綁定了一個行為test,綁定了一個事件eventAfterAttach,那么綁定行為以后,是怎么調用行為類里面的屬性和方法呢?
行為的使用原理還是看一下demo里面$myClass->_val這句代碼是怎么執行的?根據上面$myClass的數據結構可以看出并沒有_val這個屬性,但是yiiaseComponent里面實現了__get()這個魔術方法,我們看一下源碼。
public function __get($name) { $getter = "get" . $name; if (method_exists($this, $getter)) { // read property, e.g. getName() return $this->$getter(); } // behavior property $this->ensureBehaviors(); foreach ($this->_behaviors as $behavior) { if ($behavior->canGetProperty($name)) { return $behavior->$name; } } if (method_exists($this, "set" . $name)) { throw new InvalidCallException("Getting write-only property: " . get_class($this) . "::" . $name); } throw new UnknownPropertyException("Getting unknown property: " . get_class($this) . "::" . $name); }
又似曾相識?是的,跟yiiaseBaseObject里面屬性的實現類似,有興趣的小伙伴可以看一下淺析Yii2.0的屬性Property。然后直接看注釋behavior property部分,又去調用了ensureBehaviors(),先不管,接著又去遍歷_behaviors這個屬性,根據上面$myClass的數據結構得知,此時foreach里面的$behavior就是行為類commoncomponentsBehaviorTest實例,先通過canGetProperty判斷_val是否可讀或者存在,大家可以去看yiiaseBaseObject里面該方法的實現。我們這里返回的是true,然后就直接通過commoncomponentsBehaviorTest的實例$behavior返回_val的值。
根據上面獲取行為類里面屬性的流程我們注意到:
因為是通過實例化行為類去調用的屬性,所以屬性是protected或者是private是獲取不到的。
如果Component綁定了多個行為,并且多個行為中有同名的屬性,那么該Component獲取的是第一個行為類里面的該屬性。
那么行為類里面的方法是怎么被調用的呢?屬性的調用是通過__get()來實現的,很容易聯想到方法的調用是通過__call()來實現的,我們查看一下yiiaseBaseObject源碼,果然里面實現__call()這個魔術方法,下面是源碼,然后對照上面$myClass的數據結構一看就明白了。需要注意的是,跟上面屬性的調用一樣,方法也必須是public的,protected 、private方法是調用不了的。
public function __call($name, $params) { $this->ensureBehaviors(); foreach ($this->_behaviors as $object) { if ($object->hasMethod($name)) { return call_user_func_array([$object, $name], $params); } } throw new UnknownMethodException("Calling unknown method: " . get_class($this) . "::$name()"); }
注意到__call()里面又有一個老朋友ensureBehaviors()這個函數似乎無處不在?是的,查看一下yiiaseComponent里面的源碼我們可以發現所有的公有方法都調用了這個函數,那么這個函數到底是干嘛的呢,其實demo里面綁定行為的方式可以稱為主動綁定,就是我們主動調用函數attachBehavior()去綁定行為的,對應的就是被動綁定了,實現方式就是在待綁定行為的類里面重載behaviors()這個函數就可以實現綁定了,相當于一個行為的配置項。倆種綁定方式看個人喜好了,如果一個類需要綁定的行為很明確,推薦使用配置項的方法去綁定,也就是被動綁定。下面是將demo里面的綁定方式改成被動綁定。
new BehaviorTest() ]; } } 此時可以將demo中 $myClass->attachBehavior("test", $myBehavior); 這句代碼去掉,$myClass也是同樣可以調用類 BehaviorTest 里面的屬性和方法小結
這倆天查看了Yii2.0事件、行為的實現方式,覺得有很多相似的地方,都是通過yiiaseComponent來實現的,通過打印的數據結構也可以看到,Component主要就是圍繞_events _eventWildcards _behaviors這三個屬性展開的,其中第二個屬性是 事件的通配符模式,也可以歸屬到 事件中,那么這樣Component的主要功能就是就是實現了 事件和行為。并且實現原理上也是相似的,都是往Component里面綁定事件和行為的handle,然后觸發事件或者行為的時候,再去回調相應的handle。不過在解除的時候雖然都是刪掉相應的handle,但是解除行為還需要解除在綁定行為的時候綁定的事件,這點不太一樣。
以上總結參考了深入理解Yii2.0,其實以前就看過,但是也只是局限于看過,沒有自己跑demo調試、查看源代碼,然后就誤以為自己明白了,其實過倆天什么都不記得了。所以現在通過寫博客來加深自己的理解,由于水平有限,歡迎小伙伴交流和指正。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/31590.html
摘要:概念理解第一次看深入理解的時候,我也是懵逼的,屬性不就是類的屬性嗎,有什么好說的。屬性的實現步驟繼承自。聲明一個用于保存該屬性的私有成員變量。如果只提供了,那么該屬性為只讀屬性,只提供了,則為只寫。 概念理解:第一次看深入理解Yii2.0的時候,我也是懵逼的,屬性不就是類的屬性嗎,有什么好說的。后來才知道Yii框架對成員變量和屬性做了區分,那類的成員變量和屬性到底是什么關系又有什么區別...
摘要:全局級別利用實例在整個應用的生命周期中全局可訪問的特性,來實現這個全局事件的。類級別通過維護類的屬性數組,觸發事件時通過類名和事件名稱取到當前類以及父類的數據,再通過函數觸發。 概念理解:在某一個事件(trigger)發生的時候,觸發預先設定(on)的代碼,這是代碼解耦的一種方式。 事件按照級別分為三類 1. 實例級別 綁定事件、觸發事件的類繼承的是Component,只在當前示例中運...
摘要:行為所要響應的事件重載方法,表示這個行為將對類何種事件進行何種反饋。行為用的最多的,也是對于各種事件的響應。當出現命名沖突時,行為會自行排除沖突,自動使用先綁定的行為。目前還沒有能支持行為。 Yii基礎 行為(Behavior) 行為(behavior)可以在不修改現有類的情況下,對類的功能進行擴充。 通過將行為綁定到一個類,可以使類具有行為本身所定義的屬性和方法,就好像類本來就有這些...
摘要:另外,當并行器滿足條件提前退出時,所有正在執行的子行為也應該立即被終止,我們在函數中調用每個子節點的終止方法監視器監視器是并行器的應用之一,通過在行為運行過程中不斷檢查是否滿足某條件,如果不滿足則立刻退出。將條件放在并行器的尾部即可。 從上古卷軸中形形色色的人物,到NBA2K中揮灑汗水的球員,從使命召喚中詭計多端的敵人,到刺客信條中栩栩如生的人群。游戲AI幾乎存在于游戲中的每個角落,默...
摘要:否則會出現兩個下拉刷新之前之后禁用炫光和回彈效果將屬性制定為,可以禁用默認的滾動邊界效果。完整的地址源碼最終效果閱讀原文討論地址使用控制滾動行為自定義下拉刷新和溢出效果如果你想參與討論,請點擊這里 dev-reading/fe 是一個閱讀、導讀、速讀的 repo,不要依賴于 dev-reading/fe 學習知識。本 repo 只是一個快速了解文章內容的工具,并不提供全文解讀和翻譯。你...
閱讀 3859·2023-04-26 00:36
閱讀 2667·2021-11-16 11:44
閱讀 1082·2021-11-15 17:58
閱讀 1665·2021-09-30 09:47
閱讀 1208·2019-08-30 13:05
閱讀 1539·2019-08-30 12:55
閱讀 2409·2019-08-30 11:02
閱讀 2718·2019-08-29 17:01