本文档从「static 属性 vs 普通属性」「static 方法 vs 普通方法」讲起,最后重点拆解self:: 与 static::(后期静态绑定)问题。全程配可运行代码。
理解 static 的一切,都建立在这一句话上:
普通成员属于「对象(实例)」,static 成员属于「类」本身。
一个类可以 new 出无数个对象,每个对象有自己的普通属性。
但「类」只有一个,所以 static 属性/方法全类共享一份,不需要 new 就能访问。
class Dog {
public $name; // 普通属性:每只狗各自的名字
public static $count = 0; // static 属性:全类共享的「狗的总数」
public function __construct($name) {
$this->name = $name; // 用 $this 访问普通属性
self::$count++; // 用 self:: 访问 static 属性
}
}
$d1 = new Dog('旺财');
$d2 = new Dog('来福');
echo $d1->name; // 旺财
echo $d2->name; // 来福
echo Dog::$count; // 2(共享,不是每只狗各一份)| 特性 | static 属性 | 普通属性 |
|---|---|---|
| 归属 | 类本身 | 每个对象实例 |
| 内存份数 | 全类 1 份,共享 | 每个对象各 1 份 |
| 访问语法 | 类名::$属性 / self::$属性 | $对象->属性 |
| 是否需要 new | 不需要 | 需要 |
| 在方法内访问 | self::$prop 或 static::$prop | $this->prop |
| 生命周期 | 从类加载到脚本结束 | 从对象创建到销毁 |
共享性是关键差异:
class Counter {
public static $s = 0; // 共享
public $n = 0; // 独立
}
$a = new Counter();
$b = new Counter();
$a->n = 10;
$a::$s = 10; // 注意:虽然用 $a 调,改的是「类」的 static 属性
echo $a->n; // 10
echo $b->n; // 0 (各是各的)
echo Counter::$s; // 10 (共享,被改了)
echo $b::$s; // 10 (通过 b 看也是 10)注意 $a::$s = 10; 这种「用对象访问 static 属性」语法虽然合法,但极不推荐,因为会让人误以为是改 $a 自己的东西。请始终用 类名::$s 或 self::$s。
| 特性 | static 方法 | 普通方法 |
|---|---|---|
| 调用语法 | 类名::方法() / self::方法() | $对象->方法() |
| 是否需要 new | 不需要 | 需要对象 |
| 能否用 $this | 不能 | 能 |
| 能否直接访问普通属性 | 不能(需先 new) | 能 |
| 能否访问 static 成员 | 能 | 能 |
| 多态性 | 依赖后期静态绑定(见第 5 节) | 天然基于对象 |
class MathTool {
public static function add($a, $b) {
return $a + $b;
}
public $base = 100;
public function addBase($x) {
return $this->base + $x; // 普通方法可用 $this
}
}
echo MathTool::add(1, 2); // 3,无需 new
$m = new MathTool();
echo $m->addBase(5); // 105不能直接访问。 因为 static 方法里没有 $this(它不属于任何对象)。
class A {
public $prop = 1;
public static $sprop = 2;
public static function f() {
echo self::$sprop; // ✅ OK,static 属性
// echo $this->prop; // ❌ 错误:static 方法里没有 $this
// echo $this->prop; // ❌ Fatal error
}
}如果 static 方法确实要用到普通属性,只能自己 new 一个对象:
public static function f() {
$obj = new self();
echo $obj->prop; // ✅ 先实例化,再访问普通属性
}反过来:普通方法里可以访问 static 成员:
class A {
public static $sprop = 2;
public function f() {
echo self::$sprop; // ✅ 普通方法里访问 static 成员没问题
}
}一句话记:普通方法「通吃」静态和普通;static 方法「只碰静态」,碰普通必须先 new。
这是你之前不理解的代码,我们彻底讲透。
class A {
public static $val = 'A的值';
public static function test() {
echo self::$val, PHP_EOL; // self
echo static::$val, PHP_EOL; // static
}
}
class B extends A {
public static $val = 'B的值'; // 子类重新声明了同名 static 属性
}
A::test(); // 输出:A的值 / A的值
B::test(); // 输出:A的值 / B的值重点看 B::test():
self::$val → A的值
static::$val → B的值
同样的方法代码,为什么两个结果不同?这正是「早绑定」和「晚绑定」的区别。
self 在编译时就解析为「写这段代码时所在的类」,也就是 A。
所以 test() 方法里写的 self::$val,在编译那一刻就被钉死成 A::$val。无论你后面用 A::test() 还是 B::test() 调用,它永远是 A 的 $val。
记忆:self = 「我(写代码的这个类)自己」——写死在 A 里,就是 A,不会因为子类调用而改变。
static:: 不在编译时确定,而是在运行时根据「你实际调用的是哪个类」来决定,这个机制叫 后期静态绑定(Late Static Binding,LSB)。
A::test() → 运行时调用类是 A → static::$val 取 A::$val → A的值
B::test() → 运行时调用类是 B → static::$val 取 B::$val → B的值
记忆:static = 「调用我的那个类」——跟着实际调用者走。
调用 B::test() │ ▼ 进入 A 里定义的 test() 方法 │ ├── self::$val ── 编译期已定死为 A ──▶ A的值 │ └── static::$val ── 运行时看「谁调的」是 B ──▶ B的值
class A {
public static function who() { echo 'A', PHP_EOL; }
public static function test() { static::who(); } // 晚绑定
}
class B extends A {
public static function who() { echo 'B', PHP_EOL; }
}
B::test(); // 输出 B如果 test() 里写的是 self::who();,就会永远输出 A,因为 self 死绑成 A。
static::$val 能拿到 B 的值,前提是 B 自己重新声明了 $val。如果 B 没声明,B::$val 会继承 A 的值,那时 self::$val 和 static::$val 结果就一样了。
class B extends A {} // B 没重新声明 $val
B::test(); // 输出:A的值 / A的值(因为 B 继承了 A 的 $val)| 符号 | 绑定时机 | 指向 | B 调用 A 里的方法时指向 |
|---|---|---|---|
| self:: | 编译期(早) | 定义该方法的类 | A |
| static:: | 运行期(晚) | 实际调用的类 | B |
| parent:: | 编译期 | 父类 | A 的父类 |
class Grand {
public static $v = 'Grand';
}
class ParentC extends Grand {
public static $v = 'ParentC';
public static function show() {
echo 'self: ', self::$v, PHP_EOL; // 定义方法的类 ParentC
echo 'static: ', static::$v, PHP_EOL; // 实际调用的类
echo 'parent: ', parent::$v, PHP_EOL; // 父类 Grand
}
}
class Child extends ParentC {
public static $v = 'Child';
}
Child::show();
// self: ParentC
// static: Child
// parent: Grand为什么 PHP 要设计 static::?因为 self:: 写死了类名,父类的通用方法无法「感知」到被哪个子类调用。举两个经典场景。
class Model {
public static function create() {
return new static(); // 关键:用 static,返回「调用者」的实例
}
}
class User extends Model {}
$u = User::create();
var_dump($u instanceof User); // true ✅
// 如果写成 new self(),就会返回 Model,而不是 User ❌class Singleton {
private static $instance = null;
protected function __construct() {}
private function __clone() {}
public static function getInstance() {
if (static::$instance === null) { // 用 static,让每个子类有独立实例
static::$instance = new static();
}
return static::$instance;
}
}
class Db extends Singleton {}
class Cache extends Singleton {}
var_dump(Db::getInstance() === Db::getInstance()); // true
var_dump(Db::getInstance() === Cache::getInstance()); // false(各是各的)用 self::$instance 会导致所有子类共享同一个实例,用 static::$instance 才能让每个子类各自独立。
非 static 方法里也常用:
class A {
public function f() {
echo static::class; // 运行时实际对象类名
}
}
class B extends A {}
(new B())->f(); // 输出 B坑 1:外部不能用 static::
static::$val; // ❌ 语法错误,只能在类内部使用
坑 2:用 -> 访问 static 属性会报错
class A { public static $v = 1; }
$a = new A();
echo $a->v; // ❌ Undefined property(static 不能通过 -> 访问)
echo $a::$v; // ⚠️ 合法但不推荐(通过对象调 static)
echo A::$v; // ✅ 正确坑 3:static 方法里用 $this
class A {
public static function f() {
// return $this; // ❌ Fatal error: Using $this when not in object context
}
}坑 4:static:: 要生效,子类必须重新声明同名属性/方法
class A { public static $v = 'A'; public static function f(){ echo static::$v; } }
class B extends A {} // 没重新声明
B::f(); // 输出 A(继承来的)坑 5:self:: 不能用于「多态期望」
任何你希望「子类调用时表现得像子类」的地方,都不能用 self::,必须用 static::。
坑 6:static 是全局状态,注意测试与并发
static 属性是脚本级全局共享状态,单元测试之间可能相互污染,需谨慎使用。
| 你的需求 | 选什么 |
|---|---|
| 工具函数(无状态,如 Str::random()) | static 方法 |
| 全类共享计数器/缓存/配置 | static 属性 |
| 单例、工厂(要返回子类实例) | static 方法 + new static() |
| 每个对象有独立数据(用户、订单…) | 普通属性/方法 |
| 需要 $this、依赖注入、易测试 | 普通方法 |
| 父类方法要「认子类」 | static:: / 后期静态绑定 |
| 固定用当前类的成员 | self:: |
属性/方法层面:
static 属于「类」,共享一份,无需 new;普通属于「对象」,各自一份,需 new。
static 方法里没有 $this,不能直接碰普通属性;普通方法里两种都能碰。
继承与绑定层面(你的疑问):
self:: = 「定义我的那个类」 → 编译期早绑定,写死在 A 就永远是 A。
static:: = 「调用我的那个类」 → 运行期晚绑定(后期静态绑定),B 调用就用 B。
parent:: = 父类,早绑定。
一句话:
想让父类的通用方法在子类调用时「认子类」,用 static::;想固定当前类的成员,用 self::。两者都只作用于 static 成员(self:: / static:: 也可用于常量,但属性/方法要分清 static 与普通)。
class Animal {
public static $count = 0;
public static function create() {
static::$count++;
return new static();
}
public static function report() {
echo static::class, ' 累计:', static::$count, PHP_EOL;
}
}
class Cat extends Animal { public static $count = 0; }
class Dog extends Animal { public static $count = 0; }
Cat::create(); Cat::create();
Dog::create();
Cat::report(); // Cat 累计:2
Dog::report(); // Dog 累计:1
Animal::report(); // Animal 累计:0(父类自己的 $count 没被动过)
登录后参与评论
论坛评论仅限会员,登录后即可发表评论与回复