The way too handle readonly properties in the prototype.

# Xavier MONTILLET (13 years ago)

First of all here's the code that made me thought if this:

function Constructor( ) { } Constructor.prototype.property = 'prototype'; var object = new Constructor( ); object.property = 'instance'; console.log( object.property );// 'instance'

function Constructor( ) { } Object.defineProperty( Constructor.prototype, 'property', { value: 'prototype' } ); var object = new Constructor( ); object.property = 'instance'; console.log( object.property );// 'instance'

function Constructor( ) { } Object.defineProperty( Constructor.prototype, 'property', { value: 'prototype', writable: false } ); var object = new Constructor( ); object.property = 'instance'; console.log( object.property );// FF: 'prototype', Chrome: 'instance'

As you can see, the only difference is on the last line.

Let's say you have an object "O", whose prototype is "P". The difference in the behavior happens if "P" has a readonly (writable: false) value. Let's call the property "p". Setting "P.p" is forbidden and reading "O.p" or "P.p" is allowed. But what about setting "O.p"? Firefox says you can't while Chrome says you can.

When I say Firefox says, you can't I mean you really can't:

( function ( ) { 'use strict'; function Constructor ( ) { } Object.defineProperty( Constructor.prototype, 'property', { value: 'prototype', writable: false } ); var object = new Constructor( ); object.property = 'instance';// Error: 'object.property is read-only' (in Firefox) } )( );

I'm quite sure this is a bug. But I find this behavior interesting. Defining properties of the prototype with Object.defineProperty lets you prevent other instances or anything else from modifying common methods. But most of the time, at least for methods, you want them to remain the same for the life of the object. Here is an example: Let's say you want to prevent anything stupid from happening to the methods you need:

function Constructor( ) { } Object.defineProperty( Constructor.prototype, 'method', { value: function ( ) { }, writable: false } );

Now, your instances are "safe" since their method can't be removed. But sometimes, you don't even want the person using the object to be able to set another next method for you object. You could prevent him from doing so by doing this:

function Constructor ( ) { Object.defineProperty( this, 'method', { value: function ( ) { }, writable: false } ); }

but then, your methods won't be shared between instances.


To put it in a nuttshell:

  • Is it the normal behavior that if a property is protected on the prototype, you can't set it on the object?
  • I think it is a behavior that could be used in some cases, even though it isn't suited as default behavior.
# Brendan Eich (13 years ago)

On Sep 17, 2011, at 1:14 PM, Xavier MONTILLET wrote:

function Constructor( ) { } Object.defineProperty( Constructor.prototype, 'property', { value: 'prototype' } );

NB: writable is undefined in that property descriptor, and undefined is falsy.

var object = new Constructor( ); object.property = 'instance'; console.log( object.property );// 'instance'

No, 'prototype' in a conforming implementation (tested in SpiderMonkey shell and Firefox).

You're re-discovering a v8 bug that I believe is already reported, possibly even fixed. The ES5.1 spec is clear on this.

# Mark S. Miller (13 years ago)

On Sat, Sep 17, 2011 at 10:14 AM, Xavier MONTILLET <xavierm02.net at gmail.com>wrote:

When I say Firefox says, you can't I mean you really can't:

( function ( ) { 'use strict'; function Constructor ( ) { } Object.defineProperty( Constructor.prototype, 'property', { value: 'prototype', writable: false } ); var object = new Constructor( ); object.property = 'instance';// Error: 'object.property is read-only' (in Firefox) } )( );

I'm quite sure this is a bug.

Yes, as Brendan says, ES5.1 demands that it throw a TypeError.

But I find this behavior interesting. Defining properties of the prototype with Object.defineProperty lets you prevent other instances or anything else from modifying common methods.

Hi Xavier, despite appearances, this is not a protection mechanism and in fact provides no protection. The following returns 'instance' on Firefox, as it must according to the ES5.1 spec:

( function ( ) { 'use strict'; function Constructor ( ) { } Object.defineProperty( Constructor.prototype, 'property', { value: 'prototype', writable: false } ); var object = new Constructor( ); Object.defineProperty(object, 'property', {value: 'instance'}); return object.property; } )( );

The behavior is there for compatibility with ES3, not for protection. In fact, from a protection perspective, this behavior is quite unfortunate. It means that if you lock down your primordial prototypes naively, as SES currently does during its initialization, to prevent prototype poisoning and the implicit sharing of prototypes from being a communications channel, then legacy programs that do not monkey patching of primordial prototypes, as so should be SES-compatible, nevertheless fail to override inherited methods by simple assignment. This incompatibility is only an irritation, since it provides no protection.

Fortunately, there is a less naive way to lock down the primordial prototypes that don't create this incompatibility. SES will move to that soon.

But most of the time, at least for methods, you want them to remain the same for the life of the object. Here is an example: Let's say you want to prevent anything stupid from happening to the methods you need:

function Constructor( ) { } Object.defineProperty( Constructor.prototype, 'method', { value: function ( ) { }, writable: false } );

Now, your instances are "safe" since their method can't be removed. But sometimes, you don't even want the person using the object to be able to set another next method for you object. You could prevent him from doing so by doing this:

function Constructor ( ) { Object.defineProperty( this, 'method', { value: function ( ) { }, writable: false } ); }

but then, your methods won't be shared between instances.

This is the right thing surprisingly often, although it does cause an extra allocation per method per instance on most modern JS engines. Although each instance gets its own method object, all these method objects still do share code. So it depends on why you want sharing between instances.

If what you want is to place a protected method on the prototype and prevent anyone outside the abstraction from overriding this method on the instance, you can do this without the extra allocation by having the constructor seal, freeze, or preventExtensions on the instance, depending on what kind of protection you want. Note that you still can't prevent overriding by "external" inheritance, i.e., inheritance outside the abstraction:

var naughty = Object.create(new Constructor(), { method: { value: function myNaughtyOverrideMethod() {...}} });

If you then invoke naughty.foo(), where foo is inherited from Constructor.prototype that does a "this.method()", the .method() that foo() will invoke is your naughty override.

The overall lesson is that it is tricky, but not impossible, to write defensive code using "this" in a secured JavaScript. If you avoid "this" in your code, as a simple variant of your method-per-instance pattern can, then it becomes vastly easier to write defensive code in a secured JS.


To put it in a nuttshell:

  • Is it the normal behavior that if a property is protected on the prototype, you can't set it on the object?

Using assignment, you can't. Using Object.defineProperty, you can, so long as the object is still extensible. Thus it is a legacy compatibility irritant, rather than something offering any protection.

  • I think it is a behavior that could be used in some cases, even though it isn't suited as default behavior.

I would enjoy seeing an example where this half-protection is actually useful.