Skip to content

fuchsia/object-prevent-setters

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Object_preventSetters

preventSetters() freezes an object (as per Object.freeze()) and additionally alters it so its setters throw.

import {preventSetters,areSettersBlocked} from "object-prevent-setters";  
  
const u = new URL( "https://example.com/index.html" );
preventSetters( u );
areSettersBlocked(u) === true;

u.protocol = "http:"; // ***THROWS****

Motivation

Modern javascript objects use setters (on the prototype) to define properties. So Object.freeze() is largely ineffectual:

const u = new URL( "https://example.com/" );
Object.freeze( u );     
u.protocol = "http:"; // This works!
u.toString() === "http://example.com/"

It's for this reason we have abominations like DOMRectReadOnly. Contrast this to, say, C++ where you can just do const URL url = new URL("https://example.com"); and know the URL is unwriteable.

preventSetters() takes a step towards this by re-writing the setters (including those on the prototype) so they honour a hidden flag on the object which blocks setting.

It's not perfect. But if a class's methods use its own setters, then you will get readonly protection:

import {preventSetters} from "object-prevent-setters";

class Pt {
  #x = 0;
  get x() { return this.#x }
  set x(value) { this.#x = value }
  
  translate( dx ) {
     // Note `this.x` and _not_ `this.#x`
     this.x += dx;
  }
}

const p = new Pt, q = new Pt;

Object.freeze(p);
p.translate( 1 ); 
p.x === 1;          

preventSetters( q );
q.translate( 1 ); //< **throws**

Installation

npm i object-prevent-setters

There's just a single file main.mjs which can be called from the browser. It exports two functions: preventSetters() and areSettersBlocked()

Usage

preventSetters(object)

  • object Anything.
  • Return: object

This takes an object and rewrites its own setters, and all the setters on its prototype chain, so that they throw when used on this object.

It then freezes the object (with Object.freeze()).

In common with Object.freeze(), non-object values are ignored. For convienece, object is returned.

areSettersBlocked(object)

  • object anything.
  • Return: boolean

Returns true iff preventSetters() has been called on the object. Non-objects return false.

Limitations and implementation details

  • This is trivial to break; for example:

         const date = new Date(0);
         assert.equal( date.getUTCHours(), 0 );
         preventSetters( date );
         assert.doesNotThrow( () => date.setUTCHours(1) );     
         assert.equal( date.getUTCHours(), 1 );

    (Issue: should we special case date to block this? Are there any other obvious classes where we might apply this?)

    But more subtle cases are possible. For example:

         const u = new URL( "https://example.com/index.html" );
         preventSetters( u );
         assert.throws( () => u.search = "?some=thing" );
         u.searchParams.append( "some", "thing" );
         assert.equal( u.toString(), "https://example.com/index.html?some=thing" );  
  • Setters can be inserted onto the object's prototype after preventSetters() has been called and they won't be blocked.

  • A private field is added to objects passed to preventSetters(), in addition to its setters being rewritten, and that changes the "shape" of the object - which will hurt performance in most engines. It means objects which have and haven't been through preventSetters() will have different "shapes" so there's concealed polymorphism.

  • The entire prototype chain of an object is monkey-patched with all the setters being rewritten, and each prototype object then has the private field added. Suffice to say, peformance will suffer. But if this proves popular, who knows TC39 may decide to implement it for real. ;)

My recommendation would be:

  1. Only use preventSetters() on your own classes.
  2. call preventSetters() immediately after the declaraion of a class; for example:
     class C {
        // ... 
     };
     preventSetters( new C );   
    This means the changes to the prototype happen before most classes are instanced. I might add a class decorator, once decorators become mainstream.

Changes

  • 1.0.1 Fixed some typos in README.md (the examples used the old repository name).

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published