2

I joined this meetup every week discussing about the book effective javascript: 68 ways.

In Item 36: Store Instance State Only on Instance Objects, we created the following example to explain it.

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

};

We instantiate the following users.

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"]

As you can see, the addHobby function affects hobbies at the prototype level.

Now if I change the entire code to

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"]

We know the reason is because of the assignment. We are looking for a full explanation why this.hobbies = this.hobbies.concat(newArr); assigns to the instance level and not at the prototype level despite in both instances the term this.hobbies is used.

4

4 回答 4

1

这就是语言的定义方式。从规格

产生式 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. 如果正在评估的语法产生包含在严格模式代码中,则让 strict 为真,否则让 strict 为假。
  8. 返回一个 Reference 类型的值,其基值为 baseValue,其引用名称为 propertyNameString,其严格模式标志为 strict。

Ecma moon 语言没有提及在对象原型上查找属性。左值成员表达式始终引用直接涉及的基础对象上的属性。

于 2013-04-26T15:14:48.333 回答
1

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

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

在下一次调用中,实例级数组“hobbies”然后被包含先前值加上新值的新数组覆盖。

于 2013-04-26T15:17:20.380 回答
1

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

这在规范第 8.7.2 节中有描述:

4. else if IsPropertyReference(V),则
     (a)If HasPrimitiveBase(V)为假,则令 put为base[[Put]]的内部方法,否则令 put 为下面定义的特殊内部方法。(b) 使用base作为 this 值      调用 put 内部方法,并传递属性名称、值和Throw标志[[Put]]
GetReferencedName(V)WIsStrictReference(V)

[[Put]]方法在第 8.12.5 节中进行了描述,其中重要的步骤是:

6. 否则,在对象O上创建一个名为P的命名数据属性,如下所示      (a) 令 newDesc 为 Property Descriptor 。      (b)通过PnewDescThrow作为参数调用O的内部方法。
{[[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}
[[DefineOwnProperty]]


如果您仔细查看规范,您会发现如果继承的属性不是访问器属性,则分配只会在对象上创建属性

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

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'}'
于 2013-04-26T15:17:34.187 回答
0

至于我,这不是原型继承的最好例子。

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

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");
于 2013-04-26T15:20:35.357 回答