php – 何时使用self超过$this?

在PHP 5中,使用self$this?有什么区别?

什么时候适合?


简答

使用$this来指代当前对象。用self指当前类。换句话说, $this->member用于非静态成员,self::$member用于静态成员。

完整答案

这里是一个例子正确的使用$thisself用于非静态和静态成员变量:

<?php
class X {
    private $non_static_member = 1;
    private static $static_member = 2;

    function __construct() {
        echo $this->non_static_member . ' '
           . self::$static_member;
    }
}

new X();
?>

这里是一个例子不正确的使用$thisself用于非静态和静态成员变量:

<?php
class X {
    private $non_static_member = 1;
    private static $static_member = 2;

    function __construct() {
        echo self::$non_static_member . ' '
           . $this->static_member;
    }
}

new X();
?>

这里是一个例子多态性$this对成员函数:

<?php
class X {
    function foo() {
        echo 'X::foo()';
    }

    function bar() {
        $this->foo();
    }
}

class Y extends X {
    function foo() {
        echo 'Y::foo()';
    }
}

$x = new Y();
$x->bar();
?>

这是一个通过使用成员函数来抑制多态行为的例子self

<?php
class X {
    function foo() {
        echo 'X::foo()';
    }

    function bar() {
        self::foo();
    }
}

class Y extends X {
    function foo() {
        echo 'Y::foo()';
    }
}

$x = new Y();
$x->bar();
?>

这个想法是$this->foo()调用foo()任何> 的成员函数是当前对象的确切类型。如果该对象是type X,则它>调用X::foo()。如果该对象是type Y,它会调用Y::foo()。但用> self :: foo(),X::foo()总是被调用。


关键字自不只是指“当前类的,至少不会在限制你的静态成员的一种方式。在非静态成员的上下文中,self还提供了绕过当前对象的vtable(请参阅vtable上的wiki)的方法。就像你可以parent::methodName()用来调用一个函数的父母版本一样,所以你可以调用self::methodName()调用当前类的一个方法实现。

class Person {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }

    public function getTitle() {
        return $this->getName()." the person";
    }

    public function sayHello() {
        echo "Hello, I'm ".$this->getTitle()."<br/>";
    }

    public function sayGoodbye() {
        echo "Goodbye from ".self::getTitle()."<br/>";
    }
}

class Geek extends Person {
    public function __construct($name) {
        parent::__construct($name);
    }

    public function getTitle() {
        return $this->getName()." the geek";
    }
}

$geekObj = new Geek("Ludwig");
$geekObj->sayHello();
$geekObj->sayGoodbye();

这将输出:

你好,我是
Ludwig这个人,来自路德维希的怪人再见

sayHello()使用$this指针,所以调用vtable来调用Geek::getTitle()。 sayGoodbye()使用self::getTitle(),所以vtable不被使用,并被Person::getTitle()调用。在这两种情况下,我们正在处理实例化对象的方法,并可以访问$this被调用函数中的指针。


不要使用self::,使用static::

自我的另一个方面是值得一提的。令人讨厌的self::是指在定义时不在执行点的范围。考虑这个简单的类有两种方法:

class Person
{

    public static function status()
    {
        self::getStatus();
    }

    protected static function getStatus()
    {
        echo "Person is alive";
    }

}

如果我们打电话,Person::status()我们会看到“人是活着的”。现在考虑当我们创建一个继承自这个类的类时会发生什么:

class Deceased extends Person
{

    protected static function getStatus()
    {
        echo "Person is deceased";
    }

}

调用Deceased::status()我们期望看到的“人死亡”,但是我们看到的是“人活着”,因为当调用self::getStatus()被定义时,范围包含原始方法定义。

PHP 5.3有一个解决方案。在static::分辨率操作实现“晚静态绑定”,这是说的一个奇特的方式,它绑定到调用的类的范围。将行更改status()为 static::getStatus(),结果就是您所期望的。在旧版本的PHP中,你必须找到一个kludge来做到这一点。

请参阅PHP文档

所以要回答这个问题并不像问…

$this->指的是当前对象(类的一个实例),而static::指的是一个类


为了真正理解我们在谈论selfvs 时所谈论的内容$this,我们需要深入了解概念和实践层面上发生的事情。我真的不觉得任何答案能够做到这一点,所以这是我的尝试。

我们先来谈谈一个和一个对象是什么。

类和对象,概念上

