【问题标题】:Prototypal inheritance causes shared references原型继承导致共享引用
【发布时间】:2015-08-08 09:51:40
【问题描述】:

我想了解原型在 Javascript 中如何以及为何有用。在我以为我知道发生了什么之后,我偶然发现原型只是一个对象,不能像我想象的那样被许多对象“共享”。让我用一个例子来详细说明:

var SpritePrototype = {
    img: null,
    pos_x: 0,
    pos_y: 0,

    draw: function(context2d) {
        /*do stuff with the canvas
          using "this" to refer to 
          the object this method is 
          being called on*/
    },
    //Some more member functions...
}

从原型友好的Javascript通常提倡的“对象继承自对象”的概念,我想我可以这样做:

var player = Object.create(SpritePrototype);

但事实证明这种方法是有缺陷的,因为非函数字段将来自 SpritePrototype,因为玩家的原型正是 SpritePrototype。这意味着我无法从该原型创建更多对象,否则非函数字段将全部混淆。

那么 Object.create 的意义何在,更重要的是,实现我想要做的事情的正确方法是什么?也就是说,如何让“玩家”获取字段的副本并从其原型继承功能?

再说一次,我有兴趣按预期的方式做事。我总是可以手动模拟继承或完全跳过它。我的问题的重点是了解原型以及它们如何以及何时有用,尤其是在我的具体案例中。

【问题讨论】:

    标签: javascript inheritance prototype prototypal-inheritance


    【解决方案1】:

    原型上的属性值最初是共享的,但在实例上的该属性被写入(分配)到时停止共享。在那一刻,实例获得了它自己的属性版本。所以说价值是“共享的”并不完全正确。它仅在实例上的属性被分配到的时间点之前共享。

    var SpritePrototype = {
        img: 'img1'
    };
    var sprite1 = Object.create(SpritePrototype);
    var sprite2 = Object.create(SpritePrototype);
    
    sprite1.img = 'img2';            // does NOT affect prototype or sprite2
    console.log(sprite2.img);
    
    < "img1"
    

    img 被引用时,它的值取自原型。但是,当img写入 时,会在实例上创建一个新属性来保存新值,并从那时起使用。

    更改原型上的属性值的唯一方法是显式地这样做:

    SpritePrototype.img = 'img3';
    

    这将为尚未定义自己本地版本的img 的所有实例更改img,方法是分配给它。

    【讨论】:

    • 感谢您的澄清。你说的确实是对的。但我不会因此说我的例子完全不正确。无论如何,这样做的不良副作用是 img 既在 sprite1 中又在原型中,因此该属性最终被创建了两次。 (编辑):或者这是使用原型的预期方式?某种“写时复制”机制?我以前在其他任何地方都没有看到过这个。
    • 这不是“不受欢迎的”副作用;这是一个理想的。您不想在实例之间共享可变属性(除非它是“类级别”,也就是“静态”属性)。另一方面,如果您不需要属性的实例级版本,则无需保留一个。
    • 原型继承依赖于链接。当您基于原型创建对象时,除非您在新对象上显式声明值,否则它的所有方法和属性基本上都充当指向原型对象的指针。如果您想实际复制属性 - 例如设置默认对象状态 - 有不同的方法可以做到这一点。
    • @jedd.ahyoung 实际上,这是不正确的,正如一些实验所表明的那样,包括我在答案中展示的示例。对sprite1.img 的赋值遵循指向原型的“指针”并在原型上设置img 的值;它在实例上设置img的值。试试看。
    • @torazaburo 我不是这个意思。我的意思是如果您实际上没有进行分配时的行为-这就是我所说的“除非您在新对象上明确声明值”的意思。在赋值之前,sprite.img 将引用原型上的属性。
    【解决方案2】:

    您的问题是,您已经根据面向对象的设计定义了“静态”成员。原型部分中的所有内容都在对象之间共享。函数实际上并没有被复制到新创建的对象中,而是用正确的“this”调用。

    你应该在构造函数中初始化你的变量,像这样

    function Sprite(...) {
        this.img = null;
        ...
    }
    

    那么对于子类型你应该使用 Object.create 创建一个原型来继承它的方法,所以

    Player.prototype = Object.create(Sprite.prototype);
    

    最后,你可以调用父构造函数来初始化变量

    function Player(...) {
        Sprite.call(this, ...);
    }
    

    PS。构造函数应该在 Player.prototype 分配之前。

    PPS。欲了解更多信息,请参阅https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create

    【讨论】:

    • 好的,我明白了。困扰我的是为什么人们一直提倡使用 Object.create 作为构造函数的替代品。我已经阅读了许多关于该主题的文章,并且似乎都认为构造函数+新语法应该被忽略并由 Object.create 单独替换。在这里,您似乎将两者混合在一起...
    • 没有得到这个答案。首先,OP 没有询问创建子类型。其次,他大概知道如何在构造函数上使用new;他问的是一个alternative 模式,即如何使用Object.create 创建对象。最后,他不担心函数/方法;他对非方法属性及其行为方式感兴趣。
    • @Setzer22 那些主张完全避免new 而使用Object.create 的人仍然需要一个初始化步骤,如果有任何(每个实例)对象属性,例如Object.create(Foo); Foo.init()。 (一些库创建了另一种方法来执行此操作,例如 Foo.new() 会在内部调用 Object.create()。)我个人认为仅使用 new 工作正常,我不购买反对它的论点......你仍然可以无论哪种方式,都以原型方式思考。
    • 至于继承,那是另外一回事...使用Object.create 有几个明显的优势,特别是在扩展原型时,例如SubType.prototype = Object.create(ParentType.prototype) 而不是 SubType.prototype = new ParentType()。主要优点是:(1)即使ParentType 具有必需的构造函数参数,它也可以工作,(2)如果您从子构造函数调用父构造函数,它有助于快速检测错误,因为所有父属性将是未定义的,直到它被调用(而如果你使用了new,它们已经被定义了)。
    • 和 (3)(可以说是最重要的):如果 ParentType 构造函数执行以下操作:this.foo = [],那么通过执行 Object.create(ParentType.prototype),您可以避免拥有 foo 的单个副本无意间在SubType 的所有实例之间共享。
    猜你喜欢
    • 2013-03-05
    • 1970-01-01
    • 2013-06-20
    • 2015-07-07
    • 1970-01-01
    • 1970-01-01
    • 2013-05-20
    • 2010-09-28
    相关资源
    最近更新 更多