简体   繁体   中英

Weird behaviour with 'use strict' and read only properties

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

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM