PHP 中 parent、self、static、$this 的區別 & 後期靜態綁定詳解

自 PHP 5.3.0 起,PHP 增長了一個叫作後期靜態綁定的功能,用於在繼承範圍內引用靜態調用的類。 雖然也能夠調用非靜態方法,可是不會在運行時綁定。php

static 再也不只是簡單的靜態修飾關鍵字。而是還能夠調用類的靜態方法,非靜態方法,爲何靜態非靜態要分開說呢,由於調用的效果是不同的。函數

實例

class  A  {
	
	protected $name = 'A';
	static $alias = 'a';
	const HASH = 'md5';

	public function dd() {
		echo 'name:' . $this->name . PHP_EOL;
		echo 'self-alias:' . self::$alias . PHP_EOL;
		echo 'static-alias:' . static::$alias . PHP_EOL;  // 後期靜態綁定
		echo 'self-hash:' . self::HASH . PHP_EOL;
		echo 'static-hash:' . static::HASH . PHP_EOL;  // 後期靜態綁定

		var_dump(new self);
		var_dump($this);
		var_dump(new static);

	}


    public static function  who () {
        echo  __CLASS__ ; 
        echo ' [ This is A ]';
        echo PHP_EOL;
    }

    public static function  test () {
        static:: who ();  // 後期靜態綁定從這裏開始
    }

    public static function  test2 () {
        self:: who ();
    }
}
 
class  B  extends  A  {

	protected $name = 'B';
	static $alias = 'b';
	const HASH = 'sha1';

    public static function  who () {
        echo  __CLASS__ ; 
        echo ' [ This is B ]';
        echo PHP_EOL;
    }
}



(new B)->dd()

結果輸出:this

name:B
self-alias:a
static-alias:b
self-hash:md5
static-hash:sha1

object(admin\controllers\A)
  protected 'name' => string 'A' (length=1)

object(admin\controllers\B)
  protected 'name' => string 'B' (length=1)

object(admin\controllers\B)
  protected 'name' => string 'B' (length=1)

執行:code

B::who();
B::test();
B::test2();

輸出:對象

B [ This is B ]
B [ This is B ]
A [ This is A ]

總結說明:

  • self__CLASS__,都是對當前類的靜態引用,取決於定義當前方法所在的類。也就是說,self 寫在哪一個類裏面, 它引用的就是誰。繼承

  • $this 指向的是實際調用時的對象,也就是說,實際運行過程當中,誰調用了類的屬性或方法,$this 指向的就是哪一個對象。但 $this 不能訪問類的靜態屬性和常量,且 $this 不能存在於靜態方法中。md5

  • static 關鍵字除了能夠聲明類的靜態成員(屬性和方法)外,還有一個很是重要的做用就是後期靜態綁定。get

  • parent,是對當前類的父類的靜態引用。string

  • self 能夠用於訪問類的靜態屬性、靜態方法和常量,但 self 指向的是當前定義所在的類,這是 self 的限制。hash

  • $this 指向的對象所屬的類和 static 指向的類相同。

  • static 能夠用於靜態或非靜態方法中,也能夠訪問類的靜態屬性、靜態方法、常量和非靜態方法,但不能訪問非靜態屬性。

  • 靜態調用時,static 指向的是實際調用時的類;非靜態調用時,static 指向的是實際調用時的對象所屬的類。

後期靜態綁定

後期靜態綁定(也叫延遲靜態綁定),可用於在繼承範圍內引用靜態調用的類,也就是代碼運行時最初調用的類。

工做原理

確切地說,static 後期靜態綁定的工做原理是存儲了上一個非轉發調用(non-forwarding call)的類名。

當進行靜態方法調用時,該類名(static指向的類名)爲明確指定的那個(一般是 :: 運算符的左側部分),即實際調用時的類。

如:上面例子中的

A::test();  //A::test() 調用的是 static::who(),這裏static指向的即是A,因此執行的就是A::who();
B::test();  //A::test() 調用的是 static::who(),這裏static指向的即是B,因此執行的就是B::who();

static指向的類名,指向的就是實際調用的類

對比

static 和 self 的區別:

  • self 能夠用於訪問類的靜態屬性、靜態方法和常量,但 self 指向的是當前定義所在的類,這是 self 的限制。

  • static 也能夠用於訪問類的靜態屬性、靜態方法和常量,static 指向的是實際調用時的類。當進行非靜態方法調用時,該類名(static指向的類名)爲該對象所屬的類,即實際調用時的對象所屬的類。

static 和 $this 有點相似,但又有區別:

  • $this 指向的對象所屬的類和 static 指向的類相同。
  • $this 不能用於靜態方法中,也不能訪問類的靜態屬性和常量。
  • $this 指向的是實際調用的對象。
  • static 能夠用於靜態或非靜態方法中,也能夠訪問類的靜態屬性、靜態方法、常量和非靜態方法,但不能訪問非靜態屬性。
  • static 指向的是實際調用時的對象所屬的類。

