为什么赋值影响实例而不影响原型属性?

时间:2013-04-26 15:06:20

标签: javascript prototype

我每周都参加这次聚会,讨论有效的javascript:68种方式。

在第36项:仅在实例对象上存储实例状态,我们创建了以下示例来解释它。

function User() {}
User.prototype = {
    hobbies: [], // should be instance state!
    addHobby: function (x) {
        this.hobbies.push(x);
    }

};

我们实例化以下用户。

boy = new User();
// User {hobbies: Array[0], addHobby: function}
girl = new User();
// User {hobbies: Array[0], addHobby: function}
boy.addHobby("swimming"); 
girl.addHobby("running");
// undefined
boy.hobbies
// ["swimming", "running"]
girl.hobbies
// ["swimming", "running"]

如您所见,addHobby功能会影响原型级别的爱好。

现在,如果我将整个代码更改为

function User() {}
    User.prototype = {
        hobbies: [], // should be instance state!
        addHobby: function (x) {
            newArr = new Array(x);
            this.hobbies = this.hobbies.concat(newArr);
        }

    };

boy = new User();
girl = new User();

boy.addHobby("swimming"); 
girl.addHobby("running");
boy.hobbies
//["swimming"]
girl.hobbies
//["running"]

我们知道原因是因为作业。我们正在寻找完整解释为什么this.hobbies = this.hobbies.concat(newArr);分配给实例级别而不是原型级别,尽管在这两种情况下都使用了术语this.hobbies

4 个答案:

答案 0 :(得分:1)

这就是定义语言的方式。 From the spec

  

生成MemberExpression:MemberExpression [Expression]的计算方法如下:

  1. 让baseReference成为评估MemberExpression的结果。
  2. 让baseValue为GetValue(baseReference)。
  3. 让propertyNameReference成为评估Expression的结果。
  4. 让propertyNameValue为GetValue(propertyNameReference)。
  5. 调用CheckObjectCoercible(baseValue)。
  6. 让propertyNameString为ToString(propertyNameValue)。
  7. 如果正在评估的语法生成包含在严格模式代码中,则let strict为true,否则let strict为false。
  8. 返回类型为Reference的值,其基值为baseValue,其引用名称为propertyNameString,其严格模式标志为strict。
  9. Ecma moon语言中没有提到在对象原型上寻找属性的任何提及。 l值成员表达式始终是指直接涉及的基础对象上的属性。

答案 1 :(得分:1)

使用“this”你不能为原型分配任何东西,但你可以从中读取。因此,当您执行this.hobbies = x;时,您设置当前实例的属性“hobbies”而不是原型的属性,然后隐藏原型级别的同名属性(即,boy.hobbies不再从原型返回数组,因为有一个带有此名称的直接属性。)

concat()返回一个新数组,而不是对现有数组的引用,因此,您将隐藏原型级属性“hobbies”。

在下一次调用时,实例级数组“hobbies”将被包含先前值和新值的新数据覆盖。

答案 2 :(得分:1)

无论何时设置对象属性的值,都会在对象本身上定义属性,无论该属性是否存在于对象的原型链中。

规范section 8.7.2中描述了这一点:

  

4。如果IsPropertyReference(V),则为   (a)If HasPrimitiveBase(V)为false,然后将其作为 base [[Put]]内部方法,否则请将其放在下面定义的特殊[[Put]]内部方法中。登记/>   (b)使用 base 作为此值调用put内部方法,并传递GetReferencedName(V)作为属性名称,W作为值,IsStrictReference(V)表示投掷标志。

section 8.12.5中描述了[[Put]]方法,其中重要的步骤是:

  

6。否则,在对象 O 上创建一个名为 P 的命名数据属性,如下所示
  (a)让newDesc成为属性描述符   {[[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}
  (b)调用 O [[DefineOwnProperty]]内部方法,将 P newDesc 投掷作为参数。


如果您仔细查看规范,您会看到,如果继承的属性不是访问者属性,则赋值只会在对象上创建属性。

即。以下实际上不会创建实例属性:

var name = 'foo';

function User() {} 
Object.defineProperty(User.prototype, 'name', {
    'get': function() { return name;},
    'set': function(val) { name = val;}
});

var u1 = new Users();
var u2 = new Users();
u1.name = 'bar';
console.log(u2.name); // shows 'bar'
console.log(u1) // shows 'User {}' instead of 'User {name: 'bar'}'

答案 3 :(得分:0)

至于我,这不是Prototype enheritance的最好例子。

这是你的例子,由我修改:

function User() {
     this.hobbies = [];
};
    User.prototype = {

        addHobby: function (x) {
            this.hobbies.push(x);
        }

    };

boy = new User();
girl = new User();

boy.addHobby("swimming"); 
girl.addHobby("running");