Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Object.isExtensible() is true, but Object.defineProperty() throws. Why?

Tags:

javascript

In Firefox and Safari I'm seeing a situation where Object.isExtensible() is true, I can add a new property with regular property assignment (ie, o.x = y), but Object.defineProperty() throws "TypeError: Object.defineProperty(...) is not extensible".

This is happening with typed arrays, maybe other types as well. I'm trying to use isExtensible() to make sure that I don't try to define a property on the object.

I can put the defineProperty in a try/catch, but I'd like to understand what's going on here. Any ideas?

Here's an example on jsfiddle: http://jsfiddle.net/justinfagnani/qvgnk/

And the code:

function addProperty(o, name, value) {
  if (Object.isExtensible(o)) {
    Object.defineProperty(o, name, {'value': value});
    return true;
  }
  return false;
}

console.log(addProperty(new Date(), 'foo', 1));
console.log(addProperty(new ArrayBuffer(), 'foo', 1));

This should at least print true or false for each call. In Firefox it throws on the ArrayBuffer.

like image 488
Justin Fagnani Avatar asked Mar 23 '23 01:03

Justin Fagnani


2 Answers

Note: typed arrays are now in the ES6/ES2015 specification, where ArrayBuffers are actually extensible. This answer is provided for prior reference.


The Typed Array specification does not explicitly say anything about this, though it uses Web IDL to describe the objects created, which says the following:

Unless otherwise specified, the [[Extensible]] internal property of objects defined in this section has the value true.

I'm not sure why they have put this in the specification, but some reasons why they might have chosen to make [[Extensible]] true even if it isn't extensible include:

  • Wanting to allow extensions from specific functions/paths (prohibited when it is false)
  • Leaving open the possibility of allowing extending a host object (prohibited to change from false to true)

According the the ECMAScript specification (the "standard" version of JavaScript), whether a host object throws on setting a variable or not is implementation dependent (§8.6.2):

Host objects may implement these internal methods in any manner unless specified otherwise; for example, one possibility is that [[Get]] and [[Put]] for a particular host object indeed fetch and store property values but [[HasProperty]] always generates false. However, if any specified manipulation of a host object's internal properties is not supported by an implementation, that manipulation must throw a TypeError exception when attempted.

None of the invariants given by the specification apply in this instance, so this is technically legal.


Note to clarify: even if [[Extensible]] is true, the host object's internal functions do not have to allow an object to be extended. The specification only mandates that a host object is not extended if [[Extensible]] is false (reference):

The [[DefineOwnProperty]] internal method of a host object must not permit the addition of a new property to a host object if the [[Extensible]] internal property of that host object has been observed by ECMAScript code to be false.

The descriptions given in the specification in Table 8/9 (§8.6.2) are misleading themselves in that they only concern native ECMAScript objects, not host objects - this is specifically said in the paragraphs preceding Table 8, that host objects are not subject to implementing the properties as described in those tables.

like image 123
Qantas 94 Heavy Avatar answered Apr 07 '23 02:04

Qantas 94 Heavy


I believe this behavior is dependent on strict mode and the JavaScript engine.

In V8 (Chrome), if strict mode is off, o.x = y will succeed, but not assign anything to o.x, while Object.defineProperty will always throw an exception, regardless of strict mode.

In strict mode o.x will throw an exception.

In Rhino Object.defineProperty will throw an exception and o.x = y will silently fail.

Here are some examples:

/usr/local/Cellar/tomcat6/6.0.37 >rhino -strict
Rhino 1.7 release 4 2012 06 18
js> var o = {a: 132};
js> Object.freeze(o);
[object Object]
js> Object.isExtensible(o);
false
js> o.x = 789;
789
js> o.x;
js> Object.defineProperty(o,'name',{value: 789})
js: uncaught JavaScript runtime exception: TypeError: Cannot add properties to this object because extensible is false.

In V8

/usr/local/Cellar/tomcat6/6.0.37 >node
> var o = {a: 123}
undefined
> Object.freeze(o)
{ a: 123 }   
> Object.isExtensible(o)
false
> o.x = 13
13
> o.x
undefined
> Object.defineProperty(o,'name',{value:123})
TypeError: Cannot define property:name, object is not extensible.
    at Function.defineProperty (native)
    at repl:1:9
    at REPLServer.self.eval (repl.js:110:21)
    at Interface.<anonymous> (repl.js:239:12)
    at Interface.EventEmitter.emit (events.js:95:17)
    at Interface._onLine (readline.js:202:10)
    at Interface._line (readline.js:531:8)
    at Interface._ttyWrite (readline.js:760:14)
    at ReadStream.onkeypress (readline.js:99:10)
    at ReadStream.EventEmitter.emit (events.js:98:17)

In strict mode

/usr/local/Cellar/tomcat6/6.0.37 >node --use_strict
> var o = {a: 123};
undefined
> Object.freeze(o);
{ a: 123 }
> Object.isExtensible(o);
false
> o.x = 13
TypeError: Can't add property x, object is not extensible
    at repl:1:6
    at REPLServer.self.eval (repl.js:110:21)
    at Interface.<anonymous> (repl.js:239:12)
    at Interface.EventEmitter.emit (events.js:95:17)
    at Interface._onLine (readline.js:202:10)
    at Interface._line (readline.js:531:8)
    at Interface._ttyWrite (readline.js:760:14)
    at ReadStream.onkeypress (readline.js:99:10)
    at ReadStream.EventEmitter.emit (events.js:98:17)
    at emitKey (readline.js:1095:12)
like image 32
sshaw Avatar answered Apr 07 '23 01:04

sshaw