那么,什么?许多人将其定义为对象的蓝图模板。事实上,你可以在这里阅读更多关于PHP中的类。在某种程度上,这就是它的真正原因。我们来看一个班级:

class Person {
    public $name = 'my name';
    public function sayHello() {
        echo "Hello";
    }
}

正如你所看到的那样,这个类有一个属性叫做$name方法(函数)sayHello()

这是非常值得注意的是,重要的是静态结构。这意味着Person一旦定义了这个类,在你看它的任何地方都是一样的。

另一方面,对象就是所谓的类的实例。这意味着我们将采用该课程的“蓝图”,并将其用于制作动态副本。此副本现在特别与其存储的变量绑定在一起。因此,对该实例的任何实例更改都是本地的。

$bob = new Person;
$adam = new Person;
$bob->name = 'Bob';
echo $adam->name; // "my name"

我们使用操作符创建一个类的新实例new

因此,我们说一个类是一个全局结构,一个对象是一个局部结构。不要担心这种有趣的->语法,我们将稍微介绍一下。

我们应该讨论的另外一件事是,我们可以检查一个实例是否是instanceof一个特定的类:$bob instanceof Person如果$bob实例是使用Person或者子类来创建的,那么将返回一个布尔值Person

定义国家

所以让我们来深入了解一个类实际包含的内容。一类包含5类“事物”:

  1. 属性 – 将这些视为每个实例将包含的变量。
    class Foo {
        public $bar = 1;
    }
  2. 静态属性 – 将这些视为在课堂级别共享的变量。这意味着它们从不被每个实例复制。
    class Foo {
        public static $bar = 1;
    }
  3. 方法 – 这些是每个实例将包含的功能(以及对实例的操作)。
    class Foo {
        public function bar() {}
    }
  4. 静态方法 – 这些是在整个类中共享的函数。它们不对实例进行操作,而只对静态属性进行操作。
    class Foo {
        public static function bar() {}
    }
  5. 常量 – 类解析的常量。这里不做更深入的了解,但是为了完整性而添加:
    class Foo {
        const BAR = 1;
    }

所以基本上,我们使用关于静态的 “提示”来存储关于类和对象容器的信息,这些提示标识信息是否被共享(并因此是静态的)或不动态的(因此是动态的)。

状态和方法

在一个方法内部,一个对象的实例由$this变量表示。该对象的当前状态存在,并且改变(改变)任何属性将导致对该实例的更改(但不是其他状态)。

如果静态调用某个方法,则该$this变量未定义。这是因为没有与静态调用关联的实例。

这里有趣的是静态调用。那么让我们来谈谈我们如何访问国家:

进入状态

所以现在我们已经存储了这个状态,我们需要访问它。这能有点棘手(或方式比多一点),所以让我们这个分成两种观点:从一个实例/类外(从普通的函数调用说,还是从全球范围),以及一个实例内部/类(从对象的方法中)。

来自实例/类之外

从实例/类的外部,我们的规则非常简单且可预测。我们有两个操作符,每个操作符都会立即告诉我们是否正在处理实例或类的静态:

  • ->– object-operator – 当我们访问一个实例时总是使用它。
    $bob = new Person;
    echo $bob->name;

    重要的是要注意调用Person->foo没有意义(因为Person是一个类,而不是一个实例)。因此,这是一个解析错误。

  • ::– scope-resolution-operator – 这总是用来访问一个Class静态属性或方法。
    echo Foo::bar()

    另外,我们可以用相同的方式在对象上调用静态方法:

    echo $foo::bar()

    注意到当我们从外面做这件事,对象的实例对方法隐藏是非常重要的。这意味着它与跑步完全相同:bar()

    $class = get_class($foo);
    $class::bar();

因此,$this在静态调用中没有定义。

来自实例/类的内部

事情在这里有点改变。使用相同的操作符,但其含义变得非常模糊。

对象的操作 ->仍然是用来对对象的实例状态的呼叫。

class Foo {
    public $a = 1;
    public function bar() {
        return $this->a;
    }
}

使用object-operator 调用bar()方法$foo(的一个实例Foo$foo->bar()将导致实例的版本$a

这就是我们的期望。

::操作员的含义虽然有变化。它取决于调用当前函数的上下文:

  • 在静态上下文中

    在静态上下文中,使用的任何调用::也将是静态的。我们来看一个例子:

    class Foo {
        public function bar() {
            return Foo::baz();
        }
        public function baz() {
            return isset($this);
        }
    }

    呼叫Foo::bar()将调用baz()静态方法,因此$this不会被填充。值得注意的是,在PHP(5.3+)的最新版本中,这会引发E_STRICT错误,因为我们正在静态调用非静态方法。

  • 在实例上下文中

    另一方面,在实例环境中,使用的调用::取决于调用的接收者(我们调用的方法)。如果方法定义为static,那么它将使用静态调用。如果不是,它将转发实例信息。

    因此,查看上面的代码,调用$foo->bar()将返回true,因为“静态”调用发生在实例上下文中。

合理?没想到。这很混乱。

快捷关键字

因为使用类名将所有东西捆绑在一起非常脏,所以PHP提供了3个基本的“快捷方式”关键字,以便简化范围解析。

  • self – 这是指当前的班级名称。所以和课堂内部self::baz()相同(任何方法)。Foo::baz()Foo
  • parent – 这是指当前班的父母。
  • static – 这是指被调用的类。由于继承,子类可以覆盖方法和静态属性。因此,使用static而不是类名来调用它们可以让我们解决调用的来源,而不是当前的级别。

例子

理解这个最简单的方法是开始看一些例子。让我们选一个班级:

class Person {
    public static $number = 0;
    public $id = 0;
    public function __construct() {
        self::$number++;
        $this->id = self::$number;
    }
    public $name = "";
    public function getName() {
        return $this->name;
    }
    public function getId() {
        return $this->id;
    }
}

class Child extends Person {
    public $age = 0;
    public function __construct($age) {
        $this->age = $age;
        parent::__construct();
    }
    public function getName() {
        return 'child: ' . parent::getName();
    }
}

现在,我们也在看这里的继承。暂时忽略这是一个不好的对象模型,但让我们看看当我们玩这个时会发生什么:

$bob = new Person;
$bob->name = "Bob";
$adam = new Person;
$adam->name = "Adam";
$billy = new Child;
$billy->name = "Billy";
var_dump($bob->getId()); // 1
var_dump($adam->getId()); // 2
var_dump($billy->getId()); // 3

所以ID计数器在两个实例和孩子之间共享(因为我们正在使用self它来访问它,如果我们使用了static,我们可以在子类中覆盖它)。

var_dump($bob->getName()); // Bob
var_dump($adam->getName()); // Adam
var_dump($billy->getName()); // child: Billy

请注意,我们每次都在执行Person::getName() 实例方法。但我们正在使用parent::getName()它来处理其中一个案例(儿童案例)。这就是使这种方法强大的原因。

警告词#1

请注意,调用上下文是决定是否使用实例的原因。因此:

class Foo {
    public function isFoo() {
        return $this instanceof Foo;
    }
}

并不总是如此。

class Bar {
    public function doSomething() {
        return Foo::isFoo();
    }
}
$b = new Bar;
var_dump($b->doSomething()); // bool(false)

现在这里真的很奇怪。我们正在调用一个不同的类,但$this传递给该Foo::isFoo()方法的是该实例$bar

这可能会导致各种bug和概念性WTF-ery。所以我强烈建议避免了::运营商从实例方法中的任何东西,除了这三个虚拟的“捷径”关键字(staticself,和parent)。

警告词#2

请注意,每个人都共享静态方法和属性。这使得它们基本上是全局变量。与全局相同的问题。所以我会真的犹豫是否将信息存储在静态方法/属性中,除非你对它是真正的全球化感到满意。

警告词#3

一般来说,你会希望使用所谓的Late-Static-Binding来static代替self。但请注意,它们并不是一回事,所以说“总是使用static而不是self真的是短视的,相反,停下来想一想你想做的调用,然后想想如果你希望子类能够覆盖静态解析呼叫。

TL / DR

太糟糕了,回去看看吧。这可能太长,但这是很长的,因为这是一个复杂的话题

TL / DR#2

好的。简而言之,self用于引用类中的当前类名,其中as $this指向当前对象实例。请注意,这self是一个复制/粘贴快捷方式。你可以安全地将它替换为你的类名,并且它可以正常工作。但是,这$this是一个不能事先确定的动态变量(甚至可能不是你的班级)。

TL / DR#3

如果使用对象操作符(->),那么你总是知道你正在处理一个实例。如果使用scope-resolution-operator(::),则需要更多关于上下文的信息(我们是否已经在对象上下文中?我们是否在对象之外?等等)。

添加评论

友情链接:蝴蝶教程