首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP static 靜態關鍵字深度解析:與 self 的關鍵區別與晚期靜態綁定

     編輯:PHP 綜合知識

前言

在學習設計模式(特別是單例模式)時,`static` 關鍵字的行為往往是困惑的來源。本文將從語言規格、記憶體模型、繼承多態三個維度,徹底釐清 `static`、`self`、`static`(晚期靜態綁定)的本質區別,並附帶可直接執行的驗證代碼。

一、static 關鍵字核心語義

根據 PHP 官方手冊:

將類屬性或方法聲明為 static,使其無需實例化類即可訪問。靜態屬性不能通過已實例化的對象訪問(但靜態方法可以)。

四大核心特性

  1. 訪問途徑分離:靜態屬性只能用 `ClassName::$prop` 或 `self::$prop`/`static::$prop` 訪問,不可用 `$obj->prop`。
  2. 操作符限制:嚴禁使用 `->` 操作符訪問靜態成員。
  3. 無 $this 上下文:靜態方法內部不能出現 `$this`,即無法直接訪問非靜態屬性/方法。
  4. 類級共享:靜態成員屬於類本身,而非某個實例。所有實例、子類、外部代碼訪問的是同一塊記憶體地址。

二、晚期靜態綁定 (Late Static Bindings, LSB)

引入版本:PHP 5.3.0

核心問題:`self::` 在繼承鏈中總是解析到「定義該方法的類」,而非「實際調用的類」。這導致靜態方法無法實現多態。

LSB 工作原理

  • 記錄「非轉發調用」的類名(即 `::` 左側的類名,或對象所屬類)。
  • `self::`、`parent::`、`static::`、`forward_static_call()` 屬於「轉發調用」,不會更新這個記錄。
  • `get_called_class()` 可獲取實際調用類名;`static::` 則在當前作用域解析為該類名。

三、self vs static:實戰對比

定義基類 `Vehicle` 與子類 `Sedan`,均擁有靜態屬性 `$name`。

案例 1:使用 self::

<?php
class Vehicle {
    protected static $name = 'This is a Vehicle';
    public static function what_vehicle() {
        echo get_called_class() . "\n";
        echo self::$name;
    }
}
class Sedan extends Vehicle {
    protected static $name = 'This is a Sedan';
}
Sedan::what_vehicle();

輸出:

Sedan
This is a Vehicle

解析:`get_called_class()` 正確返回 `Sedan`,但 `self::$name` 綁定在 `Vehicle` 定義處,故輸出父類屬性。

案例 2:使用 static:: (LSB)

<?php
class Vehicle {
    protected static $name = 'This is a Vehicle';
    public static function what_vehicle() {
        echo get_called_class() . "\n";
        echo static::$name;  // 關鍵改動
    }
}
class Sedan extends Vehicle {
    protected static $name = 'This is a Sedan';
}
Sedan::what_vehicle();

輸出:

Sedan
This is a Sedan

解析:`static::$name` 在運行時解析為實際調用類 `Sedan`,實現了靜態屬性的「多態」。

四、進階陷阱與最佳實踐

1. 靜態屬性繼承的「共享陷阱」

<?php
class A { public static $x = 1; }
class B extends A { }
class C extends A { }

B::$x = 2;
echo A::$x; // 2  —— 父子類共享同一記憶體槽位!
echo C::$x; // 2

對策:若需子類獨立狀態,必須在子類重新聲明該屬性(如上文 `$name` 例子),或改用實例屬性 + 單例模式。

2. 靜態方法調用非靜態成員的致命錯誤

<?php
class Foo {
    public $bar = 'baz';
    public static function test() {
        echo $this->bar; // Fatal Error: Using $this when not in object context
    }
}

3. 晚期靜態綁定在單例模式中的應用

<?php
abstract class Singleton {
    private static $instances = [];
    final protected function __construct() {}
    final protected function __clone() {}
    final public static function getInstance() {
        $cls = get_called_class(); // 關鍵:獲取實際調用的子類名
        if (!isset(self::$instances[$cls])) {
            self::$instances[$cls] = new $cls();
        }
        return self::$instances[$cls];
    }
}

class Config extends Singleton { }
class Logger extends Singleton { }

// Config::getInstance() 與 Logger::getInstance() 互不干擾,各自維護單例

五、總結對照表

特性self::static:: (LSB)
綁定時機編譯期(定義所在類)運行期(實際調用類)
繼承多態不支援支援
適用場景調用父類確定的靜態成員、常量需要子類覆寫的靜態屬性/方法、單例基類
性能極快(無運行期查找)微乎其微的開銷(查找調用棧)

結語

理解 `static`、`self`、`static::` 的區別,關鍵在於掌握 「定義時綁定」vs「調用時綁定」 的本質差異。現代 PHP 開發中,優先使用 `static::` 實現靜態多態,僅在明確需要鎖定父類實現時使用 `self::`。配合 `get_called_class()`,可構建出極具擴展性的框架級基類(如單例、工廠、註冊表模式)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved