【问题标题】:Javascript object members that are prototyped as arrays become shared by all class instances原型化为数组的 Javascript 对象成员被所有类实例共享
【发布时间】:2011-05-24 10:38:50
【问题描述】:

以前有没有人注意到这种行为?这真的让我大吃一惊……我本来希望原型数组对每个类实例都是私有的,而不是在所有类实例之间共享。

有人可以验证这是正确的行为,或许可以更详细地解释这种行为吗?

注意注释代码以及它如何影响脚本的行为。

<html>
<head>

<script type="text/javascript">

function print_r( title, object ) {

    var output = '';
    for( var key in object ) {

        output += key + ": " + object[ key ] + "\n";

    }

    output = title + "\n\n" + output;

    alert( output );

}

function Sandwich() {

    // Uncomment this to fix the problem
    //this.ingredients = [];

}

Sandwich.prototype = {

    "ingredients" : [],
    "addIngredients" : function( ingArray ) {

        for( var key in ingArray ) {

            this.addIngredient( ingArray[ key ] );

        }

    },
    "addIngredient" : function( thing ) {

        this.ingredients.push( thing );

    }

}

var cheeseburger = new Sandwich();
cheeseburger.addIngredients( [ "burger", "cheese" ] );

var blt = new Sandwich();
blt.addIngredients( [ "bacon", "lettuce", "tomato" ] );

var spicy_chicken_sandwich = new Sandwich();
spicy_chicken_sandwich.addIngredients( [ "spicy chicken pattie", "lettuce", "tomato", "honey dijon mayo", "love" ] );

var onLoad = function() {

    print_r( "Cheeseburger contains:", cheeseburger.ingredients );

};

</script>

</head>
<body onload="onLoad();">
</body>
</html>

非常感谢。

【问题讨论】:

    标签: javascript arrays oop class prototype


    【解决方案1】:

    对象的原型只是一个对象。原型属性在从该对象继承的所有对象之间共享。如果您创建“类”的新实例(JS 中不存在类),即从原型继承的对象,则不会复制属性。

    这只会影响您如何使用这些继承的属性:

    function Foo() {}
    
    Foo.prototype = {
        array: [],
        func: function() {}
    }
    
    a = new Foo();
    b = new Foo();
    
    a.array.push('bar');
    console.log(b.array); // prints ["bar"]
    
    b.func.bar = 'baz';
    console.log(a.func.bar); // prints baz
    

    在所有这些情况下,您总是使用同一个对象。

    但是,如果您为对象的属性赋值,则该属性将在对象本身而不是其原型上设置/创建,因此不会共享:

    console.log(a.hasOwnProperty('array')); // prints false
    console.log(a.array); // prints ["bar"]
    a.array = ['foo'];
    console.log(a.hasOwnProperty('array')); // prints true
    console.log(a.array); // prints ["foo"]
    console.log(b.array); // prints ["bar"]
    

    如果你想为每个实例创建自己的数组,你必须在构造函数中定义它:

    function Foo() {
        this.array = [];
    }
    

    因为这里this指的是调用new Foo()时生成的new对象。

    经验法则是:Instance-特定数据应该分配给constructor内的instanceshared 数据(如方法)应分配给 原型


    您可能想阅读Details of the object model,它描述了基于类和基于原型的语言之间的差异以及对象的实际工作方式。

    更新:

    您可以通过Object.getPrototypeOf(obj) 访问对象的原型(在非常旧的浏览器中可能无法使用),而Object.getPrototypeOf(a) === Object.getPrototypeOf(b) 为您提供true。它是同一个对象,也称为Foo.prototype

    【讨论】:

    • 嗯...是的,这是有道理的。我错误地期望原型能够像类定义一样工作。令人困惑的是,如果 Foo.prototype.intValue = 5,而你说 b.intValue = 4,a.intValue 仍然 = 5。所以它实际上只适用于类的对象/数组成员。
    • 再一次,如果你说 b.array = ['whatever'],那么 a.array 也不会改变。因此,使用“=”运算符的显式赋值会覆盖实例化对象的原型属性。
    • @Dan:不,我还在我的代码中表明a.array = ['foo']; 不会改变原型。但是在您使用整数的情况以及在我使用数组的情况下,您正在为属性分配一个全新的值。如果您这样做,该属性将设置在实际对象上。但是,如果您执行a.array.push(1),您不是设置属性,您只是调用包含在a.array 中的数组中的方法。您没有更改参考。
    • 是的,抱歉,这就是我所说的“覆盖实例化对象上的原型属性”时的意思——在这种情况下,您会影响实际对象,并且只会影响该实例。另外,感谢您提供指向对象模型详细信息的链接。我需要用细齿梳子仔细阅读。
    • @Dan:你的第二条评论是绝对正确的,我只是表达得不好;)
    【解决方案2】:

    行为是正确的。 [] 在运行时被转换为 new Array(),但只创建了一个这样的数组。

    换句话说,Obj.prototype = {...} 的执行就像任何其他分配一样。

    【讨论】:

      【解决方案3】:

      当您执行var exp1 = new C() 时,JavaScript 会设置exp1.[[Prototype]] = C.prototype。然后,当您访问实例的属性时,JavaScript 首先检查它们是否直接存在于该对象上,如果不存在,它会查找 [[Prototype]]。这意味着您在原型中定义的所有内容都被所有实例有效地共享,您甚至可以在以后更改原型的某些部分并使更改出现在所有现有实例中。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-09-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-02-17
        • 1970-01-01
        相关资源
        最近更新 更多