轉發調用(forwarding call)

所謂的轉發調用(forwarding call)指的是經過如下幾種方式進行的靜態調用:self::,parent::,static:: 以及 forward_static_call() 。

可用 get_called_class() 函數來獲取被調用的方法所在的類名。

如下四種形式的調用,都是轉發調用:

self::
parent::
static::
forward_static_call()

除此以外的調用,就是非轉發調用。

非轉發調用(non-forwarding call)

後期靜態綁定的工做原理是存儲了上一個非轉發調用(non-forwarding call)的類名。

經過具體的類名或具體的對象進行的調用都是非轉發調用。

注意事項

非靜態環境下的私有方法的查找順序

在非靜態環境下,在類的非靜態方法中,使用 $this 和 static 調用類的私有方法時,執行方式有所不一樣。

  • $this 會優先尋找所在定義範圍(父類)中的私有方法,若是存在就調用。
  • static 是先到它指向的類(子類)中尋找私有方法,若是找到了就會報錯,由於私有方法只能在它所定義的類內部調用;若是沒找到,再去所在定義範圍(父類)中尋找該私有方法,若是存在就調用。

具體來講,$this 會先到所在定義範圍內尋找私有方法,再到它指向的對象所屬的類中尋找私有方法,而後尋找公有方法,最後到所在定義範圍內尋找公共方法。只要找到了匹配的方法,就調用,並中止查找。

而 static 則是先到它指向的類中尋找私有方法,再尋找共有方法;而後到所在定義範圍內尋找私有方法,再尋找共有方法。只要找到了匹配的方法,就調用,並中止查找。

下面是一個例子:

<?php
 class  A  {
    private function  foo () {
        var_dump($this); echo '--';
        var_dump(new static); echo '--';
 
        echo __CLASS__; echo '--';
        echo get_called_class();
        echo '<br>';
    }
 
    public function  test () {
        $this -> foo ();
        static:: foo ();
        echo '<br>';
    }
}
 
class  B  extends  A  { }
 
class  C  extends  A  {
    private function foo () {
        echo 'this is C';
    }
}
 
(new  B())->test();
(new  C())->test();
輸出結果爲:

object(B)#1 (0) { } --object(B)#2 (0) { } --A--B
object(B)#1 (0) { } --object(B)#2 (0) { } --A--B
 
object(C)#1 (0) { } --object(C)#2 (0) { } --A--C
 
Fatal error: Uncaught Error: Call to private method C::foo() from context 'A'

關於後期靜態綁定的解析

後期靜態綁定的解析會一直到取得一個徹底解析了的靜態調用爲止。若是靜態調用使用了 parent:: 或者 self:: 等轉發調用的形式,將會轉發調用信息。

<?php
class  A  {
    public static function  foo () {
        static:: who ();
    }
 
    public static function  who () {
        echo  __CLASS__ . "\n" ;
    }
}
 
class  B  extends  A  {
    public static function  test () {
        A :: foo ();
        parent :: foo ();
        self :: foo ();
        static::foo();
        forward_static_call(['A', 'foo']);
        echo '<br>';
    }
 
    public static function  who () {
        echo  __CLASS__ . "\n" ;
    }
}
 
class  C  extends  B  {
    public static function  who () {
        echo  __CLASS__ . "\n" ;
    }
 
    public static function test2() {
        self::test();
    }
}
 
class  D  extends  C  {
    public static function  who () {
        echo  __CLASS__ . "\n" ;
    }
}
 
B::foo();
B::test();
 
C::foo();
C::test();
 
D::foo();
D::test2();

以上的輸出結果爲:

B A B B B B 
C A C C C C 
D A D D D D

static 後期靜態綁定的工做原理是存儲了上一個非轉發調用(non-forwarding call)的類名。請記住這句話。

下面的例子是非轉發調用。

A::foo();  // 輸出 A
 
B::foo();   // 輸出 B
 
C::foo();   // 輸出 C

後期靜態綁定 static ,是定義在了 foo() 方法中,哪一個類經過非轉發調用的形式調用 foo() 方法, foo() 方法中的 static 指向的就是哪一個類。

可是,若是經過轉發調用的形式,調用 foo() 方法,如:

parent :: foo ();
self :: foo ();
static::foo();
forward_static_call(['A', 'foo']);

那麼,就以轉發調用代碼所在的方法 test() 爲準,哪一個類經過非轉發調用的形式調用 test() 方法, foo() 方法中的 static 指向的就是哪一個類。

假如調用 test() 方法時,也採用了轉發調用的形式,如:

public static function test2() {
    self::test();
}

那麼,就以 test2() 方法爲準 ... 依次類推。

也就是說,在使用了後期靜態綁定的基類中,後期靜態綁定所在的方法若是被轉發調用,則 static 的指向,會一直向上追溯,直到遇到非轉發調用的形式。

相關文章
相關標籤/搜索