2

Why can't I assign new properties to non-frozen object, which has frozen prototype:

Working without Object.freeze:

'use strict'
//This object will be prototype of next objects
var defaults = {
  name: 'def name', 
  sections: {
    1: {
      secName: 'def sec name'
    }
  }
};

//So we have an empty object with prototype set to our default object.
var specificObject = Object.create(defaults);


specificObject.sections = {};
console.log(specificObject.hasOwnProperty('sections')); //true

specificObject.sections['1'] = Object.create(defaults.sections['1']);

Above code works as expected, but I want to make sure that defaults won't be accidentally changed. So I want to freeze my defaults object:

'use strict'
//This object will be prototype of next objects
var defaults = {
  name: 'def name', 
  sections: {
    1: {
      secName: 'def sec name'
    }
  }
};

//!!!!!!!!!!!!
Object.freeze(defaults);

//So we have an empty object with prototype set to our default object.
var specificObject = Object.create(defaults);

//TypeError: Cannot assign to read only property 'sections' of #<Object>
specificObject.sections = {};
console.log(specificObject.hasOwnProperty('sections')); //true

specificObject.sections['1'] = Object.create(defaults.sections['1']);

What I don't get is why can't I assign to specificObject if its prototype is frozen?

//EDIT: Notice that specific object is not frozen:

'use strict'
//This object will be prototype of next objects
var protoObj = {a: 1, o: {}};
Object.freeze(protoObj);

console.log(Object.isFrozen(protoObj)); //true

var n = Object.create(protoObj);
console.log(Object.isFrozen(n)); //false

2 Answers2

1

my understanding is that specificObject.sections is pointing to its' prototype which is defaults and it is frozen object. You define a new object {} but you try to assign it to defaults.sections. SpecificObject.sections is pointing exactly there.

If you create new ownProperty on specificObject it will work:

'use strict'
//This object will be prototype of next objects
var defaults = {
  name: 'def name',
  sections: {
    1: {
      secName: 'def sec name'
    }
  }
};

//!!!!!!!!!!!!
Object.freeze(defaults);

//So we have an empty object with prototype set to our default object.
var specificObject = Object.create(defaults);

// this will create new property
Object.defineProperty(specificObject, 'sections',{
    enumerable: true,
    writable: true,
    configurable: true,
    value: {}
});
console.log(specificObject.hasOwnProperty('sections')); //true

specificObject.sections['1'] = Object.create(defaults.sections['1']);

explanation:

if you try to access obj.prop = val then javascript looks into obj's own properties, if not found then it looks into obj's prototype own properties. if found there then it /tries to assign val to/ lookup that property. if not found there then it tries to look into obj's prototype's prototype and so on. If prop is not found in the prototype tree then it creates new own property on obj and assigns val.

Therefore if prop is find on prototype and it is frozen you will get type error. Hope it brings some light.:)

EDIT:

as correctly pointed out by @KubaWyrostek specificObj.sections = {} will create new own property of specific Object, does not assign new value to the prototype's property, but it probably does the lookup for the property to check the writability, in that case it will run into frozen object. I didn't know about this before.

webduvet
  • 4,220
  • 2
  • 28
  • 39
1

What I don't get is why can't I assign to specificObject if its prototype is frozen?

Because property attributes are inherited. Yes, it's odd.

If you freeze an object, it will set the [[writable]] attribute of all data properties to false.

If you assign to an object property, but that property does not exist on the object, it will go and look it up on the prototype - it might be defined as setter there. When this lookup will return and say that there is a property of that name but it is non-writable, your assignment will fail (and throw in strict mode).

What can you do against this?

  • Use Object.defineProperty instead of assignment, it doesn't check the prototype
  • or similarly, use the second parameter of Object.create to create the own property
  • freeze the defaults only after you've assigned to specificObject.
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Is this covered by some specs? It seems that both V8 and Firefox behave this way indeed. – Kuba Wyrostek Apr 13 '15 at 16:01
  • @KubaWyrostek: Yes, [`Put`](http://es5.github.io/#x8.12.5) does call [`CanPut`](http://es5.github.io/#x8.12.4) which does exactly this. – Bergi Apr 13 '15 at 17:02