Weird behaviour with 'use strict' and read only properties
Asked Answered
L

2

7

On the MDN strict mode reference page it says

Any assignment that silently fails in normal code (assignment to a non-writable property, assignment to a getter-only property, assignment to a new property on a non-extensible object) will throw in strict mode

So, using their example, doing something like the following throws a TypeError

"use strict";
var obj1 = {};
Object.defineProperty(obj1, "x", { value: 42, writable: false });
obj1.x = 9; // throws a TypeError

However I ran into an example where it seems 'use strict' is being a little overzealous about this rule. Here is my setup

definelol.js

Object.defineProperty(Object.prototype, 'lol', {
    value: 'wat'
})

setlol.js

'use strict';

console.log('here 0');

var sugar = { lol: '123' }

console.log('here 1');

var verbose = {};
verbose.lol = '123';

console.log('here 2');

console.log('sugar.lol:', sugar.lol);
console.log('verbose.lol:', verbose.lol);
console.log('Object.prototype.lol:', Object.prototype.lol);

app.js

require('./definelol.js');
require('./setlol.js');

running node app.js gives

here 0
here 1

/pathto/setlol.js:10
verbose.lol = '123';
            ^
TypeError: Cannot assign to read only property 'lol' of #<Object>
    at Object.<anonymous> (/pathto/setlol.js:10:13)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Module.require (module.js:364:17)
    at require (module.js:380:17)
    at Object.<anonymous> (/pathto/app.js:2:1)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)

There are a couple of interesting things that are interesting about this output. First is that we are not trying to set the lol property on Object.prototype we are trying to set the lol property of verbose. To prove this I changed definelol.js to be

Object.defineProperty(Object.prototype, 'lol', {
    writable: true,
    value: 'wat'
})

Now, running node app.js gives

here 0
here 1
here 2
sugar.lol: 123
verbose.lol: 123
Object.prototype.lol: wat

The second thing that was interesting was that the original program failed on verbose.lol = '123' but was perfectly happy creating sugar and setting its lol to 123. I don't understand this because it seems that the way we created sugar should simply be syntactic sugar for the way we created verbose

Lilybel answered 9/10, 2014 at 22:27 Comment(1)
M
3

See section 11.13.1 of the spec:

When an assignment occurs within strict mode code, its LeftHandSide must not evaluate to an unresolvable reference. If it does a ReferenceError exception is thrown upon assignment. The LeftHandSide also may not be a reference to a data property with the attribute value {[[Writable]]:false}, to an accessor property with the attribute value {[[Set]]:undefined}, nor to a non-existent property of an object whose [[Extensible]] internal property has the value false. In these cases a TypeError exception is thrown.

In your sample code, the left-hand side of an = expression is, in fact, a reference to a data property with the "writable" flag set to false.

Now I am somewhat sympathetic to the notion that it shouldn't apply to inherited properties, but I can see that there may be a strong counter-argument. That the object literal allows the property to be created as an "own" property of the new "sugar" object certainly seems odd.

edit — for clarity, the issue here is that assignment to an object property is always about assigning to an "own" property of the object. Assignments don't affect properties up the inheritance chain. Thus, the question posed involves the following apparent contradiction: if a property from the Object prototype with the "writable" flag set to false prevents assignment to that property name on existing objects, why is it that assignment to that property succeeds in the course of evaluation of an object literal?

There could be a nice rationale for this, or it could be a bug. Both V8 and whatever the Firefox runtime is currently called (something-monkey I guess) act the same way.

Micrometry answered 9/10, 2014 at 22:36 Comment(3)
Do you have any thoughts about why the sugar.lol is treated differently?Lilybel
@Tom Well that's what makes me think that there's something fishy going on. However, I don't know which behavior to call "wrong" :)Micrometry
@Tom it could be that the "sugar" thing works because of the way that object literal expressions are defined. When properties are defined that way, the spec explicitly says that [[DefineOwnProperty]] is called.Micrometry
S
0

You defined a property on every objects' prototypes, so all of them have a 'lol' property in their prototype.

Sugar is defined with his own 'lol', so that has nothing to do with the 'lol' that is in its prototype. That one is hidden.

Verbose is defined as an empty object, as such, it will have a 'lol' property accessible through its prototype. Hence verbose.lol = ... is not creating a new property, but modifying its prototype's property, which raises an error as you declared it not writable.

I think it all makes sense if you think this way.

EDIT: this is not the right way to see it, read the comments

Saleh answered 9/10, 2014 at 22:56 Comment(2)
That's not actually how things work in JavaScript. When you're assigning to a property (forget about the "writable" thing for now), you never modify any inherited properties - you're always affecting the object directly. That is, when you assign to a property on an object, whether or not there's an inheritable property with the referenced name, you always end up with an "own" property of the target object.Micrometry
You are quite right. As the spec you quoted says, strict mode will act as if you were trying to modify the prototype's property, but it will eventually only modify the object's property if all checks are OK. That's a bit funny.Saleh

© 2022 - 2024 — McMap. All rights reserved.