0

Sorry if my title is confusing, English is not my native language.

This is my code:

function x(name){
  this.name = name
}

x.prototype.score = 1;
x.prototype.arr = [1, 2, 3]
x.prototype.obj = { val1: 1}

let u1 = new x('Jason')

console.log(x.prototype.score) // 1 
console.log(++u1.score) // 2
console.log(x.prototype.score) // Still 

/* ** */ console.log('break for array testing')/* ** */

console.log(x.prototype.arr[0]) // 1 
console.log(++u1.arr[0]) // 2
console.log(x.prototype.arr[0]) // Value changed to 2

/* ** */console.log('break for object testing')/* ** */

console.log(x.prototype.obj['val1']) // 1 
console.log(++u1.obj['val1']) // 2
console.log(x.prototype.arr[0]) // Value changed to 2

I'm confused if I do the following line in the code above:

++u1.score

A new property with the name score is created in the u1 object (and the value of x.prototype.score is not affected,) and as per my guess this is what's happening under the hood:

++u1.score
u1.score = u1.score + 1

Looks for score property in the u1 for increment, don't find it, then create one and assign it to the value of u1.score + 1! Since u1.score doesn't exist, it looks into the u1.__proto__ property, which leads to x.prototype, and finds the score there, and do assignment based on what the value is there which happens to be 1.

Finally, u1.score = 1

Everything makes sense!

This has created u1.score without affecting x.prototype.score!

However, now if we do with Object or Array, neither of them is created on my u1 object + the value inside the prototype is affected.

Now my question is: Is the due to the fact that we cant do this:

u1.randomArr[0] = true

Or Array and Object are reference types?

If someone could explain this, that'd be greatly appreciated!

Thanks!

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • "*my guess this is what's happening under the hood: `u1.score = u1.score + 1`*" - yes, and that creates a new property on `u1`, exactly. – Bergi Jan 10 '21 at 16:06
  • And yes, objects (including array objects) are reference types. `u1.randomArr[0] = true` mutates the object that is referenced from `u1.randomArr` (and also from `x.prototype.arr`), it doesn't create a new property. – Bergi Jan 10 '21 at 16:07

1 Answers1

0

It is indeed because Array and Object are accessed by reference. This can in fact lead to all kinds of unpredictable bugs when not considered:

function getExtendedWith(array, value) {
    array.push(value);
    return array;
}

const arrayWithoutFour = [1, 2, 3];
const arrayWithFour = getExtendedWith(array, 4);

console.log(arrayWithoutFour); // [1, 2, 3, 4]
console.log(arrayWithFour); // [1, 2, 3, 4]

That is why in functional programming all functions operating on data are expected to make a clone of their input data before altering it:

function getExtendedWith(array, value) {
    return [...array, value]; // Using ES6 destructuring
}

const array = [1, 2, 3];
const arrayWithFour = getExtendedWith(array, 4);

console.log(array); // [1, 2, 3]
console.log(arrayWithFour); // [1, 2, 3, 4]

This way there is no unexpected mutation of the input data leading to problems later on.
As for your example, instance.value++ does indeed lead to instance.value = instance.value + 1, and that's why it works: object had no own property value before, but it is freshly created without touching the original property of x.prototype.

It boils down to the fact that prototype does not magically create clones of all its properties when a new instance is created: it acts more like a lookup table for values that are not present on the instance itself. So if instance.value is not defined, it will instead resolve to instance.__proto__.value. This is illustrated in the following example:

let instance = {};
console.log(instance.value); // undefined

instance.__proto__ = { value: 5 };
console.log(instance.value); // 5

instance.value = 10;
console.log(instance.value); // 10

delete instance.value;
console.log(instance.value); // 5

And for that reason you should give classes their own copies of arrays and objects in the constructor, to avoid contaminating each other's state.

function x(name){
  this.name = name
  this.score = 1;
  this.arr = [1, 2, 3]
  this.obj = { val1: 1}
}
soimon
  • 2,400
  • 1
  • 15
  • 16