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.

2
  • The way I've accomplished this kind of thing is to set this.hobbies = [] in the User constructor. Separately define the methods of the object using the prototype. The prototype is for describing what all instances share while the constructor lets you describe what is unique to each instance. Commented Apr 26, 2013 at 15:18
  • Thank you Rick. Yes what you said was mentioned by the book as well. The first example is in fact buggy for demonstration purposes in the book. My question was because during our meetup, we decided to try assignment instead of push and then we got an unexpected behavior. We want to find out how to explain this unexpected behavior to ourselves convincingly. Commented Apr 27, 2013 at 3:56

4 Answers 4

1

That's just the way the language is defined. From the spec:

The production MemberExpression : MemberExpression [ Expression ] is evaluated as follows:

  1. Let baseReference be the result of evaluating MemberExpression.
  2. Let baseValue be GetValue(baseReference).
  3. Let propertyNameReference be the result of evaluating Expression.
  4. Let propertyNameValue be GetValue(propertyNameReference).
  5. Call CheckObjectCoercible(baseValue).
  6. Let propertyNameString be ToString(propertyNameValue).
  7. If the syntactic production that is being evaluated is contained in strict mode code, let strict be true, else let strict be false.
  8. Return a value of type Reference whose base value is baseValue and whose referenced name is propertyNameString, and whose strict mode flag is strict.

That Ecma moon language does not include any mention of looking for properties on object prototypes. An l-value member expression always refers to a property on the base object directly involved.

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you. Erm, I find terms like baseReference a tad unfathomable. It seems to me that you have the right answer. But I don't get the aha! moment when I read this. Is there a way to rephrase this more beginner-friendly? Thank you once again. :)
I understand; the Ecma specification is really hard to read. When you've got a statement like something.property = value;, the term "baseReference" means whatever "something" is. It may be just the name of a variable that references an object, but it also may be some sort of expression that evaluates to a reference to an object.
1

Using "this" you cannot assign anything to the prototype but you can read from it. So when you do this.hobbies = x; you set the property "hobbies" of the current instance rather than that of the prototype, which then hides a prototype-level property of the same name (i.e., boy.hobbies does not longer return the array from the prototype because there is a direct property with this name).

concat() returns a new array rather than a reference to the existing one and hence, you're hiding the prototype-level property "hobbies".

At the next call, the instance-level array "hobbies" is then overwritten by a new one containing the previous values plus the new one.

2 Comments

So you are saying: a) this at prototype is read-only. b) once you attempt to set the value involving properties of this, this becomes the instance object. EVEN IF we are doing this assignment at the prototype level. Yes?
@kimsia In a way, yes. this is always the instance (even if you define the function on the prototype - if you do obj.foo(), this will be a reference to obj). So by doing this.a = 5; (write) you set the property "a" of the instance. When reading (e.g., alert(this.a);) the interpreter first checks whether the instance has a property "a" and only if that is not the case the prototype is checked if it has such a property.
1

Whenever you set the value of a property of an object, the property is defined on the object itself, no matter if the property exists in the object's prototype chain or not.

This is described in the specification, section 8.7.2:

4. Else if IsPropertyReference(V), then
     (a) If HasPrimitiveBase(V) is false, then let put be the [[Put]] internal method of base, otherwise let put be the special [[Put]] internal method defined below.
     (b) Call the put internal method using base as its this value, and passing GetReferencedName(V) for the property name, W for the value, and IsStrictReference(V) for the Throw flag.

The [[Put]] method is described in section 8.12.5, where the important step is:

6. Else, create a named data property named P on object O as follows
     (a) Let newDesc be the Property Descriptor {[[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}.
     (b) Call the [[DefineOwnProperty]] internal method of O passing P, newDesc, and Throw as arguments.


If you look closer at the specification you will see though that the assignment will only create the property on the object if the inherited property is not an accessor property.

I.e. the following will actually not create an instance property:

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 Comments

You said "Whenever you set the value of a property of an object, the property is defined on the object itself". By that you mean, this.hobbies = whatever is setting the value of a property of an object. Yes? So this.hobbies.push() is NOT setting the value of an object. Yes?
"If you look closer at the specification you will see though that the assignment will only create the property on the object if the inherited property is not an accessor property." Could you rephrase this? I feel this is key. But it is a bit dense for me. It would help me understand better if it was stated simpler.
"So this.hobbies.push() is NOT setting the value of an object". Right, you are not assigning any value to this.hobbies, you are modifying the array itself (in-place). "Could you rephrase this?" Basically, you can define a property such that upon assigning (and accessing), a function is executed. When you have an inherited property like that, instead of creating the property on the actual object, the "setter function" is executed. But since Object.defineProperty is not supported in older IE versions, it is not very common to use so you won't encounter that situation very often.
0

As for me, this is not the best example of Prototype enheritance.

Thats you example, modified by me:

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");

3 Comments

Each time a new User is instantiated, the addHobby function is redefined. Declaring it on the User.prototype ensures that it occurs only once.
This version is definitely better and easier to read, but you do not answer the original question.
I apologize. I am not seeking an answer on how to state the instance attribute and move methods into the Prototype. This was already stated in the book effective javascript that the first example was doing it wrongly. This question was inspired during a javascript meetup group we had in Singapore on Prototype inheritance and I wanted to seek explanation from the good people of SO. Thanks for contributing still. :